Files
pikasTech-unidesk/scripts/code-queue-unread-triage-contract-test.ts
T
2026-05-24 05:12:46 +00:00

164 lines
9.3 KiB
TypeScript

import { codexUnreadTriageForTest } from "./src/code-queue";
type JsonRecord = Record<string, unknown>;
type RequestRecord = { path: string; method: string; body: unknown };
function assertCondition(condition: unknown, message: string, detail: JsonRecord = {}): void {
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
}
function asRecord(value: unknown): JsonRecord {
assertCondition(typeof value === "object" && value !== null && !Array.isArray(value), "expected JSON object", { value });
return value as JsonRecord;
}
function asArray(value: unknown): unknown[] {
assertCondition(Array.isArray(value), "expected JSON array", { value });
return value as unknown[];
}
function task(id: string, queueId: string, status: string, updatedAt: string, readAt: string | null, prompt: string): JsonRecord {
return {
id,
queueId,
status,
currentAttempt: 1,
updatedAt,
finishedAt: status === "running" ? null : updatedAt,
readAt,
displayPrompt: prompt,
basePrompt: prompt,
prompt,
cwd: "/root/unidesk",
referenceTaskIds: [],
lastError: null,
};
}
function fixtureResponse(path: string, init?: { method?: string; body?: unknown }, requests: RequestRecord[] = []): JsonRecord {
const method = init?.method ?? "GET";
requests.push({ path, method, body: init?.body });
if (path.endsWith("/read")) {
const taskId = decodeURIComponent(path.split("/api/tasks/")[1]?.split("/")[0] ?? "unknown");
return {
ok: true,
status: 200,
body: {
ok: true,
task: task(taskId, "default", "succeeded", "2026-05-22T00:10:00.000Z", "2026-05-22T00:10:01.000Z", "read response prompt should stay compact"),
queue: { counts: { succeeded: 1 }, unreadTerminal: 0 },
},
};
}
assertCondition(path.startsWith("/api/microservices/code-queue/proxy/api/tasks/overview"), "unexpected path", { path, method });
return {
ok: true,
status: 200,
body: {
ok: true,
queue: { unreadTerminal: 4 },
pagination: {
limit: 200,
returned: 6,
total: 6,
hasMore: false,
nextBeforeId: null,
includeActive: true,
},
tasks: [
task("task-new-1", "default", "succeeded", "2026-05-22T00:05:00.000Z", null, "pikasTech/unidesk#20 closeout RAW_PROMPT_SHOULD_NOT_LEAK"),
task("task-new-2", "review", "failed", "2026-05-22T00:04:00.000Z", null, "https://github.com/pikasTech/unidesk/issues/20 failed task RAW_PROMPT_SHOULD_NOT_LEAK"),
task("task-new-3", "review", "canceled", "2026-05-22T00:03:00.000Z", null, "pikasTech/unidesk#21 canceled task RAW_PROMPT_SHOULD_NOT_LEAK"),
task("task-unknown", "misc", "succeeded", "2026-05-22T00:02:00.000Z", null, "no repo marker RAW_PROMPT_SHOULD_NOT_LEAK"),
task("task-read", "default", "succeeded", "2026-05-22T00:01:00.000Z", "2026-05-22T00:01:01.000Z", "pikasTech/unidesk#20 already read RAW_PROMPT_SHOULD_NOT_LEAK"),
task("task-running", "default", "running", "2026-05-22T00:00:00.000Z", null, "pikasTech/unidesk#20 running RAW_PROMPT_SHOULD_NOT_LEAK"),
],
},
};
}
function countItems(bucket: JsonRecord): JsonRecord[] {
return asArray(bucket.items).map(asRecord);
}
function itemCount(bucket: JsonRecord, key: string): number {
const row = countItems(bucket).find((item) => item.key === key);
return typeof row?.count === "number" ? row.count : 0;
}
export function runCodeQueueUnreadTriageContract(): JsonRecord {
const requests: RequestRecord[] = [];
const fetcher = (path: string, init?: { method?: string; body?: unknown }): JsonRecord => fixtureResponse(path, init, requests);
const summary = codexUnreadTriageForTest(["--limit", "2"], fetcher);
const summaryBody = JSON.stringify(summary);
const triage = asRecord(asRecord(summary).unreadTriage);
const counts = asRecord(triage.counts);
const newest = asRecord(triage.newest);
const newestItems = asArray(newest.items).map(asRecord);
const commands = asRecord(triage.commands);
assertCondition(asRecord(summary).ok === true, "default unread triage should succeed", asRecord(summary));
assertCondition(triage.readOnly === true && triage.bounded === true, "default unread triage must be read-only and bounded", triage);
assertCondition(counts.totalUnreadTerminal === 4, "counts should include only unread terminal tasks", counts);
assertCondition(itemCount(asRecord(counts.byRepo), "pikasTech/unidesk") === 3, "repo counts should group owner/name refs", counts);
assertCondition(itemCount(asRecord(counts.byIssue), "#20") === 2, "issue counts should group issue refs", counts);
assertCondition(itemCount(asRecord(counts.byStatus), "succeeded") === 2, "status counts should include terminal statuses", counts);
assertCondition(itemCount(asRecord(counts.byQueue), "review") === 2, "queue counts should include queues", counts);
assertCondition(newest.returned === 2 && newest.hasMore === true, "newest items should obey --limit and expose pagination", newest);
assertCondition(newestItems.every((item) => item.commands === undefined && typeof item.nextStep === "string"), "default unread rows must stay compact without repeated per-task command blocks", { newestItems });
assertCondition(typeof commands.perTaskRead === "string" && String(commands.perTaskRead).includes("codex read <taskId>"), "triage should preserve per-task read drill-down", commands);
assertCondition(typeof commands.full === "string" && String(commands.full).includes("codex unread") && String(commands.full).includes("--full"), "triage should expose one full-view expansion command", commands);
assertCondition(!summaryBody.includes("RAW_PROMPT_SHOULD_NOT_LEAK"), "triage output must not dump raw prompt text", { summaryBody });
assertCondition(!requests.some((request) => request.path.includes("/summary")), "triage must not fetch per-task summaries by default", { requests });
assertCondition(!requests.some((request) => request.method === "POST"), "default triage must not mutate", { requests });
const full = codexUnreadTriageForTest(["--view", "full", "--limit", "2"], fetcher);
const fullBody = JSON.stringify(full);
const fullTriage = asRecord(asRecord(full).unreadTriage);
const fullNewest = asRecord(fullTriage.newest);
const fullItems = asArray(fullNewest.items).map(asRecord);
const fullItemCommands = fullItems.map((item) => asRecord(item.commands));
assertCondition(asRecord(fullTriage.filters).view === "full", "codex unread --view full should disclose full view", fullTriage);
assertCondition(fullItems.length === 2 && fullItemCommands.every((item) => typeof item.detail === "string" && typeof item.read === "string"), "explicit full unread view should expand per-task commands", { fullItems });
assertCondition(summaryBody.length < fullBody.length, "default unread summary should stay smaller than explicit full view", { summaryChars: summaryBody.length, fullChars: fullBody.length });
const guardStart = requests.length;
const guarded = codexUnreadTriageForTest(["mark-read", "--repo", "pikasTech/unidesk", "--issue", "20", "--limit", "2"], fetcher);
const guardedTriage = asRecord(asRecord(guarded).unreadTriage);
const guardedMutation = asRecord(guardedTriage.mutation);
assertCondition(asRecord(guarded).ok === false, "batch mark-read without --confirm should fail closed", asRecord(guarded));
assertCondition(guardedMutation.blocked === true && guardedMutation.confirmed === false, "confirm guard should describe blocked mutation", guardedMutation);
assertCondition(!requests.slice(guardStart).some((request) => request.method === "POST"), "missing --confirm must not POST read calls", { requests: requests.slice(guardStart) });
const confirmStart = requests.length;
const confirmed = codexUnreadTriageForTest(["mark-read", "--repo", "pikasTech/unidesk", "--issue", "20", "--limit", "2", "--confirm"], fetcher);
const confirmedTriage = asRecord(asRecord(confirmed).unreadTriage);
const confirmedMutation = asRecord(confirmedTriage.mutation);
const readPosts = requests.slice(confirmStart).filter((request) => request.method === "POST" && request.path.endsWith("/read"));
assertCondition(asRecord(confirmed).ok === true, "confirmed batch mark-read should succeed", asRecord(confirmed));
assertCondition(confirmedTriage.readOnly === false && confirmedMutation.confirmed === true, "confirmed mutation should be explicit", confirmedMutation);
assertCondition(confirmedMutation.attempted === 2 && confirmedMutation.succeeded === 2, "confirmed mutation should respect filters and limit", confirmedMutation);
assertCondition(readPosts.length === 2 && readPosts[0]?.path.includes("task-new-1") && readPosts[1]?.path.includes("task-new-2"), "confirmed mutation should POST newest filtered task reads only", { readPosts });
return {
ok: true,
checks: [
"default unread triage is read-only",
"repo/issue/status/queue counts are present",
"newest items are bounded",
"default rows avoid repeated per-task command blocks",
"--view full expands per-task commands",
"raw prompt text is omitted",
"batch mark-read requires --confirm",
"confirmed batch read respects filters and limit",
],
summaryChars: summaryBody.length,
fullChars: fullBody.length,
};
}
if (import.meta.main) {
process.stdout.write(`${JSON.stringify(runCodeQueueUnreadTriageContract(), null, 2)}\n`);
}