Files
SCOPENET-MC/panel/server/tests/worldmap.rs
T
SCOPEDD 60edc99780
Server integrations / Paper and protocol tests (push) Failing after 20s
Server integrations / fabric / 1.20.1 (push) Failing after 11s
Server integrations / fabric / 1.21.1 (push) Failing after 14s
Server integrations / fabric / 26.1.2 (push) Failing after 17s
Server integrations / fabric / 26.2 (push) Failing after 12s
Server integrations / fabric / 26.3 (push) Failing after 6s
Server integrations / forge / 1.20.1 (push) Failing after 6s
Server integrations / forge / 1.21.1 (push) Failing after 8s
Server integrations / forge / 26.1.2 (push) Failing after 8s
Server integrations / forge / 26.2 (push) Failing after 8s
Server integrations / forge / 26.3 (push) Failing after 7s
CI / Web UIs (type-check + build) (panel/web) (push) Canceled after 0s
CI / Launcher (Windows build check) (push) Canceled after 2m53s
CI / Real installs (Mojang, Fabric, Quilt, Forge, NeoForge) (push) Canceled after 0s
CI / Docker image builds (push) Canceled after 0s
CI / Rust (fmt, clippy, tests) (push) Canceled after 1m22s
CI / Web UIs (type-check + build) (launcher) (push) Canceled after 1m27s
Fix Rust formatting
2026-09-30 22:56:38 -04:00

150 lines
7.3 KiB
Rust

//! The SCOPENET Map: tiles in from a game server, tiles and overlay out to signed-in viewers.
mod common;
use common::*;
const PNG: [u8; 8] = [0x89, b'P', b'N', b'G', 0x0d, 0x0a, 0x1a, 0x0a];
fn record(zoom: u8, x: i32, y: i32, payload: &[u8]) -> Vec<u8> {
let mut out = vec![zoom];
out.extend(x.to_be_bytes());
out.extend(y.to_be_bytes());
let mut png = PNG.to_vec();
png.extend_from_slice(payload);
out.extend((png.len() as u32).to_be_bytes());
out.extend(png);
out
}
struct World {
t: TestApp,
admin: String,
alex: String,
server: String,
sid: i64,
}
async fn world() -> World {
let t = setup().await;
let admin = t.login("admin", "supersecret").await;
t.call("POST", "/api/admin/users", Some(&admin), Some(json!({"username": "Alex", "password": "password123"}))).await;
let alex = t.login("Alex", "password123").await;
let (_, srv) = t.call("POST", "/api/admin/servers", Some(&admin), Some(json!({"name": "SMP", "instance_id": "smp"}))).await;
World { t, admin, alex, server: srv["token"].as_str().unwrap().to_string(), sid: srv["server"]["id"].as_i64().unwrap() }
}
impl World {
async fn upload(&self, dim: &str, body: Vec<u8>) -> (StatusCode, Value) {
self.t.call_raw("POST", &format!("/api/server/v1/map/tiles?dim={dim}"), Some(&self.server), body).await
}
async fn tile(&self, token: &str, dim: &str, z: u8, x: i32, y: i32) -> (StatusCode, Vec<u8>) {
self.t.fetch(&format!("/api/map/{}/{dim}/{z}/{x}/{y}.png?t={token}", self.sid)).await
}
}
#[tokio::test]
async fn tiles_flow_from_the_game_server_to_signed_in_viewers() {
let w = world().await;
let (s, cfg) = w.t.call("GET", "/api/server/v1/map/config", Some(&w.server), None).await;
assert_eq!(s, StatusCode::OK);
assert_eq!((cfg["enabled"].as_bool(), cfg["epoch"].as_u64(), cfg["tile_size"].as_u64()), (Some(true), Some(0), Some(256)));
// Nothing drawn yet.
let (_, info) = w.t.call("GET", &format!("/api/v1/servers/{}/map", w.sid), Some(&w.alex), None).await;
assert_eq!((info["ready"].as_bool(), info["token"].is_null()), (Some(false), true));
assert!(info["message"].as_str().unwrap().contains("Waiting"));
let body = [record(0, -1, 2, b"aa"), record(0, 0, 0, b"bb"), record(1, 0, 0, b"cc")].concat();
let (s, r) = w.upload("minecraft:overworld", body).await;
assert_eq!((s, r["stored"].as_u64()), (StatusCode::OK, Some(3)));
assert_eq!(w.upload("minecraft:the_nether", record(0, 5, 5, b"n")).await.0, StatusCode::OK);
let (_, info) = w.t.call("GET", &format!("/api/v1/servers/{}/map", w.sid), Some(&w.alex), None).await;
assert_eq!(info["ready"], true);
let dims = info["dimensions"].as_array().unwrap();
assert_eq!((dims[0]["slug"].as_str(), dims[1]["label"].as_str()), (Some("overworld"), Some("The Nether")));
assert_eq!(dims[0]["bounds"]["min_y"], 0);
assert_eq!(dims[0]["bounds"]["max_x"], 0);
let token = info["token"].as_str().unwrap().to_string();
assert_eq!(info["tile_base"], format!("/api/map/{}", w.sid));
// The token opens tiles for plain <img> tags.
let (s, bytes) = w.tile(&token, "overworld", 0, -1, 2).await;
assert_eq!(s, StatusCode::OK);
assert!(bytes.ends_with(b"aa") && bytes.starts_with(&PNG));
assert_eq!(w.tile(&token, "overworld", 0, 7, 7).await.0, StatusCode::NOT_FOUND);
assert_eq!(w.tile(&token, "overworld", 9, 0, 0).await.0, StatusCode::NOT_FOUND);
assert_eq!(w.tile("wrong", "overworld", 0, -1, 2).await.0, StatusCode::UNAUTHORIZED);
assert_eq!(w.tile(&token, "..%2Fetc", 0, 0, 0).await.0, StatusCode::NOT_FOUND);
// Tokens belong to one server.
let (_, other) = w.t.call("POST", "/api/admin/servers", Some(&w.admin), Some(json!({"name": "Other", "instance_id": "o"}))).await;
let other_id = other["server"]["id"].as_i64().unwrap();
assert_eq!(w.t.fetch(&format!("/api/map/{other_id}/overworld/0/0/0.png?t={token}")).await.0, StatusCode::UNAUTHORIZED);
// Info needs an account.
assert_eq!(w.t.call("GET", &format!("/api/v1/servers/{}/map", w.sid), None, None).await.0, StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn bad_uploads_are_refused() {
let w = world().await;
assert_eq!(w.upload("minecraft:overworld", b"not tiles at all".to_vec()).await.0, StatusCode::BAD_REQUEST);
assert_eq!(w.upload("../x", record(0, 0, 0, b"a")).await.0, StatusCode::BAD_REQUEST);
assert_eq!(w.upload("minecraft:overworld", record(12, 0, 0, b"a")).await.0, StatusCode::BAD_REQUEST);
let (s, _) = w.t.call_raw("POST", "/api/server/v1/map/tiles?dim=minecraft:overworld", None, record(0, 0, 0, b"a")).await;
assert_eq!(s, StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn players_and_overlay_are_served_to_viewers() {
let w = world().await;
let (s, _) = w.t.call("POST", "/api/server/v1/map/players", Some(&w.server), Some(json!({"players": [
{"uuid": "11111111-2222-3333-4444-555555555555", "name": "Steve", "dimension": "minecraft:overworld", "x": 10.5, "y": 70.0, "z": -3.0, "yaw": 90.0}]}))).await;
assert_eq!(s, StatusCode::OK);
let overlay = json!({"claims": [{"guild_id": "g1", "name": "Iron", "tag": "IRON", "color": 123}], "pins": [{"id": "spawn-0", "kind": "spawn", "label": "Spawn"}]});
assert_eq!(w.t.call("POST", "/api/server/v1/map/overlay", Some(&w.server), Some(overlay)).await.0, StatusCode::OK);
assert_eq!(
w.t.call("POST", "/api/server/v1/map/overlay", Some(&w.server), Some(json!({"claims": "nope"}))).await.0,
StatusCode::BAD_REQUEST
);
let (s, o) = w.t.call("GET", &format!("/api/v1/servers/{}/map/overlay", w.sid), Some(&w.alex), None).await;
assert_eq!(s, StatusCode::OK);
assert_eq!(o["players"][0]["name"], "Steve");
assert_eq!(o["claims"][0]["tag"], "IRON");
assert_eq!(o["pins"][0]["kind"], "spawn");
assert!(o["updated"].is_string());
let (_, info) = w.t.call("GET", &format!("/api/v1/servers/{}/map", w.sid), Some(&w.alex), None).await;
assert_eq!(info["players"], 1);
}
#[tokio::test]
async fn the_map_can_be_switched_off_and_reset() {
let w = world().await;
w.upload("minecraft:overworld", record(0, 0, 0, b"a")).await;
let (s, r) = w.t.call("DELETE", &format!("/api/admin/servers/{}/map", w.sid), Some(&w.admin), None).await;
assert_eq!(s, StatusCode::OK, "{r}");
let (_, cfg) = w.t.call("GET", "/api/server/v1/map/config", Some(&w.server), None).await;
assert_eq!(cfg["epoch"], 1, "game servers see a new epoch and redraw");
let (_, info) = w.t.call("GET", &format!("/api/v1/servers/{}/map", w.sid), Some(&w.alex), None).await;
assert_eq!(info["ready"], false);
assert_eq!(
w.t.call("GET", &format!("/api/admin/servers/{}/map", w.sid), Some(&w.alex), None).await.0,
StatusCode::FORBIDDEN,
"admin only"
);
let (s, _) =
w.t.call(
"PUT",
&format!("/api/admin/servers/{}", w.sid),
Some(&w.admin),
Some(json!({"name": "SMP", "instance_id": "smp", "map_enabled": false})),
)
.await;
assert_eq!(s, StatusCode::OK);
assert_eq!(w.upload("minecraft:overworld", record(0, 0, 0, b"a")).await.0, StatusCode::FORBIDDEN);
let (_, cfg) = w.t.call("GET", "/api/server/v1/map/config", Some(&w.server), None).await;
assert_eq!(cfg["enabled"], false);
assert_eq!(w.t.call("GET", &format!("/api/v1/servers/{}/map/overlay", w.sid), Some(&w.alex), None).await.0, StatusCode::FORBIDDEN);
}