Archived
refactor: add session route service seam
This commit is contained in:
@@ -4,7 +4,8 @@ import fastifyWebsocket from "@fastify/websocket";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
import type { SessionCleanupExecuteResponse, SessionCleanupPreviewResponse } from "../../shared/apiTypes.js";
|
||||
import { SessionEventHub } from "../realtime/sessionEventHub.js";
|
||||
import { PiSessionService, type PiSessionManagerGateway, type PiSessionRef } from "./piSessionService.js";
|
||||
import { PiSessionService, type PiSessionManagerGateway } from "./piSessionService.js";
|
||||
import type { SessionRouteLookup, SessionRouteService } from "./sessionService.js";
|
||||
import { registerSessionRoutes } from "./sessionRoutes.js";
|
||||
import type { NormalizedSessionCleanupRequest } from "./sessionCleanup.js";
|
||||
|
||||
@@ -39,7 +40,7 @@ describe("session routes", () => {
|
||||
const routeApp = Fastify({ logger: false });
|
||||
await routeApp.register(fastifyWebsocket);
|
||||
const eventHub = new SessionEventHub();
|
||||
const routeService = new CapturingRouteSessionService(eventHub);
|
||||
const routeService = new CapturingRouteSessionService();
|
||||
registerSessionRoutes(routeApp, routeService, eventHub);
|
||||
|
||||
try {
|
||||
@@ -59,7 +60,7 @@ describe("session routes", () => {
|
||||
const routeApp = Fastify({ logger: false });
|
||||
await routeApp.register(fastifyWebsocket);
|
||||
const eventHub = new SessionEventHub();
|
||||
const routeService = new CapturingRouteSessionService(eventHub);
|
||||
const routeService = new CapturingRouteSessionService();
|
||||
registerSessionRoutes(routeApp, routeService, eventHub);
|
||||
|
||||
const attachments = [{ kind: "image", mimeType: "image/png", data: "QUJD", name: "shot.png" }];
|
||||
@@ -81,7 +82,7 @@ describe("session routes", () => {
|
||||
const routeApp = Fastify({ logger: false });
|
||||
await routeApp.register(fastifyWebsocket);
|
||||
const eventHub = new SessionEventHub();
|
||||
const routeService = new CapturingRouteSessionService(eventHub);
|
||||
const routeService = new CapturingRouteSessionService();
|
||||
registerSessionRoutes(routeApp, routeService, eventHub);
|
||||
|
||||
try {
|
||||
@@ -104,7 +105,7 @@ describe("session routes", () => {
|
||||
const routeApp = Fastify({ logger: false });
|
||||
await routeApp.register(fastifyWebsocket);
|
||||
const eventHub = new SessionEventHub();
|
||||
const routeService = new CapturingRouteSessionService(eventHub);
|
||||
const routeService = new CapturingRouteSessionService();
|
||||
registerSessionRoutes(routeApp, routeService, eventHub);
|
||||
|
||||
try {
|
||||
@@ -124,7 +125,7 @@ describe("session routes", () => {
|
||||
const routeApp = Fastify({ logger: false });
|
||||
await routeApp.register(fastifyWebsocket);
|
||||
const eventHub = new SessionEventHub();
|
||||
const routeService = new CapturingRouteSessionService(eventHub);
|
||||
const routeService = new CapturingRouteSessionService();
|
||||
routeService.reloadError = new Error("Stop current session activity before reloading");
|
||||
registerSessionRoutes(routeApp, routeService, eventHub);
|
||||
|
||||
@@ -143,7 +144,7 @@ describe("session routes", () => {
|
||||
const routeApp = Fastify({ logger: false });
|
||||
await routeApp.register(fastifyWebsocket);
|
||||
const eventHub = new SessionEventHub();
|
||||
const routeService = new CapturingRouteSessionService(eventHub);
|
||||
const routeService = new CapturingRouteSessionService();
|
||||
registerSessionRoutes(routeApp, routeService, eventHub);
|
||||
|
||||
try {
|
||||
@@ -164,7 +165,7 @@ describe("session routes", () => {
|
||||
const routeApp = Fastify({ logger: false });
|
||||
await routeApp.register(fastifyWebsocket);
|
||||
const eventHub = new SessionEventHub();
|
||||
const routeService = new CapturingRouteSessionService(eventHub);
|
||||
const routeService = new CapturingRouteSessionService();
|
||||
registerSessionRoutes(routeApp, routeService, eventHub);
|
||||
|
||||
try {
|
||||
@@ -180,34 +181,38 @@ describe("session routes", () => {
|
||||
});
|
||||
});
|
||||
|
||||
class CapturingRouteSessionService extends PiSessionService {
|
||||
class CapturingRouteSessionService implements SessionRouteService {
|
||||
readonly calls: unknown[] = [];
|
||||
readonly reloadCalls: (string | PiSessionRef)[] = [];
|
||||
readonly reloadCalls: SessionRouteLookup[] = [];
|
||||
readonly cleanupPreviewCalls: NormalizedSessionCleanupRequest[] = [];
|
||||
readonly cleanupCalls: NormalizedSessionCleanupRequest[] = [];
|
||||
reloadError: Error | undefined;
|
||||
|
||||
constructor(eventHub: SessionEventHub) {
|
||||
super(eventHub, { sessionManager: new RejectingSessionManager(), heartbeatIntervalMs: 60_000 });
|
||||
}
|
||||
|
||||
override cleanupPreview(request: NormalizedSessionCleanupRequest): Promise<SessionCleanupPreviewResponse> {
|
||||
cleanupPreview(request: NormalizedSessionCleanupRequest): Promise<SessionCleanupPreviewResponse> {
|
||||
this.cleanupPreviewCalls.push(request);
|
||||
return Promise.resolve({ generatedAt: "2026-06-25T00:00:00.000Z", thresholds: request.thresholds, projects: [], totals: { archiveCount: 0, deleteCount: 0 } });
|
||||
}
|
||||
|
||||
override cleanup(request: NormalizedSessionCleanupRequest): Promise<SessionCleanupExecuteResponse> {
|
||||
cleanup(request: NormalizedSessionCleanupRequest): Promise<SessionCleanupExecuteResponse> {
|
||||
this.cleanupCalls.push(request);
|
||||
return Promise.resolve({ generatedAt: "2026-06-25T00:00:00.000Z", thresholds: request.thresholds, projects: [], totals: { archiveCount: 0, deleteCount: 0 }, archivedSessionIds: [], deletedSessionIds: [] });
|
||||
}
|
||||
|
||||
override reload(lookup: string | PiSessionRef): Promise<void> {
|
||||
reload(lookup: SessionRouteLookup): Promise<void> {
|
||||
this.reloadCalls.push(lookup);
|
||||
if (this.reloadError !== undefined) return Promise.reject(this.reloadError);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
override status(lookup: string | PiSessionRef) {
|
||||
dispose(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
list(): never { throw unusedRouteMethod("list"); }
|
||||
start(): never { throw unusedRouteMethod("start"); }
|
||||
messages(): Promise<unknown[]> { return Promise.resolve([]); }
|
||||
|
||||
status(lookup: SessionRouteLookup) {
|
||||
this.calls.push(lookup);
|
||||
return Promise.resolve({
|
||||
sessionId: sessionIdFromLookup(lookup),
|
||||
@@ -221,12 +226,20 @@ class CapturingRouteSessionService extends PiSessionService {
|
||||
});
|
||||
}
|
||||
|
||||
override prompt(lookup: string | PiSessionRef, text: unknown, _streamingBehavior?: unknown, attachments?: unknown): Promise<void> {
|
||||
availableModels(): Promise<[]> { return Promise.resolve([]); }
|
||||
setModel(): never { throw unusedRouteMethod("setModel"); }
|
||||
cycleModel(): never { throw unusedRouteMethod("cycleModel"); }
|
||||
availableThinkingLevels(): Promise<[]> { return Promise.resolve([]); }
|
||||
setThinkingLevel(): never { throw unusedRouteMethod("setThinkingLevel"); }
|
||||
cycleThinkingLevel(): never { throw unusedRouteMethod("cycleThinkingLevel"); }
|
||||
commands(): Promise<[]> { return Promise.resolve([]); }
|
||||
|
||||
prompt(lookup: SessionRouteLookup, text: unknown, _streamingBehavior?: unknown, attachments?: unknown): Promise<void> {
|
||||
this.calls.push(attachments === undefined ? { lookup, text } : { lookup, text, attachments });
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
override saveAttachments(_lookup: string | PiSessionRef, attachments: unknown, folder?: string) {
|
||||
saveAttachments(_lookup: SessionRouteLookup, attachments: unknown, folder?: string) {
|
||||
const list = Array.isArray(attachments) ? attachments : [];
|
||||
return Promise.resolve(list.map((attachment: { mimeType: string; data: string; name?: string }) => ({
|
||||
path: `${folder ?? ".pi-web/attachments"}/${attachment.name ?? "file.png"}`,
|
||||
@@ -234,6 +247,19 @@ class CapturingRouteSessionService extends PiSessionService {
|
||||
size: Buffer.from(attachment.data, "base64").byteLength,
|
||||
})));
|
||||
}
|
||||
|
||||
shell(): never { throw unusedRouteMethod("shell"); }
|
||||
runCommand(): never { throw unusedRouteMethod("runCommand"); }
|
||||
respondToCommand(): never { throw unusedRouteMethod("respondToCommand"); }
|
||||
abort(): never { throw unusedRouteMethod("abort"); }
|
||||
stop(): never { throw unusedRouteMethod("stop"); }
|
||||
archive(): never { throw unusedRouteMethod("archive"); }
|
||||
archiveTree(): never { throw unusedRouteMethod("archiveTree"); }
|
||||
restore(): never { throw unusedRouteMethod("restore"); }
|
||||
deleteArchived(): never { throw unusedRouteMethod("deleteArchived"); }
|
||||
|
||||
|
||||
detachParent(): never { throw unusedRouteMethod("detachParent"); }
|
||||
}
|
||||
|
||||
class RejectingSessionManager implements PiSessionManagerGateway {
|
||||
@@ -260,6 +286,10 @@ class RejectingSessionManager implements PiSessionManagerGateway {
|
||||
}
|
||||
}
|
||||
|
||||
function sessionIdFromLookup(lookup: string | PiSessionRef): string {
|
||||
function sessionIdFromLookup(lookup: SessionRouteLookup): string {
|
||||
return typeof lookup === "string" ? lookup : lookup.id;
|
||||
}
|
||||
|
||||
function unusedRouteMethod(name: string): Error {
|
||||
return new Error(`Route test did not expect ${name} to be called`);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user