test(peer): cover update commit rollback
Add a focused transaction test for the branch where update extraction succeeds but promoting `.local.installing` to `local` fails. The fake unpacker creates a non-empty `local/` conflict after extraction, so the commit rename fails without adding production hooks or brittle platform-specific permission tricks. The assertion verifies the old install is restored from `.local.backup`, the conflict and staging directories are removed, the backup is consumed, and the intent is cleared back to None. Test Plan: - git diff --check - just fmt - just clippy - just test Follow-up-Plan: FOLLOW_UP_2.md
This commit is contained in:
@@ -477,6 +477,7 @@ mod tests {
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct FakeUnpacker {
|
struct FakeUnpacker {
|
||||||
fail: bool,
|
fail: bool,
|
||||||
|
create_commit_conflict: bool,
|
||||||
archives: Mutex<Vec<PathBuf>>,
|
archives: Mutex<Vec<PathBuf>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,6 +485,15 @@ mod tests {
|
|||||||
fn failing() -> Self {
|
fn failing() -> Self {
|
||||||
Self {
|
Self {
|
||||||
fail: true,
|
fail: true,
|
||||||
|
create_commit_conflict: false,
|
||||||
|
archives: Mutex::new(Vec::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn commit_conflict() -> Self {
|
||||||
|
Self {
|
||||||
|
fail: false,
|
||||||
|
create_commit_conflict: true,
|
||||||
archives: Mutex::new(Vec::new()),
|
archives: Mutex::new(Vec::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -500,6 +510,14 @@ mod tests {
|
|||||||
eyre::bail!("forced unpack failure");
|
eyre::bail!("forced unpack failure");
|
||||||
}
|
}
|
||||||
tokio::fs::write(dest.join("payload.txt"), b"installed").await?;
|
tokio::fs::write(dest.join("payload.txt"), b"installed").await?;
|
||||||
|
if self.create_commit_conflict {
|
||||||
|
let game_root = dest
|
||||||
|
.parent()
|
||||||
|
.ok_or_else(|| eyre::eyre!("staging dir should have parent"))?;
|
||||||
|
let local_conflict = game_root.join(LOCAL_DIR);
|
||||||
|
tokio::fs::create_dir_all(&local_conflict).await?;
|
||||||
|
tokio::fs::write(local_conflict.join("conflict.txt"), b"conflict").await?;
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -608,6 +626,34 @@ mod tests {
|
|||||||
assert_eq!(intent.state, InstallIntentState::None);
|
assert_eq!(intent.state, InstallIntentState::None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn update_commit_rename_failure_restores_previous_local() {
|
||||||
|
let temp = TempDir::new();
|
||||||
|
let root = temp.game_root();
|
||||||
|
write_file(&root.join("game.eti"), b"archive");
|
||||||
|
write_file(&root.join("version.ini"), b"20250101");
|
||||||
|
write_file(&root.join("local").join("old.txt"), b"old");
|
||||||
|
|
||||||
|
let err = update(&root, "game", Arc::new(FakeUnpacker::commit_conflict()))
|
||||||
|
.await
|
||||||
|
.expect_err("update should fail at commit rename");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
err.to_string().contains("failed to promote update"),
|
||||||
|
"{err:?}"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
std::fs::read(root.join("local").join("old.txt"))
|
||||||
|
.expect("old install should be restored"),
|
||||||
|
b"old"
|
||||||
|
);
|
||||||
|
assert!(!root.join("local").join("conflict.txt").exists());
|
||||||
|
assert!(!root.join(".local.installing").exists());
|
||||||
|
assert!(!root.join(".local.backup").exists());
|
||||||
|
let intent = read_intent(&root, "game").await;
|
||||||
|
assert_eq!(intent.state, InstallIntentState::None);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn update_success_promotes_new_local_and_removes_backup() {
|
async fn update_success_promotes_new_local_and_removes_backup() {
|
||||||
let temp = TempDir::new();
|
let temp = TempDir::new();
|
||||||
|
|||||||
Reference in New Issue
Block a user