b6c6ddfbc4
Implements docs/architecture/world_persistence_impl.md §9's backend task: chunk/room/revision Postgres schema (migration 0005), a Python port of the world.md §3.6 binary chunk codec with golden-file tests, a WorldStore service (world.md §5.1 minus flush_dirty/rebuild_index), the public world-content router (rooms/chunks CRUD, optimistic concurrency, batch writes) and the internal read-only API for dedicated servers, and game_servers.py changes: a per-spawn bearer token, --api-url/--server-token args, and a real "ready"-field wait replacing _await_port, which bound the UDP port before content existed and failed open (no error) past its deadline. create_world now auto-creates the "main" room. Deviations/findings, not silently decided: - chunk_codec.py implements real IEEE-754 float32 packing rather than mirroring a confirmed bug in scripts/chunk_data_decoder.gd's _read_float32/_write_float32, which do a lossy int<->float cast instead of a bit reinterpretation (verified against Godot 4.6.2 headless) and will corrupt any non-integer position/rotation/scale/bounds value. Not fixed here (client-side, out of scope); flagged for the client task. - world_persistence_impl.md references a world's monotonic content_revision (§6/§7.2/§8.1) but never defines where it's stored. Resolved as MAX(id) over the append-only world_chunk_revisions table, scoped to world_id, rather than adding an undocumented column. - No existing Godot-side golden fixture/test vectors were found to reuse, so tests/fixtures/chunks/*.bin are authored fresh from this implementation (see generate_fixtures.py). Verification: 132/132 backend pytest tests pass against a real ephemeral Postgres (both reusing an already-running instance and cold via conftest.py's own docker-compose bootstrap), including migration upgrade/downgrade/upgrade, codec round-trip + validation-error tests, and full HTTP round trips for PUT/GET/batch/delete/revisions/rollback and the internal API's token auth. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y2FESCaMXeJpC9gSctiEeD
491 lines
20 KiB
Python
491 lines
20 KiB
Python
"""Integration tests for the world content API: rooms + chunks.
|
|
|
|
Covers docs/architecture/world_persistence_impl.md §8.1 (public router,
|
|
app/routers/world_content.py) and §8.3 (internal router,
|
|
app/routers/internal.py), plus the create_world -> auto "main" room
|
|
behaviour (§4.1).
|
|
|
|
Runs against the real ephemeral Postgres the hermetic conftest.py stands
|
|
up -- these are full HTTP round trips through TestClient, not mocked.
|
|
"""
|
|
|
|
import base64
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from app.main import app
|
|
from app.services import chunk_codec as cc
|
|
from app.services.game_servers import supervisor
|
|
|
|
GRID_32 = (32, 32, 1)
|
|
|
|
|
|
def _auth(client: TestClient, name: str) -> tuple[dict, int]:
|
|
r = client.post("/api/auth/register", json={
|
|
"username": name, "email": f"{name}@e.com", "password": "secret123",
|
|
"date_of_birth": "1990-01-01",
|
|
})
|
|
assert r.status_code == 201, r.text
|
|
headers = {"Authorization": f"Bearer {r.json()['access_token']}"}
|
|
me = client.get("/api/users/me", headers=headers)
|
|
return headers, me.json()["id"]
|
|
|
|
|
|
def _make_world(client: TestClient, headers: dict, visibility: str = "public") -> int:
|
|
r = client.post("/api/worlds/", headers=headers, json={"name": "W", "visibility": visibility})
|
|
assert r.status_code == 201, r.text
|
|
return r.json()["id"]
|
|
|
|
|
|
def _build_chunk(world_id: int, slug: str, cx: int, cy: int, cz: int, *,
|
|
grid=GRID_32, revision: int = 1, extra_tag: str = "") -> bytes:
|
|
"""A small but structurally-real chunk payload for `world_id`/`slug`/coord,
|
|
sized to match the room's default 32x32x1 grid."""
|
|
w, h, d = grid
|
|
layer = cc.TileLayer(
|
|
name="ground", layer_id=0, width=w, height=h, depth=d,
|
|
palette=[cc.TileDef(id=0, asset_id="", solid=False, flags=0, variants=1)],
|
|
tiles=[0] * (w * h * d),
|
|
)
|
|
obj = cc.WorldObject(
|
|
id=f"obj_{extra_tag or '1'}", type="prop", asset_id="props/rock",
|
|
pos=(1.0, 1.0, 0.0), properties={"tag": extra_tag},
|
|
)
|
|
payload = cc.ChunkPayload(
|
|
world_id=str(world_id), room_id=slug, coord=(cx, cy, cz),
|
|
bounds=cc.ChunkBounds(min=(0.0, 0.0, 0.0), size=(float(w), float(h), float(d))),
|
|
tile_layers=[layer], objects=[obj], revision=revision, author="pytest",
|
|
created_at=1_700_000_000, updated_at=1_700_000_000,
|
|
)
|
|
return cc.encode_binary(payload)
|
|
|
|
|
|
class TestCreateWorldMainRoom:
|
|
def test_create_world_auto_creates_main_room(self):
|
|
client = TestClient(app)
|
|
headers, _ = _auth(client, "cw_main")
|
|
wid = _make_world(client, headers)
|
|
|
|
r = client.get(f"/api/worlds/{wid}/rooms", headers=headers)
|
|
assert r.status_code == 200, r.text
|
|
rooms = r.json()
|
|
assert len(rooms) == 1
|
|
assert rooms[0]["slug"] == "main"
|
|
assert rooms[0]["name"] == "Main"
|
|
assert rooms[0]["chunk_count"] == 0
|
|
assert rooms[0]["grid"]["size"] == [32, 32, 1]
|
|
|
|
|
|
class TestRoomAuth:
|
|
def test_create_room_requires_owner(self):
|
|
client = TestClient(app)
|
|
owner_headers, _ = _auth(client, "room_owner")
|
|
wid = _make_world(client, owner_headers)
|
|
other_headers, _ = _auth(client, "room_other")
|
|
r = client.post(f"/api/worlds/{wid}/rooms", headers=other_headers,
|
|
json={"slug": "annex", "name": "Annex"})
|
|
assert r.status_code == 403
|
|
|
|
def test_list_rooms_of_private_world_is_404_for_non_owner(self):
|
|
client = TestClient(app)
|
|
owner_headers, _ = _auth(client, "room_priv_owner")
|
|
wid = _make_world(client, owner_headers, "private")
|
|
other_headers, _ = _auth(client, "room_priv_other")
|
|
r = client.get(f"/api/worlds/{wid}/rooms", headers=other_headers)
|
|
assert r.status_code == 404
|
|
|
|
|
|
class TestChunkPutGetRoundTrip:
|
|
def test_binary_put_then_binary_get_round_trips(self):
|
|
client = TestClient(app)
|
|
headers, _ = _auth(client, "chunk_rt")
|
|
wid = _make_world(client, headers)
|
|
payload = _build_chunk(wid, "main", 0, 0, 0)
|
|
|
|
r = client.put(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/0/0/0",
|
|
headers={**headers, "Content-Type": "application/octet-stream", "X-Base-Revision": "0"},
|
|
content=payload,
|
|
)
|
|
assert r.status_code == 200, r.text
|
|
body = r.json()
|
|
assert body["revision"] == 1
|
|
assert body["payload_len"] == len(payload)
|
|
assert body["checksum"] == cc.checksum(payload[cc.HEADER_SIZE:])
|
|
assert body["live_session"] is False
|
|
|
|
g = client.get(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/0/0/0",
|
|
headers={**headers, "Accept": "application/octet-stream"},
|
|
)
|
|
assert g.status_code == 200
|
|
assert g.content == payload
|
|
assert g.headers["ETag"] == f'"{body["checksum"]}"'
|
|
assert g.headers["X-Chunk-Revision"] == "1"
|
|
|
|
def test_json_accept_returns_decoded_shape(self):
|
|
client = TestClient(app)
|
|
headers, _ = _auth(client, "chunk_json")
|
|
wid = _make_world(client, headers)
|
|
payload = _build_chunk(wid, "main", 1, 1, 0)
|
|
client.put(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/1/1/0",
|
|
headers={**headers, "Content-Type": "application/octet-stream", "X-Base-Revision": "0"},
|
|
content=payload,
|
|
)
|
|
g = client.get(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/1/1/0",
|
|
headers={**headers, "Accept": "application/json"},
|
|
)
|
|
assert g.status_code == 200
|
|
data = g.json()
|
|
assert data["coord"] == {"cx": 1, "cy": 1, "cz": 0}
|
|
assert data["world_id"] == str(wid)
|
|
assert data["room_id"] == "main"
|
|
assert len(data["objects"]) == 1
|
|
|
|
def test_json_put_with_base64_payload(self):
|
|
client = TestClient(app)
|
|
headers, _ = _auth(client, "chunk_json_put")
|
|
wid = _make_world(client, headers)
|
|
payload = _build_chunk(wid, "main", 2, 2, 0)
|
|
r = client.put(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/2/2/0", headers=headers,
|
|
json={"base_revision": 0, "payload_b64": base64.b64encode(payload).decode()},
|
|
)
|
|
assert r.status_code == 200, r.text
|
|
assert r.json()["revision"] == 1
|
|
|
|
def test_if_none_match_returns_304(self):
|
|
client = TestClient(app)
|
|
headers, _ = _auth(client, "chunk_304")
|
|
wid = _make_world(client, headers)
|
|
payload = _build_chunk(wid, "main", 3, 3, 0)
|
|
put = client.put(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/3/3/0",
|
|
headers={**headers, "Content-Type": "application/octet-stream", "X-Base-Revision": "0"},
|
|
content=payload,
|
|
)
|
|
checksum = put.json()["checksum"]
|
|
g = client.get(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/3/3/0",
|
|
headers={**headers, "If-None-Match": f'"{checksum}"'},
|
|
)
|
|
assert g.status_code == 304
|
|
|
|
def test_unwritten_coordinate_is_404(self):
|
|
client = TestClient(app)
|
|
headers, _ = _auth(client, "chunk_404")
|
|
wid = _make_world(client, headers)
|
|
g = client.get(f"/api/worlds/{wid}/rooms/main/chunks/9/9/0", headers=headers)
|
|
assert g.status_code == 404
|
|
|
|
|
|
class TestRevisionConflict:
|
|
def test_create_conflict_when_chunk_already_exists(self):
|
|
client = TestClient(app)
|
|
headers, _ = _auth(client, "conflict_create")
|
|
wid = _make_world(client, headers)
|
|
payload = _build_chunk(wid, "main", 4, 4, 0)
|
|
first = client.put(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/4/4/0",
|
|
headers={**headers, "Content-Type": "application/octet-stream", "X-Base-Revision": "0"},
|
|
content=payload,
|
|
)
|
|
assert first.status_code == 200
|
|
|
|
second = client.put(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/4/4/0",
|
|
headers={**headers, "Content-Type": "application/octet-stream", "X-Base-Revision": "0"},
|
|
content=payload,
|
|
)
|
|
assert second.status_code == 409
|
|
body = second.json()
|
|
assert body["detail"] == "revision conflict"
|
|
assert body["current_revision"] == 1
|
|
|
|
def test_stale_base_revision_conflict_reports_current_state(self):
|
|
client = TestClient(app)
|
|
headers, _ = _auth(client, "conflict_stale")
|
|
wid = _make_world(client, headers)
|
|
payload_v1 = _build_chunk(wid, "main", 5, 5, 0, revision=1)
|
|
client.put(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/5/5/0",
|
|
headers={**headers, "Content-Type": "application/octet-stream", "X-Base-Revision": "0"},
|
|
content=payload_v1,
|
|
)
|
|
payload_v2 = _build_chunk(wid, "main", 5, 5, 0, revision=2, extra_tag="v2")
|
|
r2 = client.put(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/5/5/0",
|
|
headers={**headers, "Content-Type": "application/octet-stream", "X-Base-Revision": "1"},
|
|
content=payload_v2,
|
|
)
|
|
assert r2.status_code == 200
|
|
assert r2.json()["revision"] == 2
|
|
|
|
# Now retry with the now-stale base_revision=1 again.
|
|
payload_v3 = _build_chunk(wid, "main", 5, 5, 0, revision=3, extra_tag="v3")
|
|
stale = client.put(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/5/5/0",
|
|
headers={**headers, "Content-Type": "application/octet-stream", "X-Base-Revision": "1"},
|
|
content=payload_v3,
|
|
)
|
|
assert stale.status_code == 409
|
|
body = stale.json()
|
|
assert body["current_revision"] == 2
|
|
assert body["current_checksum"] == r2.json()["checksum"]
|
|
|
|
|
|
class TestBatchWrite:
|
|
def test_batch_put_all_succeed(self):
|
|
client = TestClient(app)
|
|
headers, _ = _auth(client, "batch_ok")
|
|
wid = _make_world(client, headers)
|
|
chunks = [
|
|
{"cx": cx, "cy": 0, "cz": 0, "base_revision": 0,
|
|
"payload_b64": base64.b64encode(_build_chunk(wid, "main", cx, 0, 0)).decode()}
|
|
for cx in (10, 11)
|
|
]
|
|
r = client.put(f"/api/worlds/{wid}/rooms/main/chunks", headers=headers, json={"chunks": chunks})
|
|
assert r.status_code == 200, r.text
|
|
body = r.json()
|
|
assert len(body["chunks"]) == 2
|
|
assert all(c["revision"] == 1 for c in body["chunks"])
|
|
|
|
idx = client.get(f"/api/worlds/{wid}/rooms/main/chunks", headers=headers)
|
|
keys = {c["key"] for c in idx.json()["chunks"]}
|
|
assert f"{wid}:main:10:0:0" in keys
|
|
assert f"{wid}:main:11:0:0" in keys
|
|
|
|
def test_batch_put_is_all_or_nothing_on_conflict(self):
|
|
client = TestClient(app)
|
|
headers, _ = _auth(client, "batch_conflict")
|
|
wid = _make_world(client, headers)
|
|
|
|
# Pre-create one of the two coordinates so the batch's base_revision=0
|
|
# for it will conflict.
|
|
pre = _build_chunk(wid, "main", 20, 0, 0)
|
|
client.put(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/20/0/0",
|
|
headers={**headers, "Content-Type": "application/octet-stream", "X-Base-Revision": "0"},
|
|
content=pre,
|
|
)
|
|
|
|
chunks = [
|
|
{"cx": 21, "cy": 0, "cz": 0, "base_revision": 0,
|
|
"payload_b64": base64.b64encode(_build_chunk(wid, "main", 21, 0, 0)).decode()},
|
|
{"cx": 20, "cy": 0, "cz": 0, "base_revision": 0, # conflicts: already exists at revision 1
|
|
"payload_b64": base64.b64encode(_build_chunk(wid, "main", 20, 0, 0, extra_tag="v2")).decode()},
|
|
]
|
|
r = client.put(f"/api/worlds/{wid}/rooms/main/chunks", headers=headers, json={"chunks": chunks})
|
|
assert r.status_code == 409
|
|
|
|
# Nothing from this batch should have been persisted -- (21,0,0)
|
|
# must NOT exist even though it was valid and came first.
|
|
g = client.get(f"/api/worlds/{wid}/rooms/main/chunks/21/0/0", headers=headers)
|
|
assert g.status_code == 404
|
|
|
|
|
|
class TestDelete:
|
|
def test_delete_then_get_is_404(self):
|
|
client = TestClient(app)
|
|
headers, _ = _auth(client, "chunk_delete")
|
|
wid = _make_world(client, headers)
|
|
payload = _build_chunk(wid, "main", 30, 0, 0)
|
|
client.put(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/30/0/0",
|
|
headers={**headers, "Content-Type": "application/octet-stream", "X-Base-Revision": "0"},
|
|
content=payload,
|
|
)
|
|
d = client.delete(f"/api/worlds/{wid}/rooms/main/chunks/30/0/0", headers=headers)
|
|
assert d.status_code == 204
|
|
g = client.get(f"/api/worlds/{wid}/rooms/main/chunks/30/0/0", headers=headers)
|
|
assert g.status_code == 404
|
|
|
|
|
|
class TestRevisionsAndRollback:
|
|
def test_revisions_then_rollback(self):
|
|
client = TestClient(app)
|
|
headers, _ = _auth(client, "chunk_rollback")
|
|
wid = _make_world(client, headers)
|
|
v1 = _build_chunk(wid, "main", 40, 0, 0, revision=1)
|
|
r1 = client.put(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/40/0/0",
|
|
headers={**headers, "Content-Type": "application/octet-stream", "X-Base-Revision": "0"},
|
|
content=v1,
|
|
)
|
|
v2 = _build_chunk(wid, "main", 40, 0, 0, revision=2, extra_tag="v2")
|
|
r2 = client.put(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/40/0/0",
|
|
headers={**headers, "Content-Type": "application/octet-stream", "X-Base-Revision": "1"},
|
|
content=v2,
|
|
)
|
|
assert r2.status_code == 200
|
|
|
|
revs = client.get(f"/api/worlds/{wid}/rooms/main/chunks/40/0/0/revisions", headers=headers)
|
|
assert revs.status_code == 200
|
|
rev_numbers = [row["revision"] for row in revs.json()]
|
|
assert 1 in rev_numbers and 2 in rev_numbers
|
|
|
|
rb = client.post(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/40/0/0/rollback",
|
|
headers=headers, json={"revision": 1},
|
|
)
|
|
assert rb.status_code == 200, rb.text
|
|
assert rb.json()["revision"] == 3 # a new revision, restoring v1's content
|
|
assert rb.json()["checksum"] == r1.json()["checksum"]
|
|
|
|
def test_revisions_requires_owner(self):
|
|
client = TestClient(app)
|
|
owner_headers, _ = _auth(client, "revisions_owner")
|
|
wid = _make_world(client, owner_headers)
|
|
payload = _build_chunk(wid, "main", 41, 0, 0)
|
|
client.put(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/41/0/0",
|
|
headers={**owner_headers, "Content-Type": "application/octet-stream", "X-Base-Revision": "0"},
|
|
content=payload,
|
|
)
|
|
other_headers, _ = _auth(client, "revisions_other")
|
|
r = client.get(f"/api/worlds/{wid}/rooms/main/chunks/41/0/0/revisions", headers=other_headers)
|
|
assert r.status_code == 403
|
|
|
|
|
|
class TestValidationErrors:
|
|
def test_malformed_payload_is_422(self):
|
|
client = TestClient(app)
|
|
headers, _ = _auth(client, "chunk_malformed")
|
|
wid = _make_world(client, headers)
|
|
r = client.put(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/0/0/0",
|
|
headers={**headers, "Content-Type": "application/octet-stream", "X-Base-Revision": "0"},
|
|
content=b"not a real chunk",
|
|
)
|
|
assert r.status_code == 422
|
|
assert r.json()["reason"] == "malformed"
|
|
|
|
def test_wrong_grid_size_is_422_layer_dims(self):
|
|
client = TestClient(app)
|
|
headers, _ = _auth(client, "chunk_layerdims")
|
|
wid = _make_world(client, headers)
|
|
# Room's default grid is 32x32x1; build a chunk sized for 4x4x1.
|
|
payload = _build_chunk(wid, "main", 0, 0, 0, grid=(4, 4, 1))
|
|
r = client.put(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/0/0/0",
|
|
headers={**headers, "Content-Type": "application/octet-stream", "X-Base-Revision": "0"},
|
|
content=payload,
|
|
)
|
|
assert r.status_code == 422
|
|
assert r.json()["reason"] == "layer_dims"
|
|
|
|
def test_oversized_payload_is_413(self, monkeypatch):
|
|
from app.config import settings
|
|
monkeypatch.setattr(settings, "WORLD_CHUNK_MAX_PAYLOAD_BYTES", 10)
|
|
client = TestClient(app)
|
|
headers, _ = _auth(client, "chunk_413")
|
|
wid = _make_world(client, headers)
|
|
payload = _build_chunk(wid, "main", 0, 0, 0)
|
|
r = client.put(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/0/0/0",
|
|
headers={**headers, "Content-Type": "application/octet-stream", "X-Base-Revision": "0"},
|
|
content=payload,
|
|
)
|
|
assert r.status_code == 413
|
|
|
|
|
|
class TestInternalApi:
|
|
"""docs/architecture/world_persistence_impl.md §7/§8.3. No real Godot
|
|
process is spawned in this test env (GAME_SERVERS_ENABLED=False); we
|
|
register a fake in-memory server entry with a known token to exercise
|
|
require_game_server without needing a live process."""
|
|
|
|
@pytest.fixture
|
|
def fake_server_token(self):
|
|
# NB: a class body can't read a same-named local from the
|
|
# enclosing function via `token = token` (class bodies use LOAD_NAME,
|
|
# which skips enclosing function scopes) -- use a literal instead.
|
|
world_id_str = "internal-test-world"
|
|
|
|
class _FakeSrv:
|
|
def alive(self_inner):
|
|
return True
|
|
token = "test-internal-token-12345"
|
|
|
|
supervisor._servers[world_id_str] = _FakeSrv()
|
|
yield _FakeSrv.token
|
|
supervisor._servers.pop(world_id_str, None)
|
|
|
|
def test_missing_token_is_401(self):
|
|
client = TestClient(app)
|
|
r = client.get("/api/internal/worlds/1/manifest")
|
|
assert r.status_code == 401
|
|
|
|
def test_wrong_token_is_403(self):
|
|
client = TestClient(app)
|
|
r = client.get(
|
|
"/api/internal/worlds/1/manifest",
|
|
headers={"Authorization": "Bearer not-the-real-token"},
|
|
)
|
|
assert r.status_code == 403
|
|
|
|
def test_valid_token_but_wrong_world_id_is_403(self, fake_server_token):
|
|
# The fake token is registered under world_id "internal-test-world";
|
|
# a request for a different world_id must not authenticate.
|
|
client = TestClient(app)
|
|
r = client.get(
|
|
"/api/internal/worlds/999999/manifest",
|
|
headers={"Authorization": f"Bearer {fake_server_token}"},
|
|
)
|
|
assert r.status_code == 403
|
|
|
|
def test_valid_token_reaches_manifest_for_real_world(self):
|
|
client = TestClient(app)
|
|
headers, _ = _auth(client, "internal_manifest")
|
|
wid = _make_world(client, headers)
|
|
|
|
class _FakeSrv:
|
|
def alive(self_inner):
|
|
return True
|
|
token = "another-real-token"
|
|
|
|
supervisor._servers[str(wid)] = _FakeSrv()
|
|
try:
|
|
r = client.get(
|
|
f"/api/internal/worlds/{wid}/manifest",
|
|
headers={"Authorization": "Bearer another-real-token"},
|
|
)
|
|
assert r.status_code == 200, r.text
|
|
body = r.json()
|
|
assert body["world_id"] == wid
|
|
assert len(body["rooms"]) == 1
|
|
assert body["rooms"][0]["slug"] == "main"
|
|
finally:
|
|
supervisor._servers.pop(str(wid), None)
|
|
|
|
def test_internal_chunk_fetch_returns_binary(self):
|
|
client = TestClient(app)
|
|
headers, _ = _auth(client, "internal_chunk")
|
|
wid = _make_world(client, headers)
|
|
payload = _build_chunk(wid, "main", 0, 0, 0)
|
|
client.put(
|
|
f"/api/worlds/{wid}/rooms/main/chunks/0/0/0",
|
|
headers={**headers, "Content-Type": "application/octet-stream", "X-Base-Revision": "0"},
|
|
content=payload,
|
|
)
|
|
|
|
class _FakeSrv:
|
|
def alive(self_inner):
|
|
return True
|
|
token = "internal-chunk-token"
|
|
|
|
supervisor._servers[str(wid)] = _FakeSrv()
|
|
try:
|
|
r = client.get(
|
|
f"/api/internal/worlds/{wid}/rooms/main/chunks/0/0/0",
|
|
headers={"Authorization": "Bearer internal-chunk-token"},
|
|
)
|
|
assert r.status_code == 200
|
|
assert r.content == payload
|
|
finally:
|
|
supervisor._servers.pop(str(wid), None)
|