dc7185c15e
Requested after Phase 2: from here on, MCMapper development follows TDD (test-first) — this retrofits the pieces already built before that request landed. worker: unit tests for the tile rasterizer (background fill, exact upscaled-block boundaries, full-grid painting, out-of-bounds columns) and the block-color palette (distinctness checks, including that the "unmapped block" placeholder never accidentally collides with a real block's color). 16 tests total alongside the existing mesher tests. api: wired up `bun test`. Unit tests for chunkOf's coordinate math. Integration tests (real Postgres/Redis/MinIO, see README's new "Running tests" section) for wsGateway.message() — auth accept/reject, upsert + dedup on columns/sections, not-authenticated/invalid-JSON handling — and for the tile/mesh/servers HTTP routes, driven through Elysia's in-process `.handle()` rather than a bound port (sidesteps the stale dev-server port-collision issue hit repeatedly this session). index.ts now exports `app` and only calls `.listen()` when run directly, specifically so tests can drive it this way. frontend: extracted mesh.js's binary-format parser into its own ESM module (mesh-format.js) so it's unit-testable without a browser/Babylon; mesh.js now imports it. Tests build a buffer independently of the parser (mirroring worker's encoder layout) so a mismatch in either direction — Rust producer or JS consumer drifting — would be caught.
129 lines
4.3 KiB
TypeScript
129 lines
4.3 KiB
TypeScript
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);
|
|
});
|
|
});
|