use super::supersede_tests::all_statuses;
use super::tests::crud::add;
use crate::memory::NewMemory;
use crate::memory::lifecycle::{MemoryImportance, MemoryStatus, MemoryType};
use crate::memory::store::MemoryStore;
#[test]
fn superseding_a_superseded_row_is_refused_and_writes_nothing() {
let (_dir, mut store) = MemoryStore::test_store();
let project = "retired-superseded";
let old_id = add(&mut store, project, "original fact", None);
let first_replacement = add(&mut store, project, "first replacement", None);
seed_retired(
&mut store,
&old_id,
project,
MemoryStatus::Superseded,
Some(&first_replacement),
);
let msg = refuse_retired_supersede(&mut store, project, &old_id, "second replacement");
assert!(
msg.contains("Invalid input") && msg.contains("'superseded'"),
"the refusal must be InvalidInput and name the current status: {msg}"
);
assert!(
msg.contains(&first_replacement),
"the refusal must name the existing superseded_by: {msg}"
);
let row = retired_row(&store, project, &old_id);
assert_eq!(row.status, "superseded", "the refusal rewrote the status");
assert_eq!(
row.superseded_by.as_deref(),
Some(first_replacement.as_str()),
"the refusal must not fork the supersede chain"
);
assert_eq!(
all_statuses(&store, project).len(),
2,
"a refused supersede must insert no replacement row"
);
}
#[test]
fn superseding_a_deprecated_row_is_refused_and_writes_nothing() {
let (_dir, mut store) = MemoryStore::test_store();
let project = "retired-deprecated";
let old_id = add(&mut store, project, "pruned fact", None);
seed_retired(&mut store, &old_id, project, MemoryStatus::Deprecated, None);
let msg = refuse_retired_supersede(&mut store, project, &old_id, "second replacement");
assert!(
msg.contains("Invalid input") && msg.contains("'deprecated'"),
"the refusal must be InvalidInput and name the current status: {msg}"
);
let row = retired_row(&store, project, &old_id);
assert_eq!(row.status, "deprecated", "the refusal rewrote the status");
assert_eq!(row.superseded_by, None, "the refusal wrote a link");
assert_eq!(
all_statuses(&store, project).len(),
1,
"a refused supersede must insert no replacement row"
);
}
fn seed_retired_spelled(
store: &mut MemoryStore,
id: &str,
project: &str,
status: &str,
superseded_by: Option<&str>,
) {
store
.db
.update(
id,
project,
crate::sqlite::UpdateOptions {
content: None,
embedding: None,
metadata: None,
memory_type: None,
status: Some(status),
importance: None,
},
)
.expect("seed the retired status");
if let Some(by) = superseded_by {
store
.db
.conn()
.execute(
"UPDATE memories SET superseded_by = ?1 WHERE id = ?2",
rusqlite::params![by, id],
)
.expect("seed the superseded_by link");
}
}
fn seed_retired(
store: &mut MemoryStore,
id: &str,
project: &str,
status: MemoryStatus,
superseded_by: Option<&str>,
) {
seed_retired_spelled(store, id, project, status.as_str(), superseded_by);
}
#[test]
fn superseding_a_row_whose_retired_status_is_spelled_oddly_is_refused() {
let (_dir, mut store) = MemoryStore::test_store();
let project = "retired-spelling";
for (spelling, canonical) in [
("Superseded", "superseded"),
("DEPRECATED", "deprecated"),
(" Superseded ", "superseded"),
] {
let old_id = add(&mut store, project, &format!("fact {spelling}"), None);
seed_retired_spelled(&mut store, &old_id, project, spelling, None);
let msg =
refuse_retired_supersede(&mut store, project, &old_id, &format!("{spelling} twin"));
assert!(
msg.contains("Invalid input") && msg.contains(&format!("'{canonical}'")),
"a tolerant spelling must be refused by its canonical status name: {msg}"
);
assert!(
!msg.contains(spelling),
"the refusal must not echo the raw column spelling {spelling:?}: {msg}"
);
assert_eq!(
retired_row(&store, project, &old_id).superseded_by,
None,
"the refusal must not fork the supersede chain"
);
assert_eq!(
all_statuses(&store, project)
.iter()
.filter(|row| row.content == format!("{spelling} twin"))
.count(),
0,
"a refused supersede must insert no replacement row"
);
}
}
fn refuse_retired_supersede(
store: &mut MemoryStore,
project: &str,
old_id: &str,
content: &str,
) -> String {
match supersede(store, project, content, old_id, MemoryStatus::Active, None) {
Err(err) => err.to_string(),
Ok(new_id) => panic!("superseding a retired row was accepted: {new_id}"),
}
}
fn supersede(
store: &mut MemoryStore,
project: &str,
content: &str,
old_id: &str,
status: MemoryStatus,
metadata: Option<&str>,
) -> Result<String, crate::errors::Error> {
store.supersede_with(&crate::memory::NewSupersedingMemory {
row: NewMemory {
project_id: project,
content,
metadata,
memory_type: MemoryType::Fact,
status,
},
old_id,
importance: MemoryImportance::Medium,
})
}
#[test]
fn superseding_an_unknown_id_is_not_found_and_writes_nothing() {
let (_dir, mut store) = MemoryStore::test_store();
let project = "not-found-target";
let live = add(&mut store, project, "live fact", None);
let other = add(&mut store, "not-found-other-project", "foreign fact", None);
for (label, missing) in [
("absent id", "no-such-memory-id"),
("other project", &other),
] {
let err = supersede(
&mut store,
project,
"replacement",
missing,
MemoryStatus::Active,
None,
)
.expect_err(&format!("{label} must be refused"));
assert!(
matches!(err, crate::errors::Error::NotFound(_)),
"{label} must be NotFound, got {err}"
);
assert!(
err.to_string().contains("not found"),
"{label} must render as a not-found: {err}"
);
}
let rows = all_statuses(&store, project);
assert_eq!(
rows.len(),
1,
"a not-found supersede must insert no replacement row"
);
assert_eq!(rows[0].id, live, "the only row is the one this test added");
assert_eq!(
rows[0].superseded_by, None,
"a not-found supersede wrote a link"
);
assert_eq!(
retired_row(&store, "not-found-other-project", &other).status,
"active",
"a not-found supersede must not touch the foreign row"
);
}
#[test]
fn supersede_with_rejects_invalid_metadata_and_writes_nothing() {
let (_dir, mut store) = MemoryStore::test_store();
let project = "bad-metadata";
let old_id = add(&mut store, project, "original fact", None);
let err = supersede(
&mut store,
project,
"replacement with bad metadata",
&old_id,
MemoryStatus::Active,
Some("{not json"),
)
.expect_err("invalid metadata JSON must be refused");
assert!(
err.to_string().contains("metadata"),
"the refusal must name metadata: {err}"
);
assert_eq!(
all_statuses(&store, project).len(),
1,
"a refused supersede must insert no replacement row"
);
let row = retired_row(&store, project, &old_id);
assert_eq!(row.status, "active", "the refusal retired the target");
assert_eq!(row.superseded_by, None, "the refusal wrote a link");
}
fn retired_row(store: &MemoryStore, project: &str, id: &str) -> crate::sqlite::Memory {
store
.get(id, project)
.expect("get")
.expect("retired row still readable")
}