Imported as a linear vendor commit so forge rebases do not replay the upstream root tree. git-subtree-dir: third_party/pi git-subtree-split: d981de1229ef899957bbe968bc8dcda02a21f477
140 lines
5.4 KiB
TypeScript
140 lines
5.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { BACKGROUND_CONTEXT } from "../../src/harness/context.ts";
|
|
import * as sessionWrites from "../../src/harness/session/commit.ts";
|
|
import { MemorySessionRepo } from "../../src/harness/session/index.ts";
|
|
import * as storedValues from "../../src/harness/session/values.ts";
|
|
|
|
const NOW = 1_700_000_000_000;
|
|
function uuidTimestamp(id: string): number {
|
|
return Number.parseInt(id.replaceAll("-", "").slice(0, 12), 16);
|
|
}
|
|
|
|
describe("MemorySessionRepo metadata", () => {
|
|
it("uses its injected clock for generated session identity and metadata", async () => {
|
|
const repo = new MemorySessionRepo({ now: () => NOW });
|
|
const session = await repo.create({}, BACKGROUND_CONTEXT);
|
|
|
|
expect(session.metadata.createdAt).toBe(NOW);
|
|
expect(uuidTimestamp(session.metadata.id)).toBe(NOW);
|
|
await Promise.all([session.close(BACKGROUND_CONTEXT), repo.close(BACKGROUND_CONTEXT)]);
|
|
});
|
|
|
|
it("returns a fresh facade after close while retaining one session and storage", async () => {
|
|
const repo = new MemorySessionRepo({ now: () => NOW });
|
|
const first = await repo.create({ id: "session" }, BACKGROUND_CONTEXT);
|
|
const firstBranch = await first.createBranch("main", null, BACKGROUND_CONTEXT);
|
|
const admittedWrite = first.setName("preserved", BACKGROUND_CONTEXT);
|
|
|
|
await expect(repo.open(first.metadata, BACKGROUND_CONTEXT)).rejects.toThrow("already open");
|
|
await Promise.all([admittedWrite, first.close(BACKGROUND_CONTEXT)]);
|
|
await expect(first.getName(BACKGROUND_CONTEXT)).rejects.toThrow("Session is closed");
|
|
await expect(first.scanBranch({ start: "entry" }, BACKGROUND_CONTEXT)).rejects.toThrow("Session is closed");
|
|
await expect(firstBranch.getTipId(BACKGROUND_CONTEXT)).rejects.toThrow("Session is closed");
|
|
|
|
const second = await repo.open(first.metadata, BACKGROUND_CONTEXT);
|
|
expect(second).not.toBe(first);
|
|
expect(await second.getName(BACKGROUND_CONTEXT)).toBe("preserved");
|
|
await second.close(BACKGROUND_CONTEXT);
|
|
await repo.close(BACKGROUND_CONTEXT);
|
|
});
|
|
|
|
it("waits for an explicit mutation before closing its facade", async () => {
|
|
const repo = new MemorySessionRepo({ now: () => NOW });
|
|
const session = await repo.create({ id: "session" }, BACKGROUND_CONTEXT);
|
|
const mutation = await session.beginMutation(BACKGROUND_CONTEXT);
|
|
let closed = false;
|
|
const closing = session.close(BACKGROUND_CONTEXT).then(() => {
|
|
closed = true;
|
|
});
|
|
|
|
await Promise.resolve();
|
|
expect(closed).toBe(false);
|
|
await mutation.end(BACKGROUND_CONTEXT);
|
|
await closing;
|
|
expect(closed).toBe(true);
|
|
|
|
const reopened = await repo.open(session.metadata, BACKGROUND_CONTEXT);
|
|
await Promise.all([reopened.close(BACKGROUND_CONTEXT), repo.close(BACKGROUND_CONTEXT)]);
|
|
});
|
|
|
|
it("rejects an explicit scope that had not acquired before facade close", async () => {
|
|
const repo = new MemorySessionRepo({ now: () => NOW });
|
|
const session = await repo.create({ id: "session" }, BACKGROUND_CONTEXT);
|
|
const first = await session.beginMutation(BACKGROUND_CONTEXT);
|
|
const secondPromise = session.beginMutation(BACKGROUND_CONTEXT);
|
|
void secondPromise.catch(() => {});
|
|
let closed = false;
|
|
const closing = session.close(BACKGROUND_CONTEXT).then(() => {
|
|
closed = true;
|
|
});
|
|
|
|
await first.end(BACKGROUND_CONTEXT);
|
|
await expect(secondPromise).rejects.toThrow("Session is closed");
|
|
await closing;
|
|
expect(closed).toBe(true);
|
|
|
|
const reopened = await repo.open(session.metadata, BACKGROUND_CONTEXT);
|
|
await Promise.all([reopened.close(BACKGROUND_CONTEXT), repo.close(BACKGROUND_CONTEXT)]);
|
|
});
|
|
|
|
it("captures fork options before waiting for its snapshot boundary", async () => {
|
|
const repo = new MemorySessionRepo({ now: () => NOW });
|
|
const source = await repo.create({ id: "source" }, BACKGROUND_CONTEXT);
|
|
const rootId = "00000000-0000-7000-8000-000000000001";
|
|
const childId = "00000000-0000-7000-8000-000000000002";
|
|
const mutation = await source.beginMutation(BACKGROUND_CONTEXT);
|
|
const commit = mutation.commit(
|
|
[
|
|
sessionWrites.insertEntry({
|
|
id: rootId,
|
|
parentId: null,
|
|
type: "custom",
|
|
customType: "root",
|
|
}),
|
|
sessionWrites.insertEntry({
|
|
id: childId,
|
|
parentId: rootId,
|
|
type: "custom",
|
|
customType: "child",
|
|
}),
|
|
storedValues.setValue(storedValues.branchTip("main"), childId),
|
|
storedValues.setValue(storedValues.laneConfig("main"), {
|
|
model: { provider: "test", modelId: "test" },
|
|
thinkingLevel: "off",
|
|
activeToolNames: [],
|
|
}),
|
|
storedValues.setValue(storedValues.laneState("main"), {
|
|
currentOperationId: null,
|
|
lastOperationId: null,
|
|
inbox: [],
|
|
}),
|
|
],
|
|
BACKGROUND_CONTEXT,
|
|
);
|
|
const options = {
|
|
id: "fork",
|
|
scope: "branch" as const,
|
|
branch: "main",
|
|
entryId: childId,
|
|
position: "before" as "before" | "at",
|
|
};
|
|
const fork = repo.fork(source.metadata, options, BACKGROUND_CONTEXT);
|
|
options.entryId = rootId;
|
|
options.position = "at";
|
|
|
|
await commit;
|
|
await mutation.end(BACKGROUND_CONTEXT);
|
|
await expect(
|
|
source.scanBranch({ start: childId, order: "oldestFirst" }, BACKGROUND_CONTEXT),
|
|
).resolves.toMatchObject([{ id: rootId }, { id: childId }]);
|
|
const forked = await fork;
|
|
const forkedMain = await forked.branch("main", BACKGROUND_CONTEXT);
|
|
expect(await forkedMain?.getTipId(BACKGROUND_CONTEXT)).toBe(rootId);
|
|
await Promise.all([
|
|
source.close(BACKGROUND_CONTEXT),
|
|
forked.close(BACKGROUND_CONTEXT),
|
|
repo.close(BACKGROUND_CONTEXT),
|
|
]);
|
|
});
|
|
});
|