use fatt::db;
use fatt::rules::Severity;
use rusqlite::{params, Connection};
use tempfile::tempdir;
#[test]
fn test_db_initialization() -> anyhow::Result<()> {
let temp_dir = tempdir()?;
let db_path = temp_dir.path().join("test.sqlite");
let conn = db::init_db(db_path.to_str().unwrap())?;
assert!(db_path.exists());
let tables_count: i32 = conn.query_row(
"SELECT count(*) FROM sqlite_master WHERE type='table' AND name='findings'",
[],
|row| row.get(0),
)?;
assert_eq!(tables_count, 1, "findings table should be created");
let has_columns = conn.query_row(
"SELECT COUNT(*) FROM pragma_table_info('findings') WHERE name IN ('id', 'domain', 'rule_name', 'matched_path', 'detected', 'scanned_at')",
[],
|row| row.get::<_, i32>(0)
)?;
assert_eq!(
has_columns, 6,
"findings table should have all required columns"
);
Ok(())
}
#[test]
fn test_record_finding() -> anyhow::Result<()> {
let conn = Connection::open_in_memory()?;
conn.execute(
"CREATE TABLE findings (
id INTEGER PRIMARY KEY,
domain TEXT NOT NULL,
rule_name TEXT NOT NULL,
matched_path TEXT NOT NULL,
detected INTEGER NOT NULL,
scanned_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE(domain, rule_name)
)",
[],
)?;
let domain = "example.com";
let rule_name = "test-rule";
let matched_path = "/admin";
let detected = true;
let finding_id = db::insert_finding(&conn, domain, rule_name, matched_path, detected)?;
assert!(finding_id > 0, "Finding ID should be positive");
let count: i64 = conn.query_row(
"SELECT COUNT(*) FROM findings WHERE domain = ? AND rule_name = ? AND matched_path = ? AND detected = ?",
params![domain, rule_name, matched_path, 1],
|row| row.get(0),
)?;
assert_eq!(count, 1, "One record should be found");
let new_detected = false;
let update_id = db::insert_finding(&conn, domain, rule_name, matched_path, new_detected)?;
assert_eq!(finding_id, update_id, "Update should return same record ID");
let updated_detected: i64 = conn.query_row(
"SELECT detected FROM findings WHERE domain = ? AND rule_name = ?",
params![domain, rule_name],
|row| row.get(0),
)?;
assert_eq!(updated_detected, 0, "detected should be updated to 0");
Ok(())
}
#[test]
fn test_get_findings_count() -> anyhow::Result<()> {
let conn = Connection::open_in_memory()?;
conn.execute(
"CREATE TABLE findings (
id INTEGER PRIMARY KEY,
domain TEXT NOT NULL,
rule_name TEXT NOT NULL,
matched_path TEXT NOT NULL,
detected INTEGER NOT NULL,
scanned_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE(domain, rule_name)
)",
[],
)?;
for i in 1..=5 {
let domain = format!("example{}.com", i);
let rule_name = format!("rule-{}", i % 3);
db::insert_finding(&conn, &domain, &rule_name, "/admin", true)?;
}
let counts = db::get_findings_count(&conn, None)?;
assert_eq!(counts, 5, "Should have 5 total findings");
let critical_counts = db::get_findings_count(&conn, Some(Severity::Critical))?;
assert_eq!(
critical_counts, 5,
"Should return all findings (severity ignored)"
);
Ok(())
}
#[test]
fn test_get_unique_domains_count() -> anyhow::Result<()> {
let conn = Connection::open_in_memory()?;
conn.execute(
"CREATE TABLE findings (
id INTEGER PRIMARY KEY,
domain TEXT NOT NULL,
rule_name TEXT NOT NULL,
matched_path TEXT NOT NULL,
detected INTEGER NOT NULL,
scanned_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE(domain, rule_name)
)",
[],
)?;
let domains = vec!["example.com", "test.com", "example.com", "demo.com"];
for (i, domain) in domains.iter().enumerate() {
let rule_name = format!("rule-{}", i);
db::insert_finding(&conn, domain, &rule_name, "/admin", true)?;
}
let unique_count = db::get_unique_domains_count(&conn)?;
assert_eq!(unique_count, 3, "Should have 3 unique domains");
Ok(())
}