use super::*;
use crate::recovery_landing::tests::{ID, Scene};
use crate::recovery_landing::recover_dead_transaction;
use crate::transaction_journal::LandingOperation;
use crate::transaction_journal_encode::encode_ref_updated;
fn parser_failure(name: &str) -> RecoveryError {
return RecoveryError(format!("Transaction record {name} is not a schema-version-2 {name} record."));
}
#[test]
fn every_kind_names_the_file_the_transaction_and_a_way_out() {
let directory: &str = "/repo/.git/cli-git-transactions/0b6c2c1e-6f5b-4d0e-9a55-3f5d8e2f6a10";
let cases: [(RecordKind, &str, &str, &[&str]); 4] = [
(
RecordKind::Preparing,
"preparing.json",
"preparing.json is the first record of a transaction",
&["remove", "discards the transaction", "git reset", "index.lock"],
),
(
RecordKind::Landing,
"landing-2.json",
"A landing record names the post-index",
&["restore it at", "recovers the transaction", "git reset"],
),
(
RecordKind::IndexLock,
"index-lock-1.json",
"This record names the index.lock file the transaction created",
&["Remove", "handles the transaction from its other records", "remove it yourself"],
),
(
RecordKind::RefUpdated,
"ref-updated.json",
"This record is written after the branch moved",
&["Remove", "looks in the branch's reflog", "finishes the transaction"],
),
];
for (kind, file, opening, steps) in cases {
let path: std::path::PathBuf = Path::new(directory).join(file);
let message: String = name_record_failure(parser_failure(file), path.as_path(), kind).0;
assert!(
message.starts_with(
"Cannot recover transaction 0b6c2c1e-6f5b-4d0e-9a55-3f5d8e2f6a10, so every guarded command stops with exit status 2"
),
"{message}"
);
assert!(message.contains(&format!("{} cannot be read (Transaction record {file} is not a schema-version-2", path.display())), "{message}");
assert!(message.contains(&format!("Recovery retained the transaction at {directory}.")), "{message}");
assert!(message.contains(opening), "{opening}: {message}");
for step in steps {
assert!(message.contains(step), "{step}: {message}");
}
assert!(!message.contains("fetch"), "{message}");
assert!(!message.contains("..") && !message.contains(" ."), "{message}");
}
}
fn damaged(name: &str, file: &str, bytes: &[u8]) -> (Scene, String) {
let scene: Scene = Scene::new(name, false);
scene.capture();
scene.land(LandingOperation::Commit, Vec::new());
scene.move_branch();
std::fs::write(scene.directory.join("ref-updated.json"), encode_ref_updated(scene.prepared.as_str())).expect("marker");
std::fs::write(scene.directory.join(file), bytes).expect("damaged record");
let message: String = recover_dead_transaction(&scene.place(), scene.directory.as_path(), ID, i64::from(std::process::id()))
.expect_err("a damaged record fails closed")
.0;
return (scene, message);
}
#[test]
fn recovery_names_the_path_of_each_damaged_record() {
for (file, kind_phrase) in [
("preparing.json", "preparing.json is the first record of a transaction"),
("landing-1.json", "A landing record names the post-index"),
("ref-updated.json", "This record is written after the branch moved"),
] {
for bytes in [&b""[..], &b"{\"schemaVersion\":2"[..]] {
let (scene, message): (Scene, String) = damaged("record-failure-recovery", file, bytes);
assert!(message.contains(&scene.directory.join(file).display().to_string()), "{file}: {message}");
assert!(message.contains(kind_phrase), "{file}: {message}");
assert!(scene.directory.join(file).exists(), "the damaged record is left for the person");
scene.finish();
}
}
let scene: Scene = Scene::new("record-failure-index-lock", false);
scene.capture();
scene.lock_index();
std::fs::write(scene.directory.join("index-lock-1.json"), b"").expect("damaged record");
let message: String = recover_dead_transaction(&scene.place(), scene.directory.as_path(), ID, i64::from(std::process::id()))
.expect_err("a damaged record fails closed")
.0;
assert!(message.contains(&scene.directory.join("index-lock-1.json").display().to_string()), "{message}");
assert!(message.contains("This record names the index.lock file the transaction created"), "{message}");
scene.finish();
}