Merge pull request #8844 from TiggerTakkyTTT/test/server-api-backup-goldens

🤖 Agent: Server API backup goldens: test-only, the download case reads the real streamed bytes. The union of this wave's 15 PRs on main acd184c1 passed fmt, clippy -D warnings, 3388 tests and the Python suite on the desktop. management_protocol (--fresh, on that union): every case of this PR matches vanilla; the only 3 differences were #8204's heartbeat cases, fixed there. Other harnesses PASS on the union: flat_generation, flat_place, chunk_generation, datapack_worldgen, command_exec, pick_block_data, powder_snow_collision, snow_freeze, boat_authority, vehicle_container. dedicated_server could not run locally (the vanilla oracle's world generation hit its watchdog under the desktop's memory pressure), so it and the rest of the selection run in CI run-parity.
This commit is contained in:
Ludovic Marie authored and GitHub committed 2026-11-04 15:01:34 +01:00
commit 6475f62f3c
2 files changed
+190

No files matched your search

+125
View File
@@ -1244,6 +1244,131 @@ mod tests {
std::fs::remove_dir_all(&dir).unwrap();
}
fn backup_reply(mut response: Response) -> Value {
let body = if let Some(write) = response.stream.take() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let mut client = std::net::TcpStream::connect(listener.local_addr().unwrap()).unwrap();
let (server, _) = listener.accept().unwrap();
write(&server);
server.shutdown(std::net::Shutdown::Write).unwrap();
let mut bytes = Vec::new();
std::io::Read::read_to_end(&mut client, &mut bytes).unwrap();
Value::String(
bytes
.iter()
.map(|byte| format!("{byte:02x}"))
.collect::<Vec<_>>()
.join(""),
)
} else {
serde_json::from_str(&response.body).unwrap()
};
json!({
"status": response.status,
"content_type": response.content_type,
"headers": response.headers,
"body": body,
})
}
#[test]
fn backup_replies_match_api_v1_goldens() {
let golden: Value =
serde_json::from_str(include_str!("../testdata/api-v1-backups.json")).unwrap();
let dir = std::env::temp_dir().join(format!(
"horizon-server-api-backup-goldens-{}",
std::process::id()
));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(dir.join("backups")).unwrap();
std::fs::create_dir_all(dir.join("world/dimensions/minecraft/the_end")).unwrap();
std::fs::write(dir.join(crate::config::FILE), crate::config::TEMPLATE).unwrap();
std::fs::write(dir.join("world/level.dat"), b"level").unwrap();
std::fs::write(
dir.join("world/dimensions/minecraft/the_end/region.mca"),
b"end",
)
.unwrap();
let name = "2026-11-04_00-00-00-golden.zip";
std::fs::write(dir.join("backups").join(name), b"PK\x05\x06zip").unwrap();
let shared = Shared::with_install(&dir, None);
shared.set_phase(Phase::Stopped);
let call = |method: &str, target: &str, body: Value| {
let (path, query) = target.split_once('?').unwrap_or((target, ""));
route(
&shared,
&Request {
method: method.into(),
path: path.into(),
query: query.into(),
headers: Vec::new(),
body: body.to_string().into_bytes(),
},
)
};
let check = |case: &str, response: Response| {
assert_eq!(backup_reply(response), golden[case], "{case}");
};
check("list", call("GET", "/api/v1/backups", Value::Null));
check(
"download",
call(
"GET",
&format!("/api/v1/backups/download?name={name}"),
Value::Null,
),
);
check(
"restore",
call("POST", "/api/v1/restore", json!({ "name": name })),
);
check(
"restore_dimension",
call(
"POST",
"/api/v1/restore",
json!({ "name": name, "dimension": "the_end" }),
),
);
check(
"bad_dimension",
call(
"POST",
"/api/v1/restore",
json!({ "name": name, "dimension": "../end" }),
),
);
check(
"restore_missing",
call("POST", "/api/v1/restore", json!({ "name": "missing" })),
);
check(
"delete_missing",
call("DELETE", "/api/v1/backups?name=../server.toml", Value::Null),
);
check(
"delete",
call(
"DELETE",
&format!("/api/v1/backups?name={name}"),
Value::Null,
),
);
check("list_empty", call("GET", "/api/v1/backups", Value::Null));
let mut created = backup_reply(call(
"POST",
"/api/v1/backups",
json!({ "name": "created" }),
));
let created_name = created["body"]["name"].as_str().unwrap();
assert!(created_name.ends_with("-created.zip"));
assert!(dir.join("backups").join(created_name).is_file());
created["body"]["name"] = json!("$BACKUP");
assert_eq!(created, golden["create"]);
std::fs::remove_dir_all(&dir).unwrap();
}
#[test]
fn stopped_supervisor_can_edit_lists_and_start_again() {
let dir =
+65
View File
@@ -0,0 +1,65 @@
{
"list": {
"status": 200,
"content_type": "application/json",
"headers": [],
"body": {"backups": [{"name": "2026-11-04_00-00-00-golden.zip", "bytes": 7}]}
},
"download": {
"status": 200,
"content_type": "application/octet-stream",
"headers": [
["Content-Length", "7"],
["Content-Disposition", "attachment; filename=\"2026-11-04_00-00-00-golden.zip\""]
],
"body": "504b05067a6970"
},
"restore": {
"status": 200,
"content_type": "application/json",
"headers": [],
"body": {"name": "2026-11-04_00-00-00-golden.zip", "dimension": null}
},
"restore_dimension": {
"status": 200,
"content_type": "application/json",
"headers": [],
"body": {"name": "2026-11-04_00-00-00-golden.zip", "dimension": "minecraft:the_end"}
},
"bad_dimension": {
"status": 400,
"content_type": "application/json",
"headers": [],
"body": {"error": "\"../end\" is not a dimension id (like minecraft:the_nether)"}
},
"restore_missing": {
"status": 404,
"content_type": "application/json",
"headers": [],
"body": {"error": "no backup \"missing\" in backups/ (`horizon-server backups` lists them)"}
},
"delete_missing": {
"status": 404,
"content_type": "application/json",
"headers": [],
"body": {"error": "no such backup (`GET /api/v1/backups` lists them)"}
},
"delete": {
"status": 200,
"content_type": "application/json",
"headers": [],
"body": {"name": "2026-11-04_00-00-00-golden.zip"}
},
"list_empty": {
"status": 200,
"content_type": "application/json",
"headers": [],
"body": {"backups": []}
},
"create": {
"status": 200,
"content_type": "application/json",
"headers": [],
"body": {"name": "$BACKUP"}
}
}