104 lines
3.9 KiB
JavaScript
104 lines
3.9 KiB
JavaScript
import { test, expect, beforeAll, afterAll } from 'bun:test';
|
|
import { mkdtempSync, rmSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { Hono } from 'hono';
|
|
const root = mkdtempSync(join(tmpdir(), 'ytp-piano-'));
|
|
process.env.DB_PATH = join(root, 'test.db');
|
|
const db = await import('./db.js');
|
|
const { registerPianoRoutes } = await import('./piano.js');
|
|
const ID = '0gfX0dFLaBc',
|
|
TOKEN = 'piano-worker-test-'.repeat(3);
|
|
let app,
|
|
time = 1000;
|
|
const post = (path, body = {}, token = TOKEN) =>
|
|
app.request(path, {
|
|
method: 'POST',
|
|
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body),
|
|
});
|
|
beforeAll(async () => {
|
|
await db.initDb();
|
|
await db.upsertMedia(ID, { status: 'ready', duration: 60, gen: 1 });
|
|
app = new Hono();
|
|
await registerPianoRoutes(app, {
|
|
db,
|
|
enabled: true,
|
|
workerToken: TOKEN,
|
|
now: () => time,
|
|
adminAuth: async (c, next) =>
|
|
c.req.header('x-admin') === 'yes' ? next() : c.json({ ok: false }, 401),
|
|
}).ready;
|
|
});
|
|
afterAll(async () => {
|
|
db.db.close();
|
|
rmSync(root, { recursive: true, force: true });
|
|
});
|
|
test('server piano requires admin requests and dedicated worker authentication', async () => {
|
|
expect((await post(`/api/media/${ID}/piano`)).status).toBe(401);
|
|
expect((await post('/api/piano-worker/claim', {}, 'wrong')).status).toBe(401);
|
|
const result = await app.request(`/api/media/${ID}/piano`, {
|
|
method: 'POST',
|
|
headers: { 'x-admin': 'yes' },
|
|
});
|
|
expect(result.status).toBe(202);
|
|
expect((await result.json()).job.status).toBe('queued');
|
|
});
|
|
test('claim is exclusive; validated results persist as compact notes with inferred hands', async () => {
|
|
const first = await (await post('/api/piano-worker/claim')).json();
|
|
expect(first.job.audioPath).toBe(`/api/media/${ID}?a=1`);
|
|
expect((await (await post('/api/piano-worker/claim')).json()).job).toBeNull();
|
|
expect(
|
|
(await post(`/api/piano-worker/${ID}`, { action: 'complete', lease: 'wrong', notes: [] }))
|
|
.status,
|
|
).toBe(409);
|
|
expect(
|
|
(
|
|
await post(`/api/piano-worker/${ID}`, {
|
|
action: 'complete',
|
|
lease: first.job.lease,
|
|
notes: [{ pitch: 60, start: 2, end: 1, velocity: 80 }],
|
|
})
|
|
).status,
|
|
).toBe(422);
|
|
expect(
|
|
(
|
|
await post(`/api/piano-worker/${ID}`, {
|
|
action: 'complete',
|
|
lease: first.job.lease,
|
|
notes: [{ pitch: 48, start: 1.24, end: 1.78, velocity: 85 }],
|
|
})
|
|
).status,
|
|
).toBe(200);
|
|
const result = await (await app.request(`/api/media/${ID}/piano`)).json();
|
|
expect(result.job.notes[0]).toEqual({
|
|
pitch: 48,
|
|
start: 1.24,
|
|
end: 1.78,
|
|
velocity: 85,
|
|
hand: 'left',
|
|
});
|
|
});
|
|
test('changed source invalidates notes, lost leases are re-claimed and stale workers cannot publish', async () => {
|
|
await db.upsertMedia(ID, { gen: 2 });
|
|
expect((await (await app.request(`/api/media/${ID}/piano`)).json()).job.status).toBe('stale');
|
|
await app.request(`/api/media/${ID}/piano`, { method: 'POST', headers: { 'x-admin': 'yes' } });
|
|
const old = (await (await post('/api/piano-worker/claim')).json()).job;
|
|
time += 120001;
|
|
const next = (await (await post('/api/piano-worker/claim')).json()).job;
|
|
expect(next.lease).not.toBe(old.lease);
|
|
expect(
|
|
(await post(`/api/piano-worker/${ID}`, { action: 'complete', lease: old.lease, notes: [] }))
|
|
.status,
|
|
).toBe(409);
|
|
expect(
|
|
(await post(`/api/piano-worker/${ID}`, { action: 'heartbeat', lease: next.lease })).status,
|
|
).toBe(200);
|
|
});
|
|
test('optional server pipeline defaults off and rejects malformed ids', async () => {
|
|
const disabled = new Hono();
|
|
await registerPianoRoutes(disabled, { db, adminAuth: async (c, next) => next() }).ready;
|
|
expect((await disabled.request(`/api/media/${ID}/piano`, { method: 'POST' })).status).toBe(503);
|
|
expect((await disabled.request('/api/media/bad/piano')).status).toBe(400);
|
|
});
|