open-design/apps/daemon/tests/daemon-lifecycle.test.ts
Denis Redozubov 9a3424d68c
feat(daemon): add sandbox runtime foundation (#3242)
* feat(daemon): add sandbox runtime foundation

* fix(daemon): preserve sandbox roots after agent env overrides

* fix(daemon): keep readiness probes pathless

* fix(daemon): harden headless run fallbacks

* fix(daemon): bootstrap sandbox runtime discovery

* fix(daemon): preserve explicit sandbox agent profile mounts

* fix(daemon): keep sandbox profile lookup run scoped

* fix(daemon): normalize sandbox data dir input

* fix(daemon): pin sandbox env roots to base data dir
2026-05-30 15:06:05 +00:00

91 lines
3.2 KiB
TypeScript

// Plan §3.F2 / spec §11.7 — daemon lifecycle endpoints.
//
// `od daemon status` and `od daemon stop` (both Phase 1.5) talk to the
// new /api/daemon/status + /api/daemon/shutdown routes. This suite spins
// the real Express app via `startServer` and asserts the public shape so
// the CLI can rely on it without re-checking each release.
import type http from 'node:http';
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { startServer } from '../src/server.js';
let server: http.Server;
let baseUrl: string;
let shutdown: (() => Promise<void> | void) | undefined;
beforeAll(async () => {
const started = (await startServer({ port: 0, returnServer: true })) as {
url: string;
server: http.Server;
shutdown?: () => Promise<void> | void;
};
baseUrl = started.url;
server = started.server;
shutdown = started.shutdown;
});
afterAll(async () => {
await Promise.resolve(shutdown?.());
await new Promise<void>((resolve) => server.close(() => resolve()));
});
describe('GET /api/daemon/status', () => {
it('returns a runtime snapshot covering version + bindHost + port + pid + installedPlugins', async () => {
const resp = await fetch(`${baseUrl}/api/daemon/status`);
expect(resp.status).toBe(200);
const body = (await resp.json()) as {
ok: boolean;
version: unknown;
bindHost: unknown;
port: unknown;
sandboxMode: boolean;
sandbox: { enabled: boolean };
pid: unknown;
installedPlugins: unknown;
shuttingDown: boolean;
namespace?: unknown;
};
expect(body.ok).toBe(true);
expect(typeof body.version === 'string' || typeof body.version === 'object').toBe(true);
expect(typeof body.bindHost).toBe('string');
expect(typeof body.port).toBe('number');
expect(typeof body.pid).toBe('number');
expect(typeof body.installedPlugins).toBe('number');
expect(body.sandboxMode).toBe(false);
expect(body.sandbox).toEqual({ enabled: false });
expect(body.shuttingDown).toBe(false);
expect(body).not.toHaveProperty('namespace');
});
});
describe('GET /api/ready', () => {
it('returns a readiness snapshot for headless launchers', async () => {
const resp = await fetch(`${baseUrl}/api/ready`);
expect(resp.status).toBe(200);
const body = (await resp.json()) as {
ok: boolean;
ready: boolean;
version: unknown;
};
expect(body.ok).toBe(true);
expect(body.ready).toBe(true);
expect(typeof body.version === 'string' || typeof body.version === 'object').toBe(true);
expect(body).not.toHaveProperty('dataDir');
expect(body).not.toHaveProperty('sandboxMode');
expect(body).not.toHaveProperty('sandbox');
});
});
describe('POST /api/daemon/shutdown', () => {
it('only accepts requests from local-daemon-allowed origins', async () => {
// Without the local-daemon header, the route is rejected. The
// exact rejection mode is owned by `requireLocalDaemonRequest`;
// we just check that a default test client gets a non-2xx.
const resp = await fetch(`${baseUrl}/api/daemon/shutdown`, {
method: 'POST',
headers: { origin: 'https://example.com' },
});
expect([401, 403, 404]).toContain(resp.status);
});
});