import { describe, test, expect, beforeAll, afterAll } from "bun:test"; import { app } from "./index"; import { db } from "./db/client"; import { meshPointers, tilePointers } from "./db/schema"; import { minio, TILE_BUCKET } from "./minio"; import { createTestServer, deleteTestServer } from "./test-helpers"; // Elysia's `.handle()` drives the app in-process against a plain Request/Response, without // binding a real port — avoids racing a real running instance for the port (see MCMapper's // memory notes on stale dev-server processes double-binding a port on Windows) and is faster. function get(path: string) { return app.handle(new Request(`http://localhost${path}`)); } describe("GET /health", () => { test("reports ok", async () => { const res = await get("/health"); expect(res.status).toBe(200); expect(await res.json()).toEqual({ status: "ok" }); }); }); describe("GET /api/servers", () => { let server: { id: string; token: string }; beforeAll(async () => { server = await createTestServer("servers-route"); }); afterAll(async () => { await deleteTestServer(server.id); }); test("includes the seeded server without leaking its token", async () => { const res = await get("/api/servers"); expect(res.status).toBe(200); const rows = (await res.json()) as any[]; const found = rows.find((r) => r.id === server.id); expect(found).toEqual({ id: server.id, name: "test-servers-route" }); }); }); describe("GET /api/tiles/:serverId/:dimension/:zoom/:tileX/:tileY", () => { let server: { id: string; token: string }; const storageKey = "test-tile-object.png"; const pngBytes = new Uint8Array([1, 2, 3, 4]); beforeAll(async () => { server = await createTestServer("tiles-route"); await minio.putObject(TILE_BUCKET, storageKey, Buffer.from(pngBytes)); await db.insert(tilePointers).values({ serverId: server.id, dimension: 0, zoom: 0, tileX: 5, tileZ: -3, // stored as chunkZ; the route negates the requested tileY to get here storageKey, contentHash: "test-hash", }); }); afterAll(async () => { await deleteTestServer(server.id); await minio.removeObject(TILE_BUCKET, storageKey).catch(() => {}); }); test("negates tileY back to the stored chunkZ and streams the object", async () => { // chunkZ = -3, so the frontend's tileY (see public/js/map.js) is 3. const res = await get(`/api/tiles/${server.id}/0/0/5/3.png`); expect(res.status).toBe(200); expect(res.headers.get("content-type")).toBe("image/png"); const body = new Uint8Array(await res.arrayBuffer()); expect(body).toEqual(pngBytes); }); test("404s for a tile that hasn't been rendered", async () => { const res = await get(`/api/tiles/${server.id}/0/0/999/999.png`); expect(res.status).toBe(404); }); }); describe("GET /api/meshes/...", () => { let server: { id: string; token: string }; const storageKey = "test-mesh-object.bin"; const meshBytes = new Uint8Array([9, 9, 9]); beforeAll(async () => { server = await createTestServer("meshes-route"); await minio.putObject(TILE_BUCKET, storageKey, Buffer.from(meshBytes)); await db.insert(meshPointers).values({ serverId: server.id, dimension: 0, x: 2, z: 3, sectionY: 4, storageKey, contentHash: "test-hash", }); }); afterAll(async () => { await deleteTestServer(server.id); await minio.removeObject(TILE_BUCKET, storageKey).catch(() => {}); }); test("lists the rendered sections for a chunk", async () => { const res = await get(`/api/meshes/${server.id}/0/2/3`); expect(await res.json()).toEqual([4]); }); test("lists nothing for a chunk with no meshes yet", async () => { const res = await get(`/api/meshes/${server.id}/0/999/999`); expect(await res.json()).toEqual([]); }); test("serves the mesh binary with the right content type", async () => { const res = await get(`/api/meshes/${server.id}/0/2/3/4.bin`); expect(res.status).toBe(200); expect(res.headers.get("content-type")).toBe("application/octet-stream"); const body = new Uint8Array(await res.arrayBuffer()); expect(body).toEqual(meshBytes); }); test("404s for a section that hasn't been rendered", async () => { const res = await get(`/api/meshes/${server.id}/0/2/3/15.bin`); expect(res.status).toBe(404); }); });