import { describe, expect, it } from "bun:test";
import * as Y from "yjs";
import * as Room from "./room";
import * as Service from "./service";
import { hosted } from "./conversation-persistence.test-fixtures";
import { pendingInlineResearch } from "./research-placement.test-fixtures";
import { ulid } from "@chopin/dialect";
import { socket, submitResearchUpdate } from "./projection-submit.test-fixtures";
import { insertResearchProjection } from "./research-projection.test-fixtures";
import { linkedResearchJob, setResearchJobState } from "./research-projection-jobs.test-fixtures";
describe("hosted plan persistence", () => {
it("accepts a browser-placed Research reference for a request with active work", async () => {
let context = await hosted();
let plan = await Service.open(context.channel.id, context.backend, context.server);
// ResearchWorkspaceService mints UUID request ids, not ULIDs.
let request = await pendingInlineResearch(context, context.channel.id, crypto.randomUUID());
await linkedResearchJob(context, request.workspace.id, "evidence");
let peer = await Room.restore(
plan.document.epoch,
Y.encodeStateAsUpdate(plan.document.doc),
Service.source(plan),
[],
);
try {
let update = await insertResearchProjection(peer, request.workspace.id);
let replies = await submitResearchUpdate(context, plan, update, "uuid-research-insert");
expect(replies.some(frame => frame.kind === "plan:ack")).toBe(true);
expect(context.frames.some(frame => frame.kind === "plan:reset")).toBe(false);
expect(Service.source(plan)).toContain(``);
} finally {
peer.doc.destroy();
await Service.close(plan);
}
});
it("rejects a Research projection for an inline request with no linked work", async () => {
let context = await hosted();
let plan = await Service.open(context.channel.id, context.backend, context.server);
let originalSource = Service.source(plan);
let request = await pendingInlineResearch(context);
let peer = await Room.restore(
plan.document.epoch,
Y.encodeStateAsUpdate(plan.document.doc),
originalSource,
[],
);
try {
let update = await insertResearchProjection(peer, request.workspace.id);
let replies = await submitResearchUpdate(
context,
plan,
update,
"unlinked-research-projection",
);
expect(replies.some(frame => frame.kind === "plan:ack")).toBe(false);
expect(context.frames.some(frame => frame.kind === "plan:reset")).toBe(true);
expect(Service.source(plan)).toBe(originalSource);
let stored = await context.storage.research.get(context.channel.id, request.workspace.id);
expect(stored?.workspace.origin).toBe("inline");
expect(stored?.turns[0]?.kind).toBe("initial");
expect(stored?.turns[0]?.evidenceJobId).toBeUndefined();
expect(stored?.turns[0]?.answerJobId).toBeUndefined();
} finally {
peer.doc.destroy();
await Service.close(plan);
}
});
it("rejects a Research projection backed by a request from another channel", async () => {
let context = await hosted();
let otherChannel = await context.storage.channels.create({
id: ulid(),
repositoryId: "R_score",
repositoryOwner: "octo-org",
repositoryName: "score",
title: "Other document",
createdBy: "U_octocat",
now: context.now,
});
let plan = await Service.open(context.channel.id, context.backend, context.server);
let originalSource = Service.source(plan);
let request = await pendingInlineResearch(context, otherChannel.id);
await linkedResearchJob(context, request.workspace.id, "evidence", otherChannel.id);
let peer = await Room.restore(
plan.document.epoch,
Y.encodeStateAsUpdate(plan.document.doc),
originalSource,
[],
);
try {
let update = await insertResearchProjection(peer, request.workspace.id);
let replies: Array> = [];
Service.submit(plan, socket(context, replies), {
kind: "plan:update",
ts: 0,
rid: "foreign-research-projection",
id: "foreign-research-projection",
epoch: plan.document.epoch,
update: Buffer.from(update).toString("base64"),
});
await Bun.sleep(20);
await plan.flushing;
expect(replies.some(frame => frame.kind === "plan:ack")).toBe(false);
expect(context.frames.some(frame => frame.kind === "plan:reset")).toBe(true);
expect(Service.source(plan)).toBe(originalSource);
expect(await context.storage.research.get(context.channel.id, request.workspace.id))
.toBeUndefined();
} finally {
peer.doc.destroy();
await Service.close(plan);
}
});
it("rejects inserting a stale Research reference after its linked request failed", async () => {
let context = await hosted();
let plan = await Service.open(context.channel.id, context.backend, context.server);
let originalSource = Service.source(plan);
let request = await pendingInlineResearch(context);
let job = await linkedResearchJob(context, request.workspace.id, "evidence");
await setResearchJobState(context, job.id, "failed");
let peer = await Room.restore(
plan.document.epoch,
Y.encodeStateAsUpdate(plan.document.doc),
originalSource,
[],
);
try {
let update = await insertResearchProjection(peer, request.workspace.id);
let replies = await submitResearchUpdate(context, plan, update, "stale-research-insert");
expect(replies.some(frame => frame.kind === "plan:ack")).toBe(false);
expect(context.frames.some(frame => frame.kind === "plan:reset")).toBe(true);
expect(Service.source(plan)).toBe(originalSource);
} finally {
peer.doc.destroy();
await Service.close(plan);
}
});
});