mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-11-14 18:39:33 +01:00
76 lines
3.0 KiB
TypeScript
76 lines
3.0 KiB
TypeScript
/**
|
|||
|
|
* `POST /api/sessions/:id/keep-warm` (session-routes.ts): the per-session opt-in for the
|
||
|
|
* prompt-cache keep-warm. Stores the choice, persists and broadcasts the session, asks the
|
||
|
|
* scheduler to re-evaluate, clears the choice on `null`, and refuses a CLI with no cache
|
||
|
|
* telemetry. app.inject(), no port.
|
||
|
|
*/
|
||
|
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||
|
|
import Fastify, { type FastifyInstance } from 'fastify';
|
||
|
|
import fastifyCookie from '@fastify/cookie';
|
||
|
|
import { createMockRouteContext, type MockRouteContext } from '../mocks/index.js';
|
||
|
|
import { installRouteErrorHandler } from '../../src/web/route-error-handler.js';
|
||
|
|
import { registerSessionRoutes } from '../../src/web/routes/session-routes.js';
|
||
|
|
|
||
|
|
interface Harness {
|
||
|
|
app: FastifyInstance;
|
||
|
|
ctx: MockRouteContext;
|
||
|
|
}
|
||
|
|
|
||
|
|
async function createHarness(): Promise<Harness> {
|
||
|
|
const app = Fastify({ logger: false });
|
||
|
|
await app.register(fastifyCookie);
|
||
|
|
const ctx = createMockRouteContext();
|
||
|
|
registerSessionRoutes(app, ctx);
|
||
|
|
installRouteErrorHandler(app);
|
||
|
|
await app.ready();
|
||
|
|
return { app, ctx };
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('POST /api/sessions/:id/keep-warm', () => {
|
||
|
|
let harness: Harness;
|
||
|
|
|
||
|
|
beforeEach(async () => {
|
||
|
|
harness = await createHarness();
|
||
|
|
});
|
||
|
|
|
||
|
|
afterEach(async () => {
|
||
|
|
await harness.app.close();
|
||
|
|
});
|
||
|
|
|
||
|
|
const post = (payload: unknown) =>
|
||
|
|
harness.app.inject({ method: 'POST', url: `/api/sessions/${harness.ctx._sessionId}/keep-warm`, payload });
|
||
|
|
|
||
|
|
it('stores the choice, persists, broadcasts and re-evaluates', async () => {
|
||
|
|
const res = await post({ enabled: true });
|
||
|
|
expect(res.statusCode).toBe(200);
|
||
|
|
expect(JSON.parse(res.body).data.keepWarm).toMatchObject({ enabled: true });
|
||
|
|
expect(harness.ctx._session.keepWarmEnabled).toBe(true);
|
||
|
|
expect(harness.ctx.persistSessionState).toHaveBeenCalled();
|
||
|
|
expect(harness.ctx.keepWarm.schedule).toHaveBeenCalledWith(harness.ctx._sessionId);
|
||
|
|
// The re-evaluation runs first, so the broadcast state and the response carry the new status.
|
||
|
|
expect(harness.ctx.keepWarm.schedule.mock.invocationCallOrder[0]).toBeLessThan(
|
||
|
|
harness.ctx.persistSessionState.mock.invocationCallOrder[0]
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('clears the choice on null so the app-wide default applies again', async () => {
|
||
|
|
await post({ enabled: false });
|
||
|
|
const res = await post({ enabled: null });
|
||
|
|
expect(res.statusCode).toBe(200);
|
||
|
|
expect(harness.ctx._session.keepWarmEnabled).toBeUndefined();
|
||
|
|
expect(JSON.parse(res.body).data.keepWarm.enabled).toBeNull();
|
||
|
|
});
|
||
|
|
|
||
|
|
it('rejects a body without enabled', async () => {
|
||
|
|
expect((await post({})).statusCode).toBe(400);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('refuses a CLI that reports no cache telemetry', async () => {
|
||
|
|
harness.ctx._session.mode = 'shell';
|
||
|
|
const res = await post({ enabled: true });
|
||
|
|
// This harness has no envelope hook, so the refusal shows in the body, not the status.
|
||
|
|
expect(JSON.parse(res.body)).toMatchObject({ success: false, errorCode: 'INVALID_INPUT' });
|
||
|
|
expect(harness.ctx._session.keepWarmEnabled).toBeUndefined();
|
||
|
|
expect(harness.ctx.keepWarm.schedule).not.toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
});
|