fix: align runtime startup and model events
This commit is contained in:
@@ -6,7 +6,7 @@ afterEach(async () => Promise.all(apps.splice(0).map((app) => app.close())));
|
||||
|
||||
describe("model API", () => {
|
||||
it("configures and returns only redacted model metadata", async () => {
|
||||
const { app, modelStore } = createTestApp();
|
||||
const { app, modelStore, eventStore } = createTestApp();
|
||||
apps.push(app);
|
||||
const configured = await app.inject({ method: "PUT", url: "/api/sessions/ses_1/model-config", payload: {
|
||||
providerId: "training", api: "openai-responses", apiKey: "sk-super-secret",
|
||||
@@ -21,6 +21,10 @@ describe("model API", () => {
|
||||
const body = response.json<{ modelConfig: { keyHint: string }; requestId: string }>();
|
||||
expect(body.modelConfig.keyHint).toBe("••••cret");
|
||||
expect(body.requestId).toMatch(/^req_/);
|
||||
expect(eventStore.listAfter("ses_1").events.at(-1)).toMatchObject({
|
||||
type: "model.configured",
|
||||
payload: { providerId: "training", modelId: "training-model", supportsImages: true, source: "session" },
|
||||
});
|
||||
});
|
||||
|
||||
it("clears credentials idempotently and returns an honest deferred connection test", async () => {
|
||||
@@ -36,4 +40,21 @@ describe("model API", () => {
|
||||
expect(body.requestId).toMatch(/^req_/);
|
||||
expect(tested.body).not.toContain("server-only-secret");
|
||||
});
|
||||
|
||||
it("emits a safe validation event when model configuration fails", async () => {
|
||||
const { app, modelStore, eventStore } = createTestApp();
|
||||
apps.push(app);
|
||||
modelStore.configure.mockRejectedValueOnce(new Error("provider leaked sk-super-secret"));
|
||||
|
||||
const response = await app.inject({ method: "PUT", url: "/api/sessions/ses_1/model-config", payload: {
|
||||
providerId: "training", api: "openai-responses", apiKey: "sk-super-secret",
|
||||
modelId: "training-model", supportsImages: true,
|
||||
} });
|
||||
|
||||
expect(response.statusCode).toBe(500);
|
||||
const serialized = JSON.stringify(eventStore.listAfter("ses_1").events);
|
||||
expect(serialized).toContain("model.validation_failed");
|
||||
expect(serialized).not.toContain("sk-super-secret");
|
||||
expect(serialized).not.toContain("provider leaked");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HarnessError } from "@agent-studio/harness";
|
||||
import { HarnessError, InMemoryEventStore } from "@agent-studio/harness";
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import { vi } from "vitest";
|
||||
import { buildApp } from "../src/app.js";
|
||||
@@ -14,6 +14,7 @@ export function createTestApp(): {
|
||||
};
|
||||
attachmentStore: { add: ReturnType<typeof vi.fn>; remove: ReturnType<typeof vi.fn> };
|
||||
approvalBroker: { approve: ReturnType<typeof vi.fn>; deny: ReturnType<typeof vi.fn> };
|
||||
eventStore: InMemoryEventStore;
|
||||
} {
|
||||
const session = {
|
||||
sessionId: "ses_1",
|
||||
@@ -57,15 +58,17 @@ export function createTestApp(): {
|
||||
approve: vi.fn((approvalId: string) => ({ approvalId, status: "approved" as const })),
|
||||
deny: vi.fn((approvalId: string) => ({ approvalId, status: "denied" as const })),
|
||||
};
|
||||
const eventStore = new InMemoryEventStore();
|
||||
const app = buildApp({
|
||||
sessionRegistry: registry,
|
||||
skillCatalog: { list: () => [{ name: "ads-analysis", description: "Analyze", source: "project", filePath: ".agents/skills/ads-analysis/SKILL.md", diagnostics: [] }] },
|
||||
modelStore,
|
||||
attachmentStore,
|
||||
approvalBroker,
|
||||
eventStore,
|
||||
testModelConnection: () => Promise.resolve({ status: "deferred", message: "Saved; validation will occur on first run." }),
|
||||
});
|
||||
return { app, session, modelStore, attachmentStore, approvalBroker };
|
||||
return { app, session, modelStore, attachmentStore, approvalBroker, eventStore };
|
||||
}
|
||||
|
||||
export function makeSessionBusy(session: { startRun: ReturnType<typeof vi.fn> }): void {
|
||||
|
||||
Reference in New Issue
Block a user