mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-04 19:28:46 +01:00
test(evals): preserve distinct same-title task fixtures
This commit is contained in:
1 parent
f6d1e0dec1
commit
151488bf8c
4 files changed
+31
-3
No files matched your search
@@ -0,0 +1,9 @@
|
||||
import { expect, it } from "vitest";
|
||||
import type { ChatCatalogContext } from "../chat-catalog-contract.js";
|
||||
import { verifyDistinctTaskFixtures } from "./internal-distinct-fixtures.js";
|
||||
const originals = () => [{ id: "launch-task", companyId: "company", title: "Landing page", projectId: "launch", status: "backlog" },
|
||||
{ id: "experiment-task", companyId: "company", title: "Landing page", projectId: "experiments", status: "backlog" }];
|
||||
function context(rows = originals()) { return { input: { api: { get: async (path: string) => rows.find(row => path.endsWith(`/${row.id}`)) }, fixtures: { company: { id: "company" } }, evidence: async () => undefined } } as unknown as ChatCatalogContext; }
|
||||
it("accepts genuinely distinct saved same-title tasks in the declared projects", async () => expect(await verifyDistinctTaskFixtures(context(), originals())).toEqual(originals()));
|
||||
it("rejects public-create title reuse before sending the canonical query", async () => { const rows = originals(); rows[1] = rows[0]!; await expect(verifyDistinctTaskFixtures(context(rows), rows)).rejects.toThrow(/aliased/); });
|
||||
it("rejects a saved task in the wrong project or company", async () => { const rows = originals(); rows[1]!.projectId = "launch"; await expect(verifyDistinctTaskFixtures(context(rows), originals())).rejects.toThrow(/project/); const foreign = originals(); foreign[1]!.companyId = "foreign"; await expect(verifyDistinctTaskFixtures(context(foreign), originals())).rejects.toThrow(/identity/); });
|
||||
@@ -0,0 +1,13 @@
|
||||
import type { ChatCatalogContext } from "../chat-catalog-contract.js";
|
||||
import { type Entity, sameTask } from "./internal-support.js";
|
||||
|
||||
/** Same-title tasks are intentional context here, not repeated creation requests. */
|
||||
export async function verifyDistinctTaskFixtures(ctx: ChatCatalogContext, expected: readonly Entity[]) {
|
||||
if (new Set(expected.map(row => row.id)).size !== expected.length) throw new Error("Distinct prerequisite tasks aliased to one public-create result");
|
||||
const saved = await Promise.all(expected.map(row => ctx.input.api.get<Entity>(`/api/issues/${row.id}`)));
|
||||
if (!saved.every((row, index) => row.companyId === ctx.input.fixtures.company.id && sameTask(expected[index]!, row)))
|
||||
throw new Error("Saved prerequisite task identity, title or project differs from the declared context");
|
||||
await ctx.input.evidence("chat-catalog-distinct-prerequisite-tasks.json", { companyId: ctx.input.fixtures.company.id,
|
||||
tasks: saved.map(row => ({ id: row.id, title: row.title, projectId: row.projectId, status: row.status, priority: row.priority })) });
|
||||
return saved;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { verifyDistinctTaskFixtures } from "./internal-distinct-fixtures.js";
|
||||
import type { ChatCatalogCase, ChatCatalogContext } from "../chat-catalog-contract.js";
|
||||
import type { ChatRun, ChatIssue } from "../chat-flow.js";
|
||||
import { dropChatSendAcknowledgement } from "../lost-send.js";
|
||||
@@ -104,7 +105,8 @@ function creationDefinition(scenarioId: string): ChatCatalogCase {
|
||||
if (scenarioId === "projects.project-work") {
|
||||
priorTasks.push(await task(ctx, "Signup copy", p.id));
|
||||
priorTasks.push(await task(ctx, "Product brief", p.id, { status: "done" }));
|
||||
const other = await project(ctx, "Experiments"); priorTasks.push(await task(ctx, "Signup copy", other.id));
|
||||
const other = await project(ctx, "Experiments"); priorTasks.push(await task(ctx, "Signup copy", other.id, { allowDuplicate: true }));
|
||||
await verifyDistinctTaskFixtures(ctx, priorTasks);
|
||||
}
|
||||
}
|
||||
if (scenarioId === "tasks.website-without-project") interception = await dropChatSendAcknowledgement(ctx.input.page, ctx.scenario.query);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { withEditOwnership } from "./internal-edit-ownership.js";
|
||||
import { verifyDistinctTaskFixtures } from "./internal-distinct-fixtures.js";
|
||||
import { delegatedTaskCases } from "./internal-task-creation.js";
|
||||
import { taskPrivacyCases } from "./internal-privacy.js";
|
||||
import { taskInventoryCases } from "./internal-task-inventory.js";
|
||||
@@ -102,10 +103,13 @@ export const tasksCases: readonly ChatCatalogCase[] = [
|
||||
function ambiguousTask(): ChatCatalogCase {
|
||||
const base = define("tasks.ambiguous-task", async ctx => {
|
||||
const p = await project(ctx); const otherProject = await project(ctx, "Experiments");
|
||||
const t = await task(ctx, "Landing page", p.id); const otherTask = await task(ctx, "Landing page", otherProject.id);
|
||||
const t = await task(ctx, "Landing page", p.id);
|
||||
// Public creation defaults to company-wide recent-title reuse, regardless of project.
|
||||
const otherTask = await task(ctx, "Landing page", otherProject.id, { allowDuplicate: true });
|
||||
await verifyDistinctTaskFixtures(ctx, [t, otherTask]);
|
||||
return { project: p, task: t, otherProject, otherTask };
|
||||
});
|
||||
return { ...base, expectedRunCount: 2, expectedHumanMessageCount: 2, attemptTimeoutMs: 420_000,
|
||||
return { ...base, sourceFiles: [...base.sourceFiles, "chat-catalog-domains/internal-distinct-fixtures.ts"], expectedRunCount: 2, expectedHumanMessageCount: 2, attemptTimeoutMs: 420_000,
|
||||
async act(ctx, state) {
|
||||
const first = await pollUntil({ label: "Task ambiguity clarification", deadlineAt: Date.now() + 150_000,
|
||||
load: () => observeInternal(ctx, state), accept: o => o.runs.length === 1 && o.runs.every(row => row.status === "succeeded")
|
||||
|
||||
Reference in new issue
Block a user