Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
716 changes: 705 additions & 11 deletions apps/api/src/__snapshots__/openapi-snapshot.test.ts.snap

Large diffs are not rendered by default.

32 changes: 28 additions & 4 deletions apps/api/src/endpoints.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1381,8 +1381,21 @@ describe("API Endpoints", () => {

describe("GET /api/v1/report-templates", () => {
it("returns 200 with array of report templates", async () => {
const templates = [{ id: "rt1", name: "Default", userPrompt: "Analyze", createdAt: new Date() }];
const templates = [{
_id: "rt1",
projectId: TEST_PROJECT_ID,
id: "rt1",
subject: "run",
name: "Default",
userPrompt: "Analyze",
enabled: true,
currentVersionId: "rt1:v1",
currentVersion: 1,
contentFingerprint: "fingerprint",
createdAt: new Date(),
}];
(mocks.reportTemplateCollection.find as any).mockReturnValue({
sort: vi.fn().mockReturnThis(),
toArray: vi.fn().mockResolvedValue(templates),
});

Expand All @@ -1400,8 +1413,14 @@ describe("API Endpoints", () => {
const seededTemplate = {
projectId: TEST_PROJECT_ID,
id: TID,
_id: TID,
subject: "run",
name: "Seed",
userPrompt: "Analyze",
enabled: true,
currentVersionId: `${TID}:v1`,
currentVersion: 1,
contentFingerprint: "fingerprint",
createdAt: new Date(),
};

Expand All @@ -1428,16 +1447,21 @@ describe("API Endpoints", () => {

it("PUT /:id?projectId scopes both the existence check and the update to { projectId, id }", async () => {
(mocks.reportTemplateCollection.findOne as any).mockResolvedValue(seededTemplate);
(mocks.reportTemplateCollection.findOneAndUpdate as any).mockResolvedValue({
...seededTemplate,
name: "Renamed",
});
const res = await request(testServer())
.put(`/api/v1/report-templates/${TID}?projectId=${TEST_PROJECT_ID}`)
.send({ name: "Renamed" });
expect(res.status).toBe(200);
expect(mocks.reportTemplateCollection.findOne).toHaveBeenCalledWith(
expect.objectContaining({ projectId: TEST_PROJECT_ID, id: TID })
);
expect(mocks.reportTemplateCollection.updateOne).toHaveBeenCalledWith(
expect.objectContaining({ projectId: TEST_PROJECT_ID, id: TID }),
expect.anything()
expect(mocks.reportTemplateCollection.findOneAndUpdate).toHaveBeenCalledWith(
expect.objectContaining({ projectId: TEST_PROJECT_ID }),
expect.anything(),
{ returnDocument: "after" },
);
});

Expand Down
160 changes: 160 additions & 0 deletions apps/api/src/experiment-coordinator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,166 @@ afterEach(() => {
});

describe("ExperimentCoordinator", () => {
it("queues ready Experiment reports independently from unused linked templates", async () => {
const requestState: ExperimentRequestState = {
requestId: "request-1",
runId: "run-1",
dependencies: [],
blockers: [],
};
const initial = experiment([requestState], {
handlerSummary: {
"pp-readout": {
status: "done",
version: 1,
updatedAt: now,
},
},
reportTemplates: {},
report: {
reportId: "readout-1",
url: "/api/v1/reports/readout-1",
updatedAt: now,
},
});
initial.revision.reportTemplateIds = [];
const always = {
_id: "always",
projectId: "project-1",
id: "always",
subject: "experiment",
name: "Always",
userPrompt: "Summarize",
trigger: { type: "always" as const },
enabled: true,
currentVersionId: "always:v1",
currentVersion: 1,
contentFingerprint: "always-fingerprint",
createdAt: now,
};
const linked = {
...always,
_id: "linked",
id: "linked",
name: "Linked",
trigger: {
type: "run-report-templates" as const,
reportTemplateIds: ["security"],
},
currentVersionId: "linked:v1",
contentFingerprint: "linked-fingerprint",
};
const insertOne = vi.fn().mockResolvedValue({ acknowledged: true });
const enqueue = vi.fn().mockResolvedValue(undefined);
vi.spyOn(
ExperimentStore.prototype,
"updateReportTemplateSummaries",
).mockImplementation(async (input) => {
const next = structuredClone(initial);
next.execution.version = 2;
next.execution.reportTemplates = input.summaries;
return next;
});
const coordinator = new ExperimentCoordinator(
{
requests: {} as Collection<RequestDocument>,
experiments: {} as Collection<ExperimentDocument>,
executions: {} as Collection<ExperimentExecutionHistoryDocument>,
chunks: {} as Collection,
services: {
find: vi.fn().mockReturnValue(
cursor([
{
_id: "pp-readout",
type: "experiment-handler",
version: 1,
queue: "queue-readout",
selector: "readout",
autoBackfill: false,
dependsOn: [],
dependencyMode: "all-runs",
},
]),
),
} as unknown as Collection<ServiceHandlerDocument>,
reportTemplates: {
find: vi.fn().mockReturnValue(cursor([always, linked])),
} as unknown as Collection,
reportTemplateVersions: {} as Collection,
reports: {
insertOne,
findOne: vi.fn(),
} as unknown as Collection,
},
enqueue,
);
const request = {
_id: "request-1",
projectId: "project-1",
scenario: { task: "task", criteria: [] },
workerType: "worker",
createdAt: now,
taskPromptId: "prompt",
priority: 0,
run: {
_id: "run-1",
attemptNumber: 1,
status: "done",
logsUrl: "https://logs",
reportTemplateIds: [],
},
} as RequestDocument;

const dispatched = await (
coordinator as unknown as {
dispatchReadyReportTemplates(
experiment: ExperimentDocument,
requests: readonly ExperimentRequestState[],
requestById: ReadonlyMap<string, RequestDocument>,
): Promise<boolean>;
}
).dispatchReadyReportTemplates(
initial,
[requestState],
new Map([[request._id, request]]),
);

expect(dispatched).toBe(true);
expect(insertOne).toHaveBeenCalledTimes(1);
expect(insertOne).toHaveBeenCalledWith(
expect.objectContaining({
_id: expect.stringMatching(/^experiment-report-/),
templateId: "always",
templateVersionId: "always:v1",
reportVersion: 1,
}),
);
expect(
vi.mocked(ExperimentStore.prototype.updateReportTemplateSummaries).mock
.calls[0]?.[0].summaries,
).toMatchObject({
always: { status: "queued", blockers: [] },
linked: {
status: "not-applicable",
blockers: [
{
runTemplateId: "security",
reason: "run-template-unused",
},
],
},
});
expect(enqueue).toHaveBeenCalledTimes(1);
expect(enqueue).toHaveBeenCalledWith(
expect.objectContaining({ _id: "pp-readout" }),
expect.objectContaining({
type: "experiment-report",
templateId: "always",
templateVersionId: "always:v1",
}),
);
});

it("projects authoritative run status and dispatches an exact execution message", async () => {
const initialRequest: ExperimentRequestState = {
requestId: "request-1",
Expand Down
Loading
Loading