feat: add mock implementations for configuration settings and update AppState to include config

This commit is contained in:
GW_MC
2025-12-20 18:22:33 +08:00
parent 0cd6e837fc
commit 596eb8faea
10 changed files with 92 additions and 7 deletions

View File

@@ -147,6 +147,7 @@ fn get_app_state(
) -> AppState { ) -> AppState {
AppState { AppState {
database_connection: db_connection.clone(), database_connection: db_connection.clone(),
config: Arc::new(settings.clone()),
service: Arc::new(AppService { service: Arc::new(AppService {
server_state: Arc::new(ServerStateService::new(db_connection.clone())), server_state: Arc::new(ServerStateService::new(db_connection.clone())),
settings: Arc::new(SettingsService::new(db_connection.clone())), settings: Arc::new(SettingsService::new(db_connection.clone())),

View File

@@ -11,6 +11,8 @@ use tracing::{debug, error};
pub trait FromConfig: Sized { pub trait FromConfig: Sized {
fn from_config(config: &Config) -> Result<Self, String>; fn from_config(config: &Config) -> Result<Self, String>;
fn validate(&self) -> Result<(), String>; fn validate(&self) -> Result<(), String>;
#[cfg(test)]
fn mock() -> Self;
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -40,6 +42,16 @@ impl FromConfig for ProgramSettings {
self.auth.validate()?; self.auth.validate()?;
Ok(()) Ok(())
} }
#[cfg(test)]
fn mock() -> Self {
ProgramSettings {
logging: logging::LoggingSettings::mock(),
database: database::DatabaseSettings::mock(),
server: server::ServerSettings::mock(),
auth: auth::AuthSettings::mock(),
}
}
} }
pub fn get_program_settings() -> ProgramSettings { pub fn get_program_settings() -> ProgramSettings {

View File

@@ -48,4 +48,13 @@ impl FromConfig for AuthSettings {
fn validate(&self) -> Result<(), String> { fn validate(&self) -> Result<(), String> {
Ok(()) Ok(())
} }
#[cfg(test)]
fn mock() -> Self {
AuthSettings {
jwt_secret: Some("mock_jwt_secret".to_string()),
default_admin_username: Some("admin".to_string()),
default_admin_password: Some("password".to_string()),
}
}
} }

View File

@@ -50,4 +50,13 @@ impl FromConfig for DatabaseSettings {
fn validate(&self) -> Result<(), String> { fn validate(&self) -> Result<(), String> {
Ok(()) Ok(())
} }
#[cfg(test)]
fn mock() -> Self {
DatabaseSettings {
url: "sqlite::memory:".to_string(),
max_connections: 5,
migrate_on_startup: true,
}
}
} }

View File

@@ -5,6 +5,7 @@ pub(crate) const SERVER_ADDRESS_KEY: &str = "SERVER.ADDRESS";
pub(crate) const SERVER_PORT_KEY: &str = "SERVER.PORT"; pub(crate) const SERVER_PORT_KEY: &str = "SERVER.PORT";
pub(crate) const SERVER_SERVE_OPENAPI_KEY: &str = "SERVER.SERVE_OPENAPI"; pub(crate) const SERVER_SERVE_OPENAPI_KEY: &str = "SERVER.SERVE_OPENAPI";
pub(crate) const SERVER_CORS_ALLOWED_ORIGINS_KEY: &str = "SERVER.CORS.ALLOWED_ORIGINS"; pub(crate) const SERVER_CORS_ALLOWED_ORIGINS_KEY: &str = "SERVER.CORS.ALLOWED_ORIGINS";
pub(crate) const SERVER_COOKIES_SECURE_KEY: &str = "SERVER.COOKIES.SECURE";
// //
pub(crate) const DATABASE_URL_KEY: &str = "DATABASE.URL"; pub(crate) const DATABASE_URL_KEY: &str = "DATABASE.URL";
pub(crate) const DATABASE_MAX_CONNECTIONS_KEY: &str = "DATABASE.MAX_CONNECTIONS"; pub(crate) const DATABASE_MAX_CONNECTIONS_KEY: &str = "DATABASE.MAX_CONNECTIONS";

View File

@@ -49,4 +49,12 @@ impl FromConfig for LoggingSettings {
fn validate(&self) -> Result<(), String> { fn validate(&self) -> Result<(), String> {
Ok(()) Ok(())
} }
#[cfg(test)]
fn mock() -> Self {
LoggingSettings {
level: Level::INFO,
utc: false,
}
}
} }

View File

@@ -3,7 +3,7 @@ use std::net::IpAddr;
use config::{Config, ConfigError}; use config::{Config, ConfigError};
use tracing::warn; use tracing::warn;
use crate::configs::key::{SERVER_CORS_ALLOWED_ORIGINS_KEY, SERVER_SERVE_OPENAPI_KEY}; use crate::configs::key::{SERVER_COOKIES_SECURE_KEY, SERVER_CORS_ALLOWED_ORIGINS_KEY, SERVER_SERVE_OPENAPI_KEY};
use super::{ use super::{
FromConfig, FromConfig,
@@ -16,6 +16,7 @@ pub struct ServerSettings {
pub port: u16, pub port: u16,
pub serve_openapi: bool, pub serve_openapi: bool,
pub cors: CORSSettings, pub cors: CORSSettings,
pub cookies: CookiesSettings,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@@ -23,6 +24,11 @@ pub struct CORSSettings {
pub allowed_origins: Vec<String>, pub allowed_origins: Vec<String>,
} }
#[derive(Debug, Clone)]
pub struct CookiesSettings {
pub secure: bool,
}
impl FromConfig for ServerSettings { impl FromConfig for ServerSettings {
fn from_config(_config: &Config) -> Result<Self, String> { fn from_config(_config: &Config) -> Result<Self, String> {
Ok(ServerSettings { Ok(ServerSettings {
@@ -81,6 +87,24 @@ impl FromConfig for ServerSettings {
}) })
.collect(), .collect(),
}, },
cookies: CookiesSettings {
secure: _config
.get_bool(SERVER_COOKIES_SECURE_KEY)
.inspect(|is_secure| {
if !*is_secure {
warn!("Cookie 'secure' flag is disabled; this is not recommended in production environments.");
}
})
.unwrap_or_else(|err| {
const DEFAULT_COOKIES_SECURE: bool = true;
warn!(
"{} not set or invalid in configuration, defaulting to {}. Error: {}",
SERVER_COOKIES_SECURE_KEY, DEFAULT_COOKIES_SECURE, err
);
DEFAULT_COOKIES_SECURE
}),
},
}) })
} }
@@ -91,4 +115,19 @@ impl FromConfig for ServerSettings {
} }
Ok(()) Ok(())
} }
}
#[cfg(test)]
fn mock() -> Self {
ServerSettings {
address: "0.0.0.0".parse().unwrap(),
port: 8080,
serve_openapi: false,
cors: CORSSettings {
allowed_origins: vec![],
},
cookies: CookiesSettings {
secure: true,
},
}
}
}

View File

@@ -9,7 +9,7 @@ use axum::{Extension, Router};
use migration::sea_orm::DatabaseConnection; use migration::sea_orm::DatabaseConnection;
use crate::{ use crate::{
configs::server::CORSSettings, configs::{ProgramSettings, server::CORSSettings},
middlewares, middlewares,
services::{ services::{
auth::{ auth::{
@@ -23,10 +23,9 @@ use crate::{
#[derive(Clone)] #[derive(Clone)]
pub struct AppState { pub struct AppState {
#[allow(dead_code)]
pub database_connection: Arc<DatabaseConnection>, pub database_connection: Arc<DatabaseConnection>,
#[allow(dead_code)]
pub service: Arc<AppService>, pub service: Arc<AppService>,
pub config: Arc<ProgramSettings>,
} }
pub type ServiceState<T> = Arc<T>; pub type ServiceState<T> = Arc<T>;

View File

@@ -84,10 +84,15 @@ pub async fn login(State(state): State<Arc<AppState>>, Json(payload): Json<Value
.header( .header(
SET_COOKIE, SET_COOKIE,
format!( format!(
"{}={}; HttpOnly; Path=/; Max-Age={}; SameSite=Strict;", "{}={}; HttpOnly; Path=/; Max-Age={}; SameSite=Strict;{}",
JWT_COOKIE_NAME, JWT_COOKIE_NAME,
jwt, jwt,
claims.exp - claims.iat claims.exp - claims.iat,
if state.config.server.cookies.secure {
" Secure;"
} else {
""
}
), ),
) )
.body(Body::from(())); .body(Body::from(()));

View File

@@ -78,6 +78,7 @@ pub async fn get_health_info(
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::configs::FromConfig;
use crate::{ use crate::{
routes::{AppState, api::health::state::HealthState}, routes::{AppState, api::health::state::HealthState},
services::{ services::{
@@ -112,6 +113,7 @@ mod test {
let app_state = Arc::new(AppState { let app_state = Arc::new(AppState {
database_connection: db.clone(), database_connection: db.clone(),
config: Arc::new(crate::configs::ProgramSettings::mock()),
service: Arc::new(crate::routes::AppService { service: Arc::new(crate::routes::AppService {
settings: Arc::new(SettingsService::new(db.clone())), settings: Arc::new(SettingsService::new(db.clone())),
auth_state: crate::routes::AuthState { auth_state: crate::routes::AuthState {