use super::execute::{root_with_source, run, NEW_DIM};
use super::*;
fn validated(root: &std::path::Path) -> super::super::ExecuteOutcome {
let executed = run(root).expect("execute");
super::super::validate_destination(
&root.join("store"),
&executed.destination,
&TargetContract::automatic("hash", NEW_DIM),
1024,
)
.expect("validate");
executed
}
fn journal(workspace: &std::path::Path) -> MigrationState {
MigrationState::read(workspace)
.expect("read journal")
.expect("journal exists")
}
pub(super) fn journal_phase(workspace: &std::path::Path, phase: Phase) {
let lock = MigrationLock::acquire(workspace, "switch-test").expect("lock");
let mut state = journal(workspace);
state.phase = phase;
state.write(workspace, &lock).expect("journal write");
lock.release().expect("release");
}
#[test]
fn a_full_switch_activates_the_destination_and_frees_the_archive() {
let root = root_with_source();
let executed = validated(root.path());
let store = root.path().join("store");
let archive = root.path().join("store.archive");
let outcome = super::super::switch_over(&store, &executed.destination).expect("switch");
assert_eq!(
outcome.activated,
store.canonicalize().expect("the activated store exists"),
"the new store must sit at the source's name"
);
assert!(
!archive.exists(),
"committing must free the archive — the recovery table says advancing \
frees it, and this is the advance"
);
assert!(
!executed.destination.exists(),
"the destination directory moved; a copy left behind would be a third \
authority"
);
assert_eq!(journal(&executed.workspace).phase, Phase::Committed);
let stamped = crate::embedding_provenance::read(&store)
.expect("read provenance")
.expect("the activated store carries the stamp validation wrote");
assert_eq!(
(stamped.model.as_str(), stamped.dimension),
("hash", NEW_DIM)
);
let db = velesdb_core::Database::open(&store).expect("the activated store opens");
let facts = super::super::enumerate_by_cursor(&db, "_semantic_memory", 1024).expect("walk");
assert_eq!(
facts.len() as u64,
super::execute::SEEDED,
"the facts are the rebuilt ones"
);
}
#[test]
fn a_live_switch_retains_the_archive_until_the_new_generation_is_installed() {
let root = root_with_source();
let executed = validated(root.path());
let store = root.path().join("store");
let archive = root.path().join("store.archive");
super::super::stage_live_switch(&store, &executed.destination).expect("stage");
super::super::finalize_staged_live_switch(&store, &executed.destination).expect("finalize");
assert!(
archive.exists(),
"the old generation still has a recovery copy"
);
assert_eq!(
journal(&executed.workspace).phase,
Phase::DestinationActivated
);
super::super::commit_retained_switch(&store, &executed.destination)
.expect("commit after installing the new generation");
assert!(!archive.exists(), "commit may now release the archive");
assert_eq!(journal(&executed.workspace).phase, Phase::Committed);
}
#[test]
fn a_staged_live_switch_can_roll_back_before_activation() {
let root = root_with_source();
let executed = validated(root.path());
let store = root.path().join("store");
let archive = root.path().join("store.archive");
super::super::stage_live_switch(&store, &executed.destination).expect("stage");
assert!(store.exists() && archive.exists());
assert!(!executed.destination.exists());
assert_eq!(
journal(&executed.workspace).phase,
Phase::DestinationValidated,
"physical staging must remain pre-activation in durable state"
);
super::super::rollback_staged_live_switch(&store, &executed.destination).expect("rollback");
assert!(store.exists());
assert!(!archive.exists());
assert!(executed.destination.exists());
assert_eq!(
journal(&executed.workspace).phase,
Phase::DestinationValidated
);
}
#[test]
fn a_live_crash_after_the_first_rename_rolls_back_to_source_authority() {
let root = root_with_source();
let executed = validated(root.path());
let store = root.path().join("store");
let archive = root.path().join("store.archive");
std::fs::rename(&store, &archive).expect("first rename");
super::super::rollback_staged_live_switch(&store, &executed.destination)
.expect("rollback first rename");
assert!(store.exists());
assert!(executed.destination.exists());
assert!(!archive.exists());
assert_eq!(
journal(&executed.workspace).phase,
Phase::DestinationValidated
);
}
#[test]
fn a_staged_live_switch_is_journalled_only_after_activation() {
let root = root_with_source();
let executed = validated(root.path());
let store = root.path().join("store");
super::super::stage_live_switch(&store, &executed.destination).expect("stage");
super::super::finalize_staged_live_switch(&store, &executed.destination).expect("finalize");
assert_eq!(
journal(&executed.workspace).phase,
Phase::DestinationActivated
);
assert!(root.path().join("store.archive").exists());
}
#[test]
fn a_switch_before_validation_is_refused() {
let root = root_with_source();
let executed = run(root.path()).expect("execute");
let refusal = super::super::switch_over(&root.path().join("store"), &executed.destination)
.expect_err("switching an unvalidated destination");
assert!(
refusal.to_string().contains("validate"),
"the refusal must point at the missing validation: {refusal}"
);
assert!(root.path().join("store").exists(), "nothing may have moved");
assert!(executed.destination.exists(), "nothing may have moved");
}
#[test]
fn an_occupied_archive_slot_is_refused_with_everything_intact() {
let root = root_with_source();
let executed = validated(root.path());
let archive = root.path().join("store.archive");
std::fs::create_dir(&archive).expect("occupy the archive slot");
std::fs::write(archive.join("evidence.txt"), b"someone else's data").expect("mark it");
let refusal = super::super::switch_over(&root.path().join("store"), &executed.destination)
.expect_err("renaming the source over an existing archive would eat it");
assert!(
refusal.to_string().contains("store.archive"),
"the refusal must name the occupied slot: {refusal}"
);
assert!(
root.path().join("store").exists(),
"the source must not have moved"
);
assert!(
executed.destination.exists(),
"the destination must not have moved"
);
assert!(
std::fs::read(archive.join("evidence.txt")).is_ok(),
"whatever occupies the slot must be untouched"
);
assert_eq!(
journal(&executed.workspace).phase,
Phase::DestinationValidated,
"a refused switch must leave the journal where it was"
);
}
#[test]
fn a_crash_after_the_first_rename_is_continued_not_undone() {
let root = root_with_source();
let executed = validated(root.path());
let store = root.path().join("store");
let archive = root.path().join("store.archive");
std::fs::rename(&store, &archive).expect("simulate the crashed rename");
assert_eq!(
journal(&executed.workspace).phase,
Phase::DestinationValidated
);
let outcome = super::super::switch_over(&store, &executed.destination)
.expect("the re-run must journal the rename late and continue forward");
assert_eq!(outcome.activated, store.canonicalize().expect("exists"));
assert_eq!(journal(&executed.workspace).phase, Phase::Committed);
assert!(!archive.exists(), "the completed switch frees the archive");
}
#[test]
fn a_crash_after_the_second_rename_is_recognised_by_the_stamp() {
let root = root_with_source();
let executed = validated(root.path());
let store = root.path().join("store");
let archive = root.path().join("store.archive");
std::fs::rename(&store, &archive).expect("first rename");
journal_phase(&executed.workspace, Phase::SourceArchived);
std::fs::rename(&executed.destination, &store).expect("second rename");
let outcome = super::super::switch_over(&store, &executed.destination)
.expect("the stamp identifies the activated store; the run continues");
assert_eq!(outcome.activated, store.canonicalize().expect("exists"));
assert_eq!(journal(&executed.workspace).phase, Phase::Committed);
assert!(!archive.exists());
}
#[test]
fn an_unstamped_occupant_of_the_source_slot_is_refused() {
let root = root_with_source();
let executed = validated(root.path());
let store = root.path().join("store");
let archive = root.path().join("store.archive");
std::fs::rename(&store, &archive).expect("first rename");
journal_phase(&executed.workspace, Phase::SourceArchived);
let elsewhere = root.path().join("elsewhere");
std::fs::rename(&executed.destination, &elsewhere).expect("destination aside");
std::fs::create_dir(&store).expect("an impostor at the source's name");
let refusal = super::super::switch_over(&store, &executed.destination)
.expect_err("an unstamped occupant cannot be assumed to be the destination");
let message = refusal.to_string();
assert!(
message.contains("stamp") || message.contains("provenance"),
"the refusal must say WHY the occupant is not trusted: {message}"
);
assert!(
archive.exists(),
"the archive — the only authority — must be untouched"
);
assert!(
elsewhere.exists(),
"the set-aside destination must be untouched"
);
}
#[test]
fn a_source_that_moved_on_cannot_be_archived_by_a_stale_journal() {
let root = root_with_source();
let executed = validated(root.path());
let store = root.path().join("store");
{
let native = crate::storage::NativeStore::open(&store, DIM).expect("open");
native
.store(77, "written after validation", &EMBEDDING)
.expect("late write");
}
let refusal = super::super::switch_over(&store, &executed.destination)
.expect_err("a stale journal must not move a store that moved on");
let message = refusal.to_string();
assert!(
message.contains("fingerprint") || message.contains("changed"),
"the refusal must say the source no longer matches the journal: {message}"
);
assert!(store.exists(), "nothing may have moved");
assert!(executed.destination.exists(), "nothing may have moved");
}
#[test]
fn commit_refuses_to_free_an_archive_that_received_writes() {
let root = root_with_source();
let executed = validated(root.path());
let store = root.path().join("store");
let archive = root.path().join("store.archive");
std::fs::rename(&store, &archive).expect("first rename");
journal_phase(&executed.workspace, Phase::SourceArchived);
std::fs::rename(&executed.destination, &store).expect("second rename");
journal_phase(&executed.workspace, Phase::DestinationActivated);
std::fs::write(archive.join("daemon-was-here.wal"), b"unsynced writes").expect("foreign write");
let refusal = super::super::switch_over(&store, &executed.destination)
.expect_err("an archive that changed since the journal must not be destroyed");
let message = refusal.to_string();
assert!(
message.contains("archive"),
"the refusal must name the archive: {message}"
);
assert!(
std::fs::read(archive.join("daemon-was-here.wal")).is_ok(),
"the foreign write — possibly the only copy of someone's data — must survive"
);
assert_eq!(
journal(&executed.workspace).phase,
Phase::DestinationActivated,
"a refused commit must leave the journal where it was"
);
}
#[test]
fn a_manual_restore_is_re_archived_and_the_switch_completes() {
let root = root_with_source();
let executed = validated(root.path());
let store = root.path().join("store");
let archive = root.path().join("store.archive");
std::fs::rename(&store, &archive).expect("first rename");
journal_phase(&executed.workspace, Phase::SourceArchived);
std::fs::rename(&archive, &store).expect("the operator's manual restore");
let outcome = super::super::switch_over(&store, &executed.destination)
.expect("a restored source under a SourceArchived journal must re-archive and continue");
assert_eq!(outcome.activated, store.canonicalize().expect("exists"));
assert_eq!(journal(&executed.workspace).phase, Phase::Committed);
}
#[test]
fn a_committed_migration_refuses_to_switch_again() {
let root = root_with_source();
let executed = validated(root.path());
let store = root.path().join("store");
super::super::switch_over(&store, &executed.destination).expect("switch");
let refusal = super::super::switch_over(&store, &executed.destination)
.expect_err("a committed migration has nothing left to switch");
assert!(
refusal.to_string().contains("complete"),
"the refusal must say the migration is done: {refusal}"
);
}