test(peer-cli): cover full docker scenario matrix
Merge the S18-S36 scenario ideas into the official peer-cli scenario matrix and add a Docker-backed runner that now exercises S1-S36 with concrete file proofs. The runner creates temporary fixtures under .lanspread-peer-cli, drives JSONL peer containers, checks transferred roots with diff and SHA-256 manifests, and covers startup, discovery, transfer, failure, mutation, concurrency, mesh, lifecycle, and catalog edge cases. The scenarios exposed a few harness/runtime boundary gaps that would otherwise make the contract ambiguous. The peer CLI now rejects self-connects, rejects commands for game IDs outside the receiver catalog, filters unknown remote games from its command/event surface, and reports duplicate active same-game commands as operation-in-progress errors. The peer core also refuses non-catalog download commands before transfer, and PeerGameDB has a unit check that address changes preserve identity and library state. S12 and S28 remain unit-level invariants because the CLI cannot stably race raw serve-gate requests or rebind a live listener without restart. The runner treats those scenarios as covered by just test and checks the expected unit test names appear in the output. Test Plan: - just fmt - python3 -m py_compile crates/lanspread-peer-cli/scripts/run_extended_scenarios.py - RUSTC_WRAPPER= just test - RUSTC_WRAPPER= just clippy - RUSTC_WRAPPER= just peer-cli-build - just peer-cli-image - python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py - git diff --check Refs: PEER_CLI_SCENARIOS.md S1-S36
This commit is contained in:
@@ -198,6 +198,14 @@ pub async fn handle_download_game_files_command(
|
||||
install_after_download: bool,
|
||||
) {
|
||||
log::info!("Got PeerCommand::DownloadGameFiles");
|
||||
if !catalog_contains(ctx, &id).await {
|
||||
log::warn!("Ignoring download command for non-catalog game {id}");
|
||||
if let Err(send_err) = tx_notify_ui.send(PeerEvent::DownloadGameFilesFailed { id }) {
|
||||
log::error!("Failed to send DownloadGameFilesFailed event: {send_err}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let games_folder = { ctx.game_dir.read().await.clone() };
|
||||
|
||||
// Use majority validation to get trusted file descriptions and peer whitelist
|
||||
|
||||
@@ -948,6 +948,36 @@ mod tests {
|
||||
assert_eq!(db.peer_id_for_transport_addr(&source), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn address_update_preserves_peer_identity_and_library() {
|
||||
let old_addr = ip_addr([10, 66, 0, 2], 40000);
|
||||
let new_addr = ip_addr([10, 66, 0, 3], 41000);
|
||||
let mut db = PeerGameDB::new();
|
||||
|
||||
let first = db.upsert_peer("peer".to_string(), old_addr);
|
||||
assert!(first.is_new);
|
||||
db.update_peer_games(
|
||||
&"peer".to_string(),
|
||||
vec![summary("game", "20250101", Availability::Ready)],
|
||||
);
|
||||
|
||||
let second = db.upsert_peer("peer".to_string(), new_addr);
|
||||
assert!(!second.is_new);
|
||||
assert!(second.addr_changed);
|
||||
|
||||
let peers = db.peer_snapshots();
|
||||
assert_eq!(peers.len(), 1);
|
||||
assert_eq!(peers[0].peer_id, "peer");
|
||||
assert_eq!(peers[0].addr, new_addr);
|
||||
assert_eq!(peers[0].games.len(), 1);
|
||||
assert_eq!(peers[0].games[0].id, "game");
|
||||
assert_eq!(db.peer_id_for_addr(&old_addr), None);
|
||||
assert_eq!(
|
||||
db.peer_id_for_addr(&new_addr).map(String::as_str),
|
||||
Some("peer")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_uses_latest_version_file_metadata() {
|
||||
let old_addr = addr(12003);
|
||||
|
||||
Reference in New Issue
Block a user