pixel8/tests/api.test.js

90 lines
3.5 KiB
JavaScript

"use strict";
const { test, before, after } = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const { loadConfig } = require("../src/config");
const { Engine } = require("../src/engine");
const { openStore } = require("../src/store");
const { createServer } = require("../src/server");
const { solidSource } = require("../src/sources");
const { solid } = require("../src/frame");
let server, base, store, engine, tmpDir;
before(async () => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "pixel8-"));
const config = loadConfig({ PORT: "0", HOST: "127.0.0.1", DB_PATH: path.join(tmpDir, "t.db"), MEDIA_DIR: path.join(tmpDir, "media"), ADMIN_TOKEN: "secret" });
engine = new Engine({ fps: 30 });
engine.setSource(solidSource([0, 0, 0]), "test:black");
store = openStore(config.dbPath);
({ server } = createServer(config, { engine, store }));
await new Promise((r) => server.listen(0, "127.0.0.1", r));
base = `http://127.0.0.1:${server.address().port}`;
});
after(async () => {
if (server.closeAllConnections) server.closeAllConnections();
await new Promise((r) => server.close(r));
store.close();
fs.rmSync(tmpDir, { recursive: true, force: true });
});
test("GET / serves preview HTML", async () => {
const r = await fetch(base + "/");
assert.equal(r.status, 200);
assert.match(await r.text(), /live preview/);
});
test("GET /api/status returns canonical 8x8 frame", async () => {
const j = await (await fetch(base + "/api/status")).json();
assert.equal(j.frame.width, 8);
assert.equal(j.frame.pixels.length, 64);
assert.equal(j.label, "test:black");
});
test("POST /api/submissions creates pending artwork (pixel8 JSON)", async () => {
const r = await fetch(base + "/api/submissions", {
method: "POST", headers: { "content-type": "application/json" },
body: JSON.stringify({ title: "Red dot", pixels: solid([255, 0, 0]) }),
});
assert.equal(r.status, 201);
const j = await r.json();
assert.equal(j.status, "pending");
assert.equal(j.preview.pixels.length, 64);
assert.equal(j.preview.frameCount, 1);
});
test("POST /api/submissions rejects malformed frame", async () => {
const r = await fetch(base + "/api/submissions", {
method: "POST", headers: { "content-type": "application/json" },
body: JSON.stringify({ title: "bad", pixels: [[1, 2, 3]] }),
});
assert.equal(r.status, 400);
});
test("admin routes require token; with token -> list + override", async () => {
assert.equal((await fetch(base + "/api/admin/artworks")).status, 401);
const list = await (await fetch(base + "/api/admin/artworks", { headers: { authorization: "Bearer secret" } })).json();
assert.equal(list.artworks.length, 1);
const ov = await fetch(base + "/api/admin/playback/override", {
method: "POST", headers: { authorization: "Bearer secret", "content-type": "application/json" },
body: JSON.stringify({ source: "rainbow" }),
});
assert.equal(ov.status, 200);
assert.equal(engine.sourceLabel, "rainbow");
});
test("WS /api/live immediately sends current frame (R-083)", async () => {
const ws = new WebSocket(base.replace("http", "ws") + "/api/live");
const msg = await new Promise((resolve, reject) => {
ws.onmessage = (e) => resolve(JSON.parse(e.data));
ws.onerror = reject;
setTimeout(() => reject(new Error("timeout")), 3000);
});
assert.equal(msg.type, "frame");
assert.equal(msg.pixels.length, 64);
ws.close();
});