//! Live position and world-mirror ingest from game servers. mod common; use common::*; fn record(x: i32, z: i32, ts: u32, data: &[u8]) -> Vec { let mut b = Vec::new(); b.extend_from_slice(&x.to_be_bytes()); b.extend_from_slice(&z.to_be_bytes()); b.extend_from_slice(&ts.to_be_bytes()); b.push(2); b.extend_from_slice(&(data.len() as u32).to_be_bytes()); b.extend_from_slice(data); b } async fn post_bytes(t: &TestApp, uri: &str, token: &str, body: Vec) -> (StatusCode, Value) { let req = Request::builder().method("POST").uri(uri).header("authorization", format!("Bearer {token}")).body(Body::from(body)).unwrap(); t.send(req).await } #[tokio::test] async fn live_map_ingest_and_player_positions() { let t = setup().await; let admin = t.login("admin", "supersecret").await; let (_, v) = t.call("POST", "/api/admin/servers", Some(&admin), Some(json!({"name": "Survival"}))).await; let id = v["server"]["id"].as_i64().unwrap(); let token = v["token"].as_str().unwrap().to_string(); // Off by default: nothing is accepted. let (s, _) = post_bytes(&t, "/api/server/v1/livemap/chunks?dim=overworld", &token, record(0, 0, 5, b"abc")).await; assert_eq!(s, StatusCode::FORBIDDEN); let (_, hello) = t.call("POST", "/api/server/v1/hello", Some(&token), Some(json!({}))).await; assert_eq!(hello["live_map"]["enabled"], false); let (s, v) = t.call("PUT", &format!("/api/admin/servers/{id}"), Some(&admin), Some(json!({"name": "Survival", "live_map_enabled": true}))).await; assert_eq!(s, StatusCode::OK, "{v}"); assert_eq!(v["live_map_enabled"], true); let (_, hello) = t.call("POST", "/api/server/v1/hello", Some(&token), Some(json!({}))).await; assert_eq!(hello["live_map"]["enabled"], true); // Chunks land in the region mirror and show up in the manifest. let mut batch = record(1, 2, 100, b"hello-chunk"); batch.extend(record(-1, 40, 101, b"other-region")); let (s, v) = post_bytes(&t, "/api/server/v1/livemap/chunks?dim=overworld", &token, batch).await; assert_eq!(s, StatusCode::OK, "{v}"); assert_eq!(v["stored"], 2); let (s, _) = post_bytes(&t, "/api/server/v1/livemap/chunks?dim=../x", &token, record(0, 0, 1, b"x")).await; assert_eq!(s, StatusCode::BAD_REQUEST); let (s, _) = post_bytes(&t, "/api/server/v1/livemap/chunks", &token, vec![1, 2, 3]).await; assert_eq!(s, StatusCode::BAD_REQUEST); let (_, m) = t.call("GET", "/api/server/v1/livemap/manifest?dim=overworld", Some(&token), None).await; assert_eq!(m["regions"].as_array().unwrap().len(), 2); // level.dat must be a gzip file. let (s, _) = post_bytes(&t, "/api/server/v1/livemap/level", &token, b"not gzip at all, sorry!".to_vec()).await; assert_eq!(s, StatusCode::BAD_REQUEST); let mut gz = vec![0x1f, 0x8b]; gz.extend([0u8; 30]); let (s, _) = post_bytes(&t, "/api/server/v1/livemap/level", &token, gz).await; assert_eq!(s, StatusCode::OK); // Players, sanitised. let (s, _) = t .call( "POST", "/api/server/v1/livemap/players", Some(&token), Some(json!({"players": [ {"uuid": "b50ad385-829d-3141-a216-7e7d7539ba7f", "name": "Notch", "dimension": "minecraft:the_nether", "x": 10.5, "y": 70.0, "z": -4.0, "yaw": 90.0}, {"uuid": "bad", "name": "Nowhere", "x": 1e99, "y": 0, "z": 0} ]})), ) .await; assert_eq!(s, StatusCode::OK); // Viewers need a session. let (s, _) = t.call("GET", &format!("/api/v1/servers/{id}/livemap"), None, None).await; assert_eq!(s, StatusCode::UNAUTHORIZED); let (s, info) = t.call("GET", &format!("/api/v1/servers/{id}/livemap"), Some(&admin), None).await; assert_eq!(s, StatusCode::OK); assert_eq!(info["enabled"], true); assert_eq!(info["players"], 1); assert!(info["dimensions"][0]["available"].as_bool().unwrap()); assert_eq!(info["ready"], true); let (s, st) = t.call("GET", &format!("/api/admin/servers/{id}/livemap"), Some(&admin), None).await; assert_eq!(s, StatusCode::OK); assert_eq!(st["stats"]["chunks_received"], 2); // Public list advertises the map; reset wipes it. let (_, list) = t.call("GET", "/api/v1/servers/public", None, None).await; assert_eq!(list["servers"][0]["live_map"], true); let (s, _) = t.call("DELETE", &format!("/api/admin/servers/{id}/livemap"), Some(&admin), None).await; assert_eq!(s, StatusCode::OK); let (_, m) = t.call("GET", "/api/server/v1/livemap/manifest?dim=overworld", Some(&token), None).await; assert!(m["regions"].as_array().unwrap().is_empty()); }