Panel: SCOPENET Map storage and endpoints; Vantage proxy removed
This commit is contained in:
15 files changed
+751
-1286
No files matched your search
@@ -39,9 +39,6 @@ pub async fn setup_with(tweak: impl FnOnce(&mut Config, &std::path::Path)) -> Te
|
||||
max_upload_mb: 64,
|
||||
public_url: Some("https://panel.test".into()),
|
||||
trusted_proxies: vec!["127.0.0.1".parse().unwrap()],
|
||||
vantage_bin: None,
|
||||
vantage_assets: None,
|
||||
vantage_args: vec![],
|
||||
};
|
||||
tweak(&mut cfg, dir.path());
|
||||
let pool = db::connect_memory().await.unwrap();
|
||||
@@ -66,6 +63,24 @@ impl TestApp {
|
||||
self.send(req).await
|
||||
}
|
||||
|
||||
/// POST raw bytes (binary uploads).
|
||||
pub async fn call_raw(&self, method: &str, uri: &str, token: Option<&str>, body: Vec<u8>) -> (StatusCode, Value) {
|
||||
let mut req = Request::builder().method(method).uri(uri).header("content-type", "application/octet-stream");
|
||||
if let Some(t) = token {
|
||||
req = req.header("authorization", format!("Bearer {t}"));
|
||||
}
|
||||
self.send(req.body(Body::from(body)).unwrap()).await
|
||||
}
|
||||
|
||||
/// GET and keep the raw bytes (images).
|
||||
pub async fn fetch(&self, uri: &str) -> (StatusCode, Vec<u8>) {
|
||||
let mut req = Request::builder().method("GET").uri(uri).body(Body::empty()).unwrap();
|
||||
req.extensions_mut().insert(axum::extract::ConnectInfo("127.0.0.1:12345".parse::<std::net::SocketAddr>().unwrap()));
|
||||
let resp = self.router.clone().oneshot(req).await.unwrap();
|
||||
let status = resp.status();
|
||||
(status, axum::body::to_bytes(resp.into_body(), usize::MAX).await.unwrap().to_vec())
|
||||
}
|
||||
|
||||
pub async fn send(&self, mut req: Request<Body>) -> (StatusCode, Value) {
|
||||
if req.extensions().get::<axum::extract::ConnectInfo<std::net::SocketAddr>>().is_none() {
|
||||
req.extensions_mut().insert(axum::extract::ConnectInfo("127.0.0.1:12345".parse::<std::net::SocketAddr>().unwrap()));
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
//! Central live map: ingest from game servers, status for admins, viewer proxy.
|
||||
|
||||
mod common;
|
||||
use common::*;
|
||||
|
||||
fn record(x: i32, z: i32, ts: u32, data: &[u8]) -> Vec<u8> {
|
||||
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<u8>) -> (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_status_and_proxy() {
|
||||
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_eq!(info["base"], format!("/api/livemap/{id}"));
|
||||
assert!(info["dimensions"][0]["available"].as_bool().unwrap());
|
||||
// No generator is installed in the test environment.
|
||||
assert_eq!(info["ready"], false);
|
||||
|
||||
let (_, pj) = t.call("GET", &format!("/api/livemap/{id}/the_nether/v1/worlds/default/players.json"), Some(&admin), None).await;
|
||||
assert_eq!(pj["players"][0]["name"], "Notch");
|
||||
assert_eq!(pj["players"][0]["foreign"], false);
|
||||
let (_, pj) = t.call("GET", &format!("/api/livemap/{id}/overworld/v1/worlds/default/players.json"), Some(&admin), None).await;
|
||||
assert_eq!(pj["players"][0]["foreign"], true);
|
||||
|
||||
let (s, e) = t.call("GET", &format!("/api/livemap/{id}/overworld/v1/worlds/default/manifest.json"), Some(&admin), None).await;
|
||||
assert_eq!(s, StatusCode::SERVICE_UNAVAILABLE);
|
||||
assert!(e["error"].as_str().unwrap().contains("generator"));
|
||||
let (s, _) = t.call("GET", &format!("/api/livemap/{id}/overworld/v1/openapi.json"), Some(&admin), None).await;
|
||||
assert_eq!(s, StatusCode::NOT_FOUND);
|
||||
let (s, _) = t.call("GET", &format!("/api/livemap/{id}/overworld/v1/worlds/default/manifest.json"), None, None).await;
|
||||
assert_eq!(s, StatusCode::UNAUTHORIZED);
|
||||
|
||||
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);
|
||||
assert_eq!(st["generator"]["installed"], false);
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
/// A stand-in for `vantage server` that answers health checks and serves a
|
||||
/// manifest, to exercise the supervisor and proxy end to end.
|
||||
#[cfg(unix)]
|
||||
#[tokio::test]
|
||||
async fn proxy_starts_and_reaches_the_generator() {
|
||||
if std::process::Command::new("python3").arg("--version").output().is_err() {
|
||||
return;
|
||||
}
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let t = setup_with(|cfg, dir| {
|
||||
let bin = dir.join("fake-vantage");
|
||||
std::fs::write(
|
||||
&bin,
|
||||
r#"#!/usr/bin/env python3
|
||||
import sys, os, http.server
|
||||
a = sys.argv
|
||||
port = int(a[a.index('--port') + 1])
|
||||
assert a[1] == 'server' and '--assets' in a and '--players-file' in a
|
||||
token = os.environ['VANTAGE_SERVER_TOKEN']
|
||||
class H(http.server.BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
if self.path == '/v1/health':
|
||||
body = b'ok'
|
||||
elif self.headers.get('Authorization') != 'Bearer ' + token:
|
||||
self.send_response(401); self.end_headers(); return
|
||||
else:
|
||||
body = b'{"fake":true,"path":"' + self.path.encode() + b'"}'
|
||||
self.send_response(200); self.send_header('ETag', '"v1"'); self.send_header('Content-Type', 'application/json'); self.end_headers(); self.wfile.write(body)
|
||||
def log_message(self, *a): pass
|
||||
http.server.ThreadingHTTPServer(('127.0.0.1', port), H).serve_forever()
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::set_permissions(&bin, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||
std::fs::create_dir_all(dir.join("assets")).unwrap();
|
||||
cfg.vantage_bin = Some(bin);
|
||||
cfg.vantage_assets = Some(dir.join("assets"));
|
||||
})
|
||||
.await;
|
||||
let admin = t.login("admin", "supersecret").await;
|
||||
let (_, v) = t.call("POST", "/api/admin/servers", Some(&admin), Some(json!({"name": "S", "live_map_enabled": true}))).await;
|
||||
let id = v["server"]["id"].as_i64().unwrap();
|
||||
let token = v["token"].as_str().unwrap().to_string();
|
||||
|
||||
let uri = format!("/api/livemap/{id}/overworld/v1/worlds/default/manifest.json");
|
||||
let (s, _) = t.call("GET", &uri, Some(&admin), None).await;
|
||||
assert_eq!(s, StatusCode::SERVICE_UNAVAILABLE, "no world uploaded yet");
|
||||
|
||||
post_bytes(&t, &format!("/api/server/v1/livemap/chunks?dim=overworld"), &token, record(0, 0, 5, b"abc")).await;
|
||||
let mut gz = vec![0x1f, 0x8b];
|
||||
gz.extend([0u8; 30]);
|
||||
post_bytes(&t, "/api/server/v1/livemap/level", &token, gz).await;
|
||||
|
||||
let (_, info) = t.call("GET", &format!("/api/v1/servers/{id}/livemap"), Some(&admin), None).await;
|
||||
assert_eq!(info["ready"], true, "{info}");
|
||||
let (s, body) = t.call("GET", &uri, Some(&admin), None).await;
|
||||
assert_eq!(s, StatusCode::OK, "{body}");
|
||||
assert_eq!(body["fake"], true);
|
||||
assert_eq!(body["path"], "/v1/worlds/default/manifest.json");
|
||||
let (_, st) = t.call("GET", &format!("/api/admin/servers/{id}/livemap"), Some(&admin), None).await;
|
||||
assert_eq!(st["running"][0], "overworld");
|
||||
// Reset stops the generator and deletes the mirror.
|
||||
t.call("DELETE", &format!("/api/admin/servers/{id}/livemap"), Some(&admin), None).await;
|
||||
let (_, st) = t.call("GET", &format!("/api/admin/servers/{id}/livemap"), Some(&admin), None).await;
|
||||
assert!(st["running"].as_array().unwrap().is_empty());
|
||||
}
|
||||
@@ -23,7 +23,7 @@ async fn world() -> World {
|
||||
}
|
||||
let (alex, steve) = (t.login("Alex", "password123").await, t.login("Steve", "password123").await);
|
||||
let (alex_uuid, steve_uuid) = (t.uuid("Alex").await, t.uuid("Steve").await);
|
||||
let (s, srv) = t.call("POST", "/api/admin/servers", Some(&admin), Some(json!({"name": "SMP", "instance_id": "smp", "map_address": "https://map.example.com"}))).await;
|
||||
let (s, srv) = t.call("POST", "/api/admin/servers", Some(&admin), Some(json!({"name": "SMP", "instance_id": "smp"}))).await;
|
||||
assert_eq!(s, StatusCode::OK, "{srv}");
|
||||
let sid = srv["server"]["id"].as_i64().unwrap();
|
||||
let server = srv["token"].as_str().unwrap().to_string();
|
||||
@@ -41,20 +41,6 @@ impl World {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn map_address_is_validated_and_shown_to_launchers() {
|
||||
let w = world().await;
|
||||
let (_, list) = w.t.call("GET", "/api/v1/servers/public", None, None).await;
|
||||
assert_eq!(list["servers"][0]["map_address"], "https://map.example.com");
|
||||
for bad in ["not a url", "ftp://map.example.com", "https://user:pw@map.example.com", "https://map.example.com/#x"] {
|
||||
let (s, _) = w.t.call("PUT", &format!("/api/admin/servers/{}", w.sid), Some(&w.admin), Some(json!({"name": "SMP", "instance_id": "smp", "map_address": bad}))).await;
|
||||
assert_eq!(s, StatusCode::BAD_REQUEST, "{bad}");
|
||||
}
|
||||
let (s, r) = w.t.call("PUT", &format!("/api/admin/servers/{}", w.sid), Some(&w.admin), Some(json!({"name": "SMP", "instance_id": "smp", "map_address": ""}))).await;
|
||||
assert_eq!(s, StatusCode::OK, "{r}");
|
||||
assert_eq!(r["map_address"], "");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn tpa_and_messages_are_queued_once_for_the_game_server() {
|
||||
let w = world().await;
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
//! 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);
|
||||
}
|
||||
Reference in new issue
Block a user