mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
* feat: implement media tasks persistence
* fix(daemon): satisfy exactOptionalPropertyTypes in media-tasks-routes test
`process.env.OD_DATA_DIR` is `string | undefined`, but `openDatabase`'s
options accept `{ dataDir?: string }`. Under the daemon tsconfig's
exactOptionalPropertyTypes the two are not assignable. Spread the key
in only when defined.
* fix(daemon): restore mcp-config / mcp-oauth / mcp-tokens imports in server.ts
The earlier 'Merge branch main into main' resolved the import-block
conflict by keeping only the new media-tasks import and dropping the
three pre-existing import blocks. server.ts still uses 13+ symbols
from those modules (PendingAuthCache, MCP_TEMPLATES, beginAuth,
readMcpConfig, getToken, etc.), so the daemon crashed at startup with
'ReferenceError: PendingAuthCache is not defined' the moment Playwright
booted it. Restore the three import blocks verbatim from main.
---------
Co-authored-by: lefarcen <935902669@qq.com>
69 lines
2.1 KiB
TypeScript
69 lines
2.1 KiB
TypeScript
import type http from 'node:http';
|
|
import { afterEach, describe, expect, it } from 'vitest';
|
|
import { randomUUID } from 'node:crypto';
|
|
import { closeDatabase, insertProject, openDatabase } from '../src/db.js';
|
|
import { insertMediaTask } from '../src/media-tasks.js';
|
|
import { startServer } from '../src/server.js';
|
|
|
|
describe('media task route recovery', () => {
|
|
let server: http.Server | null = null;
|
|
|
|
afterEach(async () => {
|
|
if (server) {
|
|
await new Promise<void>((resolve) => server?.close(() => resolve()));
|
|
server = null;
|
|
}
|
|
closeDatabase();
|
|
});
|
|
|
|
it('recovers a pre-restart running task so wait returns interrupted instead of 404', async () => {
|
|
const dataDir = process.env.OD_DATA_DIR;
|
|
const db = openDatabase(process.cwd(), dataDir === undefined ? {} : { dataDir });
|
|
const projectId = `project_${randomUUID()}`;
|
|
const taskId = `task_${randomUUID()}`;
|
|
const now = Date.now() - 5_000;
|
|
|
|
insertProject(db, {
|
|
id: projectId,
|
|
name: 'Recovered media project',
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
});
|
|
insertMediaTask(db, {
|
|
id: taskId,
|
|
projectId,
|
|
status: 'running',
|
|
surface: 'video',
|
|
model: 'seedance-2',
|
|
progress: ['provider task accepted'],
|
|
startedAt: now,
|
|
updatedAt: now,
|
|
});
|
|
|
|
const started = await startServer({ port: 0, returnServer: true }) as {
|
|
url: string;
|
|
server: http.Server;
|
|
};
|
|
server = started.server;
|
|
|
|
const response = await fetch(`${started.url}/api/media/tasks/${encodeURIComponent(taskId)}/wait`, {
|
|
method: 'POST',
|
|
headers: { 'content-type': 'application/json' },
|
|
body: JSON.stringify({ since: 0, timeoutMs: 0 }),
|
|
});
|
|
const body = await response.json() as {
|
|
status?: string;
|
|
progress?: string[];
|
|
error?: { code?: string; message?: string };
|
|
};
|
|
|
|
expect(response.status).toBe(200);
|
|
expect(body.status).toBe('interrupted');
|
|
expect(body.progress).toEqual(['provider task accepted']);
|
|
expect(body.error).toMatchObject({
|
|
code: 'DAEMON_RESTART',
|
|
message: 'media task interrupted by daemon restart',
|
|
});
|
|
});
|
|
});
|