#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use crate::bridge_persistence::{AbsenceEntry, persist_absence};
use crate::persistence::Persistence;
use crate::retrieval::hybrid::RetrievalAbstention;
use crate::semantic_bridge::{CanonicalConcept, ConceptGraph};
use chrono::Utc;
use tempfile::NamedTempFile;
#[tokio::test]
async fn test_save_and_load_canonical_concept() {
let temp = NamedTempFile::new().unwrap();
let path = temp.path().to_str().unwrap();
let persistence = Persistence::new_local(path).await.unwrap();
let concept = CanonicalConcept::new("test-concept")
.with_label("label1")
.with_label("label2")
.with_related("related-concept");
persistence
.save_canonical_concept("_default", &concept)
.await
.unwrap();
let loaded = persistence
.load_canonical_concept("_default", "test-concept")
.await
.unwrap();
assert!(loaded.is_some());
let loaded = loaded.unwrap();
assert_eq!(loaded.id, "test-concept");
assert_eq!(loaded.labels, vec!["label1", "label2"]);
assert_eq!(loaded.related, vec!["related-concept"]);
}
#[tokio::test]
async fn test_delete_canonical_concept() {
let temp = NamedTempFile::new().unwrap();
let path = temp.path().to_str().unwrap();
let persistence = Persistence::new_local(path).await.unwrap();
let concept = CanonicalConcept::new("to-delete");
persistence
.save_canonical_concept("_default", &concept)
.await
.unwrap();
persistence
.delete_canonical_concept("_default", "to-delete")
.await
.unwrap();
let loaded = persistence
.load_canonical_concept("_default", "to-delete")
.await
.unwrap();
assert!(loaded.is_none());
}
#[tokio::test]
async fn test_save_and_load_concept_graph() {
let temp = NamedTempFile::new().unwrap();
let path = temp.path().to_str().unwrap();
let persistence = Persistence::new_local(path).await.unwrap();
let mut graph = ConceptGraph::new();
graph.add_concept(
CanonicalConcept::new("c1")
.with_label("label1")
.with_related("c2"),
);
graph.add_concept(CanonicalConcept::new("c2").with_label("label2"));
persistence
.save_concept_graph("_default", &graph)
.await
.unwrap();
let loaded = persistence.load_concept_graph("_default").await.unwrap();
assert_eq!(loaded.concept_count(), 2);
assert_eq!(loaded.label_count(), 2);
}
#[tokio::test]
async fn test_absence_id_generation() {
let q1 = " Test Query ";
let q2 = "test query";
let id1 = AbsenceEntry::id_for(q1);
let id2 = AbsenceEntry::id_for(q2);
assert_eq!(id1, id2);
assert!(id1.starts_with("absence:"));
}
#[test]
fn test_absence_normalize_trims_and_lowercases() {
assert_eq!(AbsenceEntry::normalize(" Hello World "), "hello world");
assert_eq!(AbsenceEntry::normalize("RUST"), "rust");
assert_eq!(AbsenceEntry::normalize("already"), "already");
}
#[test]
fn test_absence_fnv1a_hash_known_values() {
assert_eq!(
AbsenceEntry::fnv1a_hash(b""),
0xcbf2_9ce4_8422_2325,
"empty input must equal FNV-1a offset basis"
);
assert_eq!(
AbsenceEntry::fnv1a_hash(b"a"),
0xaf63_dc4c_8601_ec8c,
"fnv1a(b\"a\") must equal known FNV-1a-64 value"
);
assert_ne!(
AbsenceEntry::fnv1a_hash(b"hello"),
AbsenceEntry::fnv1a_hash(b"world"),
);
}
#[test]
fn test_merge_with_sets_score_from_none() {
let ts = Utc::now();
let mut entry = AbsenceEntry {
id: "absence:0000".to_string(),
query: "q".to_string(),
normalized_query: "q".to_string(),
attempt_count: 1,
last_threshold: 0.5,
best_score_ever: None,
first_seen: ts,
last_seen: ts,
};
let abstention = RetrievalAbstention {
query: "q".to_string(),
min_score_threshold: 0.5,
best_score_seen: Some(0.7),
attempted_modes: vec![],
timestamp: ts,
};
entry.merge_with(&abstention);
assert_eq!(
entry.best_score_ever,
Some(0.7),
"(Some(new), None) arm must set best_score_ever"
);
}
#[test]
fn test_merge_with_lower_score_unchanged() {
let ts = Utc::now();
let mut entry = AbsenceEntry {
id: "absence:0000".to_string(),
query: "q".to_string(),
normalized_query: "q".to_string(),
attempt_count: 1,
last_threshold: 0.5,
best_score_ever: Some(0.5),
first_seen: ts,
last_seen: ts,
};
let abstention = RetrievalAbstention {
query: "q".to_string(),
min_score_threshold: 0.5,
best_score_seen: Some(0.3),
attempted_modes: vec![],
timestamp: ts,
};
entry.merge_with(&abstention);
assert!(
(entry.best_score_ever.unwrap() - 0.5).abs() < f32::EPSILON,
"lower score must not overwrite best_score_ever"
);
}
#[tokio::test]
async fn test_persist_absence_lifecycle() {
let temp = NamedTempFile::new().unwrap();
let path = temp.path().to_str().unwrap();
let persistence = Persistence::new_local(path).await.unwrap();
let abstention = RetrievalAbstention {
query: "unknown".to_string(),
min_score_threshold: 0.5,
best_score_seen: Some(0.1),
attempted_modes: vec!["Auto".to_string()],
timestamp: Utc::now(),
};
let mut abstention = abstention;
abstention.best_score_seen = Some(0.1);
let entry = persist_absence(&abstention, &persistence).await.unwrap();
assert_eq!(entry.attempt_count, 1);
assert!((entry.best_score_ever.unwrap() - 0.1).abs() < f32::EPSILON);
let mut abstention2 = abstention.clone();
abstention2.best_score_seen = Some(0.4);
let entry2 = persist_absence(&abstention2, &persistence).await.unwrap();
assert_eq!(entry2.attempt_count, 2);
assert!((entry2.best_score_ever.unwrap() - 0.4).abs() < f32::EPSILON);
let mut abstention3 = abstention.clone();
abstention3.best_score_seen = Some(0.2);
let entry3 = persist_absence(&abstention3, &persistence).await.unwrap();
assert_eq!(entry3.attempt_count, 3);
assert!((entry2.best_score_ever.unwrap() - 0.4).abs() < f32::EPSILON); }
#[test]
fn fnv1a_hash_is_xor_based_not_or_based() {
let h1 = AbsenceEntry::fnv1a_hash(b"\x00");
let h2 = AbsenceEntry::fnv1a_hash(b"\xff");
let h3 = AbsenceEntry::fnv1a_hash(b"\x0f");
assert_ne!(h1, h2, "hash(0x00) must differ from hash(0xff)");
assert_ne!(h1, h3, "hash(0x00) must differ from hash(0x0f)");
assert_ne!(h2, h3, "hash(0xff) must differ from hash(0x0f)");
}
#[test]
fn fnv1a_hash_known_vector() {
let empty = AbsenceEntry::fnv1a_hash(b"");
assert_eq!(
empty, 0xcbf2_9ce4_8422_2325,
"empty hash must equal FNV offset basis"
);
let a = AbsenceEntry::fnv1a_hash(b"a");
assert_eq!(
a, 0xaf63_dc4c_8601_ec8c,
"fnv1a('a') must match reference vector"
);
}
#[test]
fn fnv1a_hash_byte_order_sensitivity() {
let forward = AbsenceEntry::fnv1a_hash(b"\x01\x02");
let backward = AbsenceEntry::fnv1a_hash(b"\x02\x01");
assert_ne!(forward, backward, "hash must be order-sensitive");
}
fn make_abstention(score: Option<f32>) -> crate::retrieval::hybrid::RetrievalAbstention {
use chrono::Utc;
crate::retrieval::hybrid::RetrievalAbstention {
query: "test query".to_string(),
min_score_threshold: 0.5,
best_score_seen: score,
attempted_modes: vec![],
timestamp: Utc::now(),
}
}
#[test]
fn merge_with_keeps_strictly_higher_score() {
let abstention_initial = make_abstention(Some(0.3));
let mut entry = AbsenceEntry::from_abstention(&abstention_initial);
let higher = make_abstention(Some(0.8));
entry.merge_with(&higher);
assert_eq!(entry.best_score_ever, Some(0.8));
}
#[test]
fn merge_with_does_not_overwrite_equal_score() {
let abstention_initial = make_abstention(Some(0.5));
let mut entry = AbsenceEntry::from_abstention(&abstention_initial);
assert_eq!(entry.best_score_ever, Some(0.5));
let equal_score = make_abstention(Some(0.5));
entry.merge_with(&equal_score);
assert_eq!(
entry.best_score_ever,
Some(0.5),
"equal score must not change best_score_ever"
);
let lower = make_abstention(Some(0.2));
entry.merge_with(&lower);
assert_eq!(
entry.best_score_ever,
Some(0.5),
"lower score must not displace existing best"
);
}
#[test]
fn merge_with_promotes_none_to_some() {
let initial = make_abstention(None);
let mut entry = AbsenceEntry::from_abstention(&initial);
assert_eq!(entry.best_score_ever, None);
let with_score = make_abstention(Some(0.7));
entry.merge_with(&with_score);
assert_eq!(entry.best_score_ever, Some(0.7));
}