feat: add tokio as a dev dependency and implement unit tests for settings service
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1339,6 +1339,7 @@ dependencies = [
|
||||
"tauri-plugin-log",
|
||||
"tauri-plugin-opener",
|
||||
"thiserror 2.0.18",
|
||||
"tokio",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
||||
@@ -34,6 +34,10 @@ tauri-plugin-log = "2.8.0"
|
||||
log = "0.4.29"
|
||||
struct_iterable = "0.1.1"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true }
|
||||
sea-orm = { workspace = true, features = ["mock"] }
|
||||
|
||||
[profile.dev]
|
||||
incremental = true
|
||||
|
||||
|
||||
@@ -157,3 +157,430 @@ impl SettingsService for SettingsServiceImpl {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use chrono::NaiveDateTime;
|
||||
use sea_orm::{DatabaseBackend, MockDatabase, MockExecResult};
|
||||
|
||||
fn create_mock_setting(key: &str, value: &str) -> settings::Model {
|
||||
settings::Model {
|
||||
key: key.to_string(),
|
||||
value: Some(value.to_string()),
|
||||
updated_at: NaiveDateTime::parse_from_str("2024-01-01 00:00:00", "%Y-%m-%d %H:%M:%S")
|
||||
.expect("Failed to parse datetime"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_settings_empty() {
|
||||
let db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.into_connection();
|
||||
|
||||
let service = SettingsServiceImpl::new(db);
|
||||
let result = service.get_settings(None).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let settings = result.expect("Failed to get settings");
|
||||
// Should return default settings when no settings exist
|
||||
assert_eq!(settings.language.to_string(), "en");
|
||||
assert_eq!(settings.default_currency, "HKD");
|
||||
assert_eq!(settings.base_currency, "HKD");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_settings_with_values() {
|
||||
let db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||
.append_query_results(vec![vec![
|
||||
create_mock_setting("language", "zh-TW"),
|
||||
create_mock_setting("default_currency", "USD"),
|
||||
create_mock_setting("base_currency", "EUR"),
|
||||
create_mock_setting("timezone", "Asia/Tokyo"),
|
||||
] as Vec<settings::Model>])
|
||||
.into_connection();
|
||||
|
||||
let service = SettingsServiceImpl::new(db);
|
||||
let result = service.get_settings(None).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let settings = result.expect("Failed to get settings");
|
||||
assert_eq!(settings.language.to_string(), "zh-TW");
|
||||
assert_eq!(settings.default_currency, "USD");
|
||||
assert_eq!(settings.base_currency, "EUR");
|
||||
assert_eq!(settings.timezone, "Asia/Tokyo");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_setting_insert_new() {
|
||||
// Mock: find_by_id returns None (setting doesn't exist)
|
||||
// Insert returns the inserted model via RETURNING clause
|
||||
let db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>]) // find_by_id
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("language", "zh-HK")] as Vec<settings::Model>
|
||||
]) // insert returning
|
||||
.append_exec_results(vec![MockExecResult {
|
||||
last_insert_id: 1,
|
||||
rows_affected: 1,
|
||||
}])
|
||||
.into_connection();
|
||||
|
||||
let service = SettingsServiceImpl::new(db);
|
||||
let result = service
|
||||
.update_setting("language".to_string(), "zh-HK".to_string(), None)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_setting_update_existing() {
|
||||
// Mock: find_by_id returns existing setting
|
||||
// Update returns the updated model via RETURNING clause
|
||||
let db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("language", "en")] as Vec<settings::Model>
|
||||
]) // find_by_id
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("language", "zh-TW")] as Vec<settings::Model>
|
||||
]) // update returning
|
||||
.append_exec_results(vec![MockExecResult {
|
||||
last_insert_id: 0,
|
||||
rows_affected: 1,
|
||||
}])
|
||||
.into_connection();
|
||||
|
||||
let service = SettingsServiceImpl::new(db);
|
||||
let result = service
|
||||
.update_setting("language".to_string(), "zh-TW".to_string(), None)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_settings_multiple() {
|
||||
// Mock two update operations
|
||||
let db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||
// First update: language - find returns None, then insert
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("language", "zh-TW")] as Vec<settings::Model>
|
||||
])
|
||||
.append_exec_results(vec![MockExecResult {
|
||||
last_insert_id: 1,
|
||||
rows_affected: 1,
|
||||
}])
|
||||
// Second update: theme - find returns None, then insert
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("theme", "dark")] as Vec<settings::Model>
|
||||
])
|
||||
.append_exec_results(vec![MockExecResult {
|
||||
last_insert_id: 2,
|
||||
rows_affected: 1,
|
||||
}])
|
||||
.into_connection();
|
||||
|
||||
let service = SettingsServiceImpl::new(db);
|
||||
let input = UpdateSettingsInput {
|
||||
settings: {
|
||||
let mut map = HashMap::new();
|
||||
map.insert("language".to_string(), "zh-TW".to_string());
|
||||
map.insert("theme".to_string(), "dark".to_string());
|
||||
map
|
||||
},
|
||||
};
|
||||
let result = service.update_settings(input, None).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_initialize_default_settings() {
|
||||
// Settings has 12 fields, we mock: 1 exists + 11 inserts
|
||||
// For each insert: initialize_default_settings does find_by_id, then update_setting does (find_by_id + insert)
|
||||
// So each insert needs 2 find queries + 1 insert returning query + 1 exec
|
||||
let db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||
// Check language (exists) - only find, no insert
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("language", "en")] as Vec<settings::Model>
|
||||
])
|
||||
// Check default_currency (not exists) -> update_setting -> find + insert
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>]) // initialize_default_settings find
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>]) // update_setting find
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("default_currency", "HKD")] as Vec<settings::Model>
|
||||
]) // insert returning
|
||||
.append_exec_results(vec![MockExecResult {
|
||||
last_insert_id: 1,
|
||||
rows_affected: 1,
|
||||
}])
|
||||
// Check base_currency (not exists) -> insert
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("base_currency", "HKD")] as Vec<settings::Model>
|
||||
])
|
||||
.append_exec_results(vec![MockExecResult {
|
||||
last_insert_id: 2,
|
||||
rows_affected: 1,
|
||||
}])
|
||||
// Check timezone (not exists) -> insert
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("timezone", "auto")] as Vec<settings::Model>
|
||||
])
|
||||
.append_exec_results(vec![MockExecResult {
|
||||
last_insert_id: 3,
|
||||
rows_affected: 1,
|
||||
}])
|
||||
// Check default_view (not exists) -> insert
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("default_view", "combined")] as Vec<settings::Model>
|
||||
])
|
||||
.append_exec_results(vec![MockExecResult {
|
||||
last_insert_id: 4,
|
||||
rows_affected: 1,
|
||||
}])
|
||||
// Check display_mode (not exists) -> insert
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("display_mode", "system")] as Vec<settings::Model>
|
||||
])
|
||||
.append_exec_results(vec![MockExecResult {
|
||||
last_insert_id: 5,
|
||||
rows_affected: 1,
|
||||
}])
|
||||
// Check decimal_places (not exists) -> insert
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("decimal_places", "2")] as Vec<settings::Model>
|
||||
])
|
||||
.append_exec_results(vec![MockExecResult {
|
||||
last_insert_id: 6,
|
||||
rows_affected: 1,
|
||||
}])
|
||||
// Check date_format (not exists) -> insert
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("date_format", "YYYY-MM-DD")] as Vec<settings::Model>
|
||||
])
|
||||
.append_exec_results(vec![MockExecResult {
|
||||
last_insert_id: 7,
|
||||
rows_affected: 1,
|
||||
}])
|
||||
// Check time_format (not exists) -> insert
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("time_format", "24h")] as Vec<settings::Model>
|
||||
])
|
||||
.append_exec_results(vec![MockExecResult {
|
||||
last_insert_id: 8,
|
||||
rows_affected: 1,
|
||||
}])
|
||||
// Check theme (not exists) -> insert
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("theme", "system")] as Vec<settings::Model>
|
||||
])
|
||||
.append_exec_results(vec![MockExecResult {
|
||||
last_insert_id: 9,
|
||||
rows_affected: 1,
|
||||
}])
|
||||
// Check week_starts_on (not exists) -> insert
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("week_starts_on", "sunday")] as Vec<settings::Model>
|
||||
])
|
||||
.append_exec_results(vec![MockExecResult {
|
||||
last_insert_id: 10,
|
||||
rows_affected: 1,
|
||||
}])
|
||||
// Check scheduled_check_interval (not exists) -> insert
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("scheduled_check_interval", "1")] as Vec<settings::Model>,
|
||||
])
|
||||
.append_exec_results(vec![MockExecResult {
|
||||
last_insert_id: 11,
|
||||
rows_affected: 1,
|
||||
}])
|
||||
.into_connection();
|
||||
|
||||
let service = SettingsServiceImpl::new(db);
|
||||
let result = service.initialize_default_settings(None).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_initialize_default_settings_all_exist() {
|
||||
// Mock scenario where all default settings already exist
|
||||
let db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("language", "en")] as Vec<settings::Model>
|
||||
])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("default_currency", "HKD")] as Vec<settings::Model>
|
||||
])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("base_currency", "HKD")] as Vec<settings::Model>
|
||||
])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("timezone", "auto")] as Vec<settings::Model>
|
||||
])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("default_view", "combined")] as Vec<settings::Model>
|
||||
])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("display_mode", "system")] as Vec<settings::Model>
|
||||
])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("decimal_places", "2")] as Vec<settings::Model>
|
||||
])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("date_format", "YYYY-MM-DD")] as Vec<settings::Model>
|
||||
])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("time_format", "24h")] as Vec<settings::Model>
|
||||
])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("theme", "system")] as Vec<settings::Model>
|
||||
])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("week_starts_on", "sunday")] as Vec<settings::Model>
|
||||
])
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("scheduled_check_interval", "1")] as Vec<settings::Model>,
|
||||
])
|
||||
.into_connection();
|
||||
|
||||
let service = SettingsServiceImpl::new(db);
|
||||
let result = service.initialize_default_settings(None).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_setting_with_default() {
|
||||
let db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.into_connection();
|
||||
|
||||
let service = SettingsServiceImpl::new(db);
|
||||
let result = service
|
||||
.get_setting("nonexistent_key", "default_value")
|
||||
.await;
|
||||
|
||||
assert_eq!(result, "default_value");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_get_setting_with_value() {
|
||||
let db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("existing_key", "stored_value")] as Vec<settings::Model>,
|
||||
])
|
||||
.into_connection();
|
||||
|
||||
let service = SettingsServiceImpl::new(db);
|
||||
let result = service.get_setting("existing_key", "default_value").await;
|
||||
|
||||
assert_eq!(result, "stored_value");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_settings_with_all_fields() {
|
||||
let db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||
.append_query_results(vec![vec![
|
||||
create_mock_setting("language", "zh-HK"),
|
||||
create_mock_setting("default_currency", "USD"),
|
||||
create_mock_setting("base_currency", "EUR"),
|
||||
create_mock_setting("timezone", "UTC"),
|
||||
create_mock_setting("default_view", "split"),
|
||||
create_mock_setting("display_mode", "dark"),
|
||||
create_mock_setting("decimal_places", "4"),
|
||||
create_mock_setting("date_format", "DD/MM/YYYY"),
|
||||
create_mock_setting("time_format", "12h"),
|
||||
create_mock_setting("theme", "light"),
|
||||
create_mock_setting("week_starts_on", "monday"),
|
||||
create_mock_setting("scheduled_check_interval", "7"),
|
||||
] as Vec<settings::Model>])
|
||||
.into_connection();
|
||||
|
||||
let service = SettingsServiceImpl::new(db);
|
||||
let result = service.get_settings(None).await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
let settings = result.expect("Failed to get settings");
|
||||
assert_eq!(settings.language.to_string(), "zh-HK");
|
||||
assert_eq!(settings.default_currency, "USD");
|
||||
assert_eq!(settings.base_currency, "EUR");
|
||||
assert_eq!(settings.timezone, "UTC");
|
||||
assert_eq!(settings.default_view.to_string(), "split");
|
||||
assert_eq!(settings.display_mode.to_string(), "dark");
|
||||
assert_eq!(settings.decimal_places, 4);
|
||||
assert_eq!(settings.date_format, "DD/MM/YYYY");
|
||||
assert_eq!(settings.time_format, "12h");
|
||||
assert_eq!(settings.theme.to_string(), "light");
|
||||
assert_eq!(settings.week_starts_on.to_string(), "monday");
|
||||
assert_eq!(settings.scheduled_check_interval, 7);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_update_setting_with_null_value_in_db() {
|
||||
let setting_with_null = settings::Model {
|
||||
key: "test_key".to_string(),
|
||||
value: None,
|
||||
updated_at: NaiveDateTime::parse_from_str("2024-01-01 00:00:00", "%Y-%m-%d %H:%M:%S")
|
||||
.expect("Failed to parse datetime"),
|
||||
};
|
||||
|
||||
let db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||
.append_query_results(vec![vec![setting_with_null] as Vec<settings::Model>]) // find_by_id
|
||||
.append_query_results(vec![
|
||||
vec![create_mock_setting("test_key", "new_value")] as Vec<settings::Model>
|
||||
]) // update returning
|
||||
.append_exec_results(vec![MockExecResult {
|
||||
last_insert_id: 0,
|
||||
rows_affected: 1,
|
||||
}])
|
||||
.into_connection();
|
||||
|
||||
let service = SettingsServiceImpl::new(db);
|
||||
let result = service
|
||||
.update_setting("test_key".to_string(), "new_value".to_string(), None)
|
||||
.await;
|
||||
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_settings_transaction_logs() {
|
||||
let db = MockDatabase::new(DatabaseBackend::Sqlite)
|
||||
.append_query_results(vec![vec![] as Vec<settings::Model>])
|
||||
.append_exec_results(vec![MockExecResult {
|
||||
last_insert_id: 0,
|
||||
rows_affected: 1,
|
||||
}])
|
||||
.into_connection();
|
||||
|
||||
let service = SettingsServiceImpl::new(db);
|
||||
|
||||
// Verify that the database backend is SQLite
|
||||
assert_eq!(service.db.get_database_backend(), DatabaseBackend::Sqlite);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user