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 {
database_connection: db_connection.clone(),
config: Arc::new(settings.clone()),
service: Arc::new(AppService {
server_state: Arc::new(ServerStateService::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 {
fn from_config(config: &Config) -> Result<Self, String>;
fn validate(&self) -> Result<(), String>;
#[cfg(test)]
fn mock() -> Self;
}
#[derive(Debug, Clone)]
@@ -40,6 +42,16 @@ impl FromConfig for ProgramSettings {
self.auth.validate()?;
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 {

View File

@@ -48,4 +48,13 @@ impl FromConfig for AuthSettings {
fn validate(&self) -> Result<(), String> {
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> {
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_SERVE_OPENAPI_KEY: &str = "SERVER.SERVE_OPENAPI";
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_MAX_CONNECTIONS_KEY: &str = "DATABASE.MAX_CONNECTIONS";

View File

@@ -49,4 +49,12 @@ impl FromConfig for LoggingSettings {
fn validate(&self) -> Result<(), String> {
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 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::{
FromConfig,
@@ -16,6 +16,7 @@ pub struct ServerSettings {
pub port: u16,
pub serve_openapi: bool,
pub cors: CORSSettings,
pub cookies: CookiesSettings,
}
#[derive(Debug, Clone)]
@@ -23,6 +24,11 @@ pub struct CORSSettings {
pub allowed_origins: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct CookiesSettings {
pub secure: bool,
}
impl FromConfig for ServerSettings {
fn from_config(_config: &Config) -> Result<Self, String> {
Ok(ServerSettings {
@@ -81,6 +87,24 @@ impl FromConfig for ServerSettings {
})
.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(())
}
}
#[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 crate::{
configs::server::CORSSettings,
configs::{ProgramSettings, server::CORSSettings},
middlewares,
services::{
auth::{
@@ -23,10 +23,9 @@ use crate::{
#[derive(Clone)]
pub struct AppState {
#[allow(dead_code)]
pub database_connection: Arc<DatabaseConnection>,
#[allow(dead_code)]
pub service: Arc<AppService>,
pub config: Arc<ProgramSettings>,
}
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(
SET_COOKIE,
format!(
"{}={}; HttpOnly; Path=/; Max-Age={}; SameSite=Strict;",
"{}={}; HttpOnly; Path=/; Max-Age={}; SameSite=Strict;{}",
JWT_COOKIE_NAME,
jwt,
claims.exp - claims.iat
claims.exp - claims.iat,
if state.config.server.cookies.secure {
" Secure;"
} else {
""
}
),
)
.body(Body::from(()));

View File

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