Testing setup working, settings moved to a singleton and initialized on

main.
This commit is contained in:
rzuasti
2026-02-18 11:23:20 -05:00
parent 93b27c7205
commit 54e6dba496
13 changed files with 140 additions and 67 deletions
+6 -2
View File
@@ -9,7 +9,7 @@ use r2d2_sqlite::SqliteConnectionManager;
use rusqlite_migration::Migrations;
use tokio::sync::Mutex;
use crate::settings;
use crate::settings::get_settings;
static MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/database_migrations");
static INITIALISED: Mutex<bool> = Mutex::const_new(false);
@@ -20,7 +20,11 @@ lazy_static! {
Migrations::from_directory(&MIGRATIONS_DIR).unwrap();
// TODO : Move pool size to configuration file
static ref POOL: r2d2::Pool<SqliteConnectionManager> = r2d2::Pool::builder().max_size(10).build(r2d2_sqlite::SqliteConnectionManager::file(settings::CONFIG.database.path.as_str())).unwrap();
static ref POOL: r2d2::Pool<SqliteConnectionManager> = r2d2::Pool::builder().
max_size(10).
build(
r2d2_sqlite::SqliteConnectionManager::file(get_settings().database.path.as_str())
).unwrap();
}
pub fn get_db_connection() -> PooledConnection<SqliteConnectionManager> {
+3 -1
View File
@@ -1,8 +1,10 @@
use chrono::Local;
use log::debug;
use crate::model::{self, notifications::Notification};
pub fn list() -> Vec<model::notifications::Notification> {
debug!("Listing notifications");
let mut result = Vec::new();
result.push(Notification {
id: 1,
@@ -23,7 +25,7 @@ mod tests {
#[tokio::test]
async fn list_default() {
tests_common::setup_database().await;
tests_common::setup().await;
let notifications = list();
assert_eq!(notifications.len(), 1);
}
+8 -5
View File
@@ -1,7 +1,7 @@
mod packet_send_receive;
use crate::model::devices::Device;
use crate::settings::CONFIG;
use crate::settings::get_settings;
use duration_string::DurationString;
use log::{debug, error, info, warn};
use packet_send_receive::{listen_for_packets, send_packet};
@@ -83,14 +83,17 @@ pub async fn find(interface: String) -> Result<Vec<Device>, String> {
let send_interface = network_interface.clone();
// Get timeouts
let sender_timeout: Duration = CONFIG.timings.arp_sender_timeout.into();
let sender_timeout: Duration = get_settings().timings.arp_sender_timeout.into();
info!(
"Sender timeout set to {}",
CONFIG.timings.arp_sender_timeout
get_settings().timings.arp_sender_timeout
);
let scan_duration: Duration = CONFIG.timings.arp_scan_duration.into();
let scan_duration: Duration = get_settings().timings.arp_scan_duration.into();
let receiver_timeout: Duration = scan_duration * 2;
info!("Scan duration set to {}", CONFIG.timings.arp_scan_duration);
info!(
"Scan duration set to {}",
get_settings().timings.arp_scan_duration
);
info!(
"Receiver timeout set to {}",
String::from(DurationString::from(receiver_timeout))
+5 -3
View File
@@ -3,7 +3,7 @@ mod pushover;
use std::time::Duration;
use crate::model::devices::Device;
use crate::settings::CONFIG;
use crate::settings::get_settings;
use chrono::Local;
use duration_string::DurationString;
use log::{debug, info, warn};
@@ -12,7 +12,7 @@ use log::{debug, info, warn};
fn send_message(body: String) -> Result<(), String> {
debug!("About to send notification ({body}).");
match CONFIG.notifications.method.as_str() {
match get_settings().notifications.method.as_str() {
"pushover" => {
pushover::send_message(body)?;
}
@@ -36,7 +36,9 @@ pub fn trigger_existing_device(existing_device: Device, new_device: Device) -> R
.to_std()
.unwrap_or(Duration::from_secs(0));
if elapsed_since_last_seen >= Duration::from(CONFIG.notifications.notify_when_not_seen_for) {
if elapsed_since_last_seen
>= Duration::from(get_settings().notifications.notify_when_not_seen_for)
{
send_message(format!(
"Device MAC {} - IP {} - Vendor {} came back online after {}.",
new_device.mac_address,
+3 -3
View File
@@ -2,15 +2,15 @@ use log::{debug, error};
use pushover::API;
use pushover::requests::message::SendMessage;
use crate::settings::CONFIG;
use crate::settings::get_settings;
pub fn send_message(body: String) -> Result<(), String> {
debug!("About to send message via pushover ({body})");
let api = API::new();
let msg = SendMessage::new(
CONFIG.notifications.pushover.token.as_str(),
CONFIG.notifications.pushover.user_key.as_str(),
get_settings().notifications.pushover.token.as_str(),
get_settings().notifications.pushover.user_key.as_str(),
body,
);
+20 -2
View File
@@ -1,4 +1,5 @@
use crate::settings::CONFIG;
use crate::settings::get_settings;
use clap::Parser;
use log::{LevelFilter, info};
mod db;
@@ -13,10 +14,27 @@ mod web_server;
#[cfg(test)]
mod tests_common;
// Command line parameters
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
/// Config file path
#[arg(short, long)]
config: Option<String>,
}
#[tokio::main]
async fn main() -> Result<(), String> {
// Parse command line parameters and init settings
// this is not thread safe so it needs to run just once
let args = Args::parse();
let config_path = args
.config
.unwrap_or(settings::DEFAULT_CONFIG_FILE_PATH.to_string());
settings::init(config_path);
// Initialize logging
let log_level = match CONFIG.log.level.as_str() {
let log_level = match get_settings().log.level.as_str() {
"off" => LevelFilter::Off,
"error" => LevelFilter::Error,
"warn" => LevelFilter::Warn,
+5 -4
View File
@@ -1,14 +1,15 @@
use crate::db;
use crate::device_finders;
use crate::events;
use crate::settings::CONFIG;
use crate::settings::get_settings;
use log::{debug, info};
use tokio::time::{Duration, sleep};
pub async fn scan() -> Result<(), String> {
loop {
// Find online devices via ARP
let devices = device_finders::arp::find(CONFIG.networking.interface.to_string()).await?;
let devices =
device_finders::arp::find(get_settings().networking.interface.to_string()).await?;
info!("Done with ARP probes");
info!("Found {} online devices", devices.iter().count());
@@ -44,8 +45,8 @@ pub async fn scan() -> Result<(), String> {
}
info!(
"Scan finished. Sleeping for {} seconds",
CONFIG.timings.wait_between_scans
get_settings().timings.wait_between_scans
);
sleep(Duration::from(CONFIG.timings.wait_between_scans)).await;
sleep(Duration::from(get_settings().timings.wait_between_scans)).await;
}
}
+22 -32
View File
@@ -1,8 +1,6 @@
use clap::Parser;
use config::{Config, ConfigError, File};
use duration_string::DurationString;
use lazy_static::lazy_static;
use log::{debug, error, info};
use once_cell::sync::OnceCell;
use serde::Deserialize;
// -----------------------------------------------------------
@@ -54,29 +52,9 @@ pub struct Settings {
// End configuration structure
// -----------------------------------------------------------
// Command line parameters relevant to configuration
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
/// Config file path
#[arg(short, long)]
config: Option<String>,
}
const DEFAULT_CONFIG_FILE_PATH: &str = "./oott.toml";
impl Settings {
pub fn new() -> Result<Self, ConfigError> {
debug!("Starting configuration");
let args_result = Args::try_parse();
let config_path = match args_result {
Ok(value) => value.config.unwrap_or(DEFAULT_CONFIG_FILE_PATH.to_string()),
Err(_) => DEFAULT_CONFIG_FILE_PATH.to_string(),
};
info!("Reading configuration from {}", config_path);
pub fn new(config_path: String) -> Result<Self, ConfigError> {
println!("Reading configuration from {}", config_path);
let local_settings = Config::builder()
.add_source(File::with_name(config_path.as_str()))
@@ -86,12 +64,24 @@ impl Settings {
}
}
lazy_static! {
pub static ref CONFIG: Settings = match Settings::new() {
Ok(value) => value,
Err(error) => {
error!("Error reading configuration file: {error}");
panic!("Error reading configuration file: {error}");
pub const DEFAULT_CONFIG_FILE_PATH: &str = "./oott.toml";
static SETTINGS: OnceCell<Settings> = OnceCell::new();
pub fn get_settings() -> &'static Settings {
match SETTINGS.get() {
Some(value) => value,
None => {
println!(
"Configuration was not initialized, reading from default path ({})",
DEFAULT_CONFIG_FILE_PATH
);
let settings = Settings::new(DEFAULT_CONFIG_FILE_PATH.to_string()).unwrap();
let _ = SETTINGS.set(settings);
SETTINGS.get().unwrap()
}
};
}
}
pub fn init(config_path: String) {
let _ = SETTINGS.set(Settings::new(config_path).unwrap());
}
+44 -15
View File
@@ -1,30 +1,59 @@
use crate::db;
use crate::settings::get_settings;
use include_dir::{Dir, include_dir};
use log::{debug, info};
use rusqlite_migration::Migrations;
use std::sync::LazyLock;
use log::{LevelFilter, debug, info};
use tokio::sync::Mutex;
static TEST_MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/tests_database_migrations");
// Define migrations. These are applied atomically.
static TEST_MIGRATIONS: LazyLock<Migrations<'static>> =
LazyLock::new(|| Migrations::from_directory(&TEST_MIGRATIONS_DIR).unwrap());
static TEST_MIGRATIONS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/tests/database_setup");
// Use a Mutex bool as semaphore to ensure single initialization
static INITIALISED: Mutex<bool> = Mutex::const_new(false);
pub async fn setup_database() {
debug!("About to setup database for testing.");
pub async fn setup() {
debug!("About to setup testing.");
let mut initialised = INITIALISED.lock().await;
if *initialised {
debug!("Database was already initialised, nothing to do.");
debug!("Testing was already initialised, nothing to do.");
return;
}
// Initialize logging
let log_level = match get_settings().log.level.as_str() {
"off" => LevelFilter::Off,
"error" => LevelFilter::Error,
"warn" => LevelFilter::Warn,
"info" => LevelFilter::Info,
"debug" => LevelFilter::Debug,
"trace" => LevelFilter::Trace,
_ => LevelFilter::Error,
};
env_logger::Builder::new()
.filter(None, log_level)
.write_style(env_logger::WriteStyle::Always)
.init();
// Initialize database
db::init_db().await.unwrap();
info!("Initializing database for testing.");
let mut conn = db::get_db_connection();
debug!("Running migrations for testing.");
TEST_MIGRATIONS.to_latest(&mut conn).unwrap();
debug!("Testing migrations run on database.");
let conn = db::get_db_connection();
debug!("Running database setup scripts for testing.");
for entry in TEST_MIGRATIONS_DIR.entries() {
if entry.path().extension().map_or(false, |ext| ext == "sql") {
debug!("About to run {}", entry.path().display());
if entry.path().extension().map_or(false, |ext| ext == "sql") {
let sql = entry.as_file().unwrap().contents_utf8().unwrap();
conn.execute(&sql, []).unwrap_or_else(|err| {
panic!("Error executing script: {}", err);
});
};
}
}
debug!("Database setup for testing.");
// Done
*initialised = true;
}