feat: add unit tests (Rust parser+DB, Vitest frontend) and test workflow
This commit is contained in:
@ -351,3 +351,184 @@ pub fn set_setting(conn: &Connection, key: &str, value: &str) -> Result<()> {
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::parser::CombatIndicator;
|
||||
|
||||
fn test_db() -> Connection {
|
||||
let conn = Connection::open_in_memory().unwrap();
|
||||
migrate(&conn).unwrap();
|
||||
conn
|
||||
}
|
||||
|
||||
fn make_indicator(t: &str, c: &str) -> CombatIndicator {
|
||||
CombatIndicator {
|
||||
combat_type: t.to_string(),
|
||||
count: c.to_string(),
|
||||
label: None,
|
||||
evitable: false,
|
||||
}
|
||||
}
|
||||
|
||||
// ── migrate ──────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_migrate_creates_all_tables() {
|
||||
let conn = test_db();
|
||||
let expected_tables = [
|
||||
"profiles",
|
||||
"guides",
|
||||
"quest_completions",
|
||||
"settings",
|
||||
"quest_step_progress",
|
||||
"resource_inventory",
|
||||
"quest_previews",
|
||||
];
|
||||
for table in &expected_tables {
|
||||
let count: i64 = conn
|
||||
.query_row(
|
||||
"SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?1",
|
||||
params![table],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(count, 1, "Table '{}' manquante après migration", table);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_migrate_idempotent() {
|
||||
let conn = test_db();
|
||||
// Un second appel ne doit pas paniquer ni retourner d'erreur (IF NOT EXISTS)
|
||||
migrate(&conn).expect("Le second appel à migrate() ne doit pas échouer");
|
||||
}
|
||||
|
||||
// ── settings ─────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_set_and_get_setting_roundtrip() {
|
||||
let conn = test_db();
|
||||
set_setting(&conn, "theme", "dark").unwrap();
|
||||
let value = get_setting(&conn, "theme");
|
||||
assert_eq!(value, Some("dark".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_setting_missing_key_returns_none() {
|
||||
let conn = test_db();
|
||||
let value = get_setting(&conn, "cle_inexistante");
|
||||
assert!(value.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_setting_overwrites_existing_value() {
|
||||
let conn = test_db();
|
||||
set_setting(&conn, "theme", "light").unwrap();
|
||||
set_setting(&conn, "theme", "dark").unwrap();
|
||||
let value = get_setting(&conn, "theme");
|
||||
assert_eq!(value, Some("dark".to_string()));
|
||||
}
|
||||
|
||||
// ── upsert_preview / get_cached_previews ─────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_upsert_and_get_cached_previews_roundtrip() {
|
||||
let conn = test_db();
|
||||
let url = "https://example.com/quete/test".to_string();
|
||||
let indicators = vec![
|
||||
make_indicator("Monstre", "3"),
|
||||
make_indicator("Boss", "1"),
|
||||
];
|
||||
|
||||
upsert_preview(&conn, &url, &indicators).unwrap();
|
||||
|
||||
let result = get_cached_previews(&conn, &[url.clone()]);
|
||||
assert!(result.contains_key(&url), "L'URL doit être présente dans le résultat");
|
||||
|
||||
let retrieved = &result[&url];
|
||||
assert_eq!(retrieved.len(), 2);
|
||||
assert_eq!(retrieved[0].combat_type, "Monstre");
|
||||
assert_eq!(retrieved[0].count, "3");
|
||||
assert_eq!(retrieved[1].combat_type, "Boss");
|
||||
assert_eq!(retrieved[1].count, "1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_upsert_preview_idempotent() {
|
||||
let conn = test_db();
|
||||
let url = "https://example.com/quete/idempotent".to_string();
|
||||
let indicators_v1 = vec![make_indicator("Monstre", "2")];
|
||||
let indicators_v2 = vec![make_indicator("Boss", "5")];
|
||||
|
||||
upsert_preview(&conn, &url, &indicators_v1).unwrap();
|
||||
// Deuxième upsert sur la même URL : pas d'erreur de contrainte UNIQUE
|
||||
upsert_preview(&conn, &url, &indicators_v2).unwrap();
|
||||
|
||||
let result = get_cached_previews(&conn, &[url.clone()]);
|
||||
let retrieved = &result[&url];
|
||||
// Seul le dernier upsert doit être présent
|
||||
assert_eq!(retrieved.len(), 1);
|
||||
assert_eq!(retrieved[0].combat_type, "Boss");
|
||||
assert_eq!(retrieved[0].count, "5");
|
||||
}
|
||||
|
||||
// ── get_cached_previews — cas limites ────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_get_cached_previews_partial_hit() {
|
||||
let conn = test_db();
|
||||
let url_cached = "https://example.com/quete/en-cache".to_string();
|
||||
let url_missing = "https://example.com/quete/absente".to_string();
|
||||
|
||||
upsert_preview(&conn, &url_cached, &[make_indicator("Monstre", "1")]).unwrap();
|
||||
|
||||
let result = get_cached_previews(&conn, &[url_cached.clone(), url_missing.clone()]);
|
||||
assert!(result.contains_key(&url_cached), "L'URL en cache doit être retournée");
|
||||
assert!(!result.contains_key(&url_missing), "L'URL absente ne doit pas figurer dans le résultat");
|
||||
assert_eq!(result.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_cached_previews_empty_input() {
|
||||
let conn = test_db();
|
||||
let result = get_cached_previews(&conn, &[]);
|
||||
assert!(result.is_empty(), "Une liste vide d'URLs doit retourner un HashMap vide");
|
||||
}
|
||||
|
||||
// ── get_cached_urls ───────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn test_get_cached_urls_returns_only_cached() {
|
||||
let conn = test_db();
|
||||
let cached: Vec<String> = (1..=3)
|
||||
.map(|i| format!("https://example.com/quete/{}", i))
|
||||
.collect();
|
||||
let extra: Vec<String> = (4..=5)
|
||||
.map(|i| format!("https://example.com/quete/{}", i))
|
||||
.collect();
|
||||
|
||||
for url in &cached {
|
||||
upsert_preview(&conn, url, &[make_indicator("Monstre", "1")]).unwrap();
|
||||
}
|
||||
|
||||
let all_urls: Vec<String> = cached.iter().chain(extra.iter()).cloned().collect();
|
||||
let result = get_cached_urls(&conn, &all_urls);
|
||||
|
||||
assert_eq!(result.len(), 3, "Seules les 3 URLs en cache doivent être retournées");
|
||||
for url in &cached {
|
||||
assert!(result.contains(url), "URL '{}' attendue dans le HashSet", url);
|
||||
}
|
||||
for url in &extra {
|
||||
assert!(!result.contains(url), "URL '{}' ne doit pas être dans le HashSet", url);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_cached_urls_empty_input() {
|
||||
let conn = test_db();
|
||||
let result = get_cached_urls(&conn, &[]);
|
||||
assert!(result.is_empty(), "Une liste vide doit retourner un HashSet vide");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user