Files
next-tjwater-agent/tests/sessions/metadataStore.test.ts
T
jiang 6a49d2209e feat(agent): unify MCP tools and adopt Responses delivery
Move project and host queries into native MCP with host-owned OAuth/context,
while retaining memory, skills, actions and publication plugins. Add nearby-model
queries and explicit chat/task/learning model defaults.

Adopt Responses HTTP/SSE and native steer/queue inbox delivery, preserving scoped
session ownership, pending input edits and investigation communication.

Disable local-provider discovery: Server /health allowed native vLLM to probe
/v1/models on port 8000. A native before/after regression prevents recurrence.

Validation: 170 focused Bun tests; service/plugin typechecks; native discovery,
provider-discovery and input-inbox smoke checks; no paid model inference.
2026-10-19 18:46:58 +08:00

183 lines
6.3 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from "bun:test";
import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
SessionContextMismatchError,
SessionMetadataStore,
} from "../../src/sessions/metadataStore.js";
describe("SessionMetadataStore", () => {
let tempDir: string;
let store: SessionMetadataStore;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "tjwater-session-"));
store = new SessionMetadataStore(tempDir);
await store.initialize();
});
it("does not return or ensure a session owned by another context", async () => {
await store.ensure({
actorKey: "actor-1",
projectId: "project-1",
projectKey: "project-key-1",
sessionId: "shared-id",
userId: "user-1",
});
expect(
await store.get(
{
actorKey: "actor-2",
projectId: "project-2",
projectKey: "project-key-2",
userId: "user-2",
},
"shared-id",
),
).toBeNull();
expect(
store.ensure({
actorKey: "actor-2",
projectId: "project-2",
projectKey: "project-key-2",
sessionId: "shared-id",
userId: "user-2",
}),
).rejects.toBeInstanceOf(SessionContextMismatchError);
});
afterEach(async () => {
await rm(tempDir, { force: true, recursive: true });
});
it("persists the provided opencode session id", async () => {
const { record, created } = await store.ensure({
actorKey: "actor-1",
projectId: "project-1",
projectKey: "project-key-1",
sessionId: "opencode-session-1",
userId: "user-1",
});
expect(created).toBe(true);
expect(record.sessionId).toBe("opencode-session-1");
expect(record.ownerUserId).toBe("user-1");
expect(record.status).toBe("active");
});
it("touches metadata and preserves scoped ownership", async () => {
const { record } = await store.ensure({
actorKey: "actor-2",
projectId: "project-2",
projectKey: "project-key-2",
sessionId: "existing-session",
userId: "user-2",
});
const touched = await store.touch(record, {
title: "新标题",
});
expect(touched.title).toBe("新标题");
expect(touched.updatedAt >= record.updatedAt).toBe(true);
const fetched = await store.get(
{
actorKey: "actor-2",
projectId: "project-2",
projectKey: "project-key-2",
userId: "user-2",
},
"existing-session",
);
expect(fetched?.title).toBe("新标题");
});
it("finds a triggered session only inside the owning context", async () => {
const context = {
actorKey: "actor-trigger",
projectId: "project-trigger",
projectKey: "project-key-trigger",
userId: "user-trigger",
};
const { record } = await store.ensure({
...context,
sessionId: "triggered-session",
trigger: {
triggerId: "peripheral.work_order.created:project:order:1",
source: "peripheral_sample",
subjectType: "work_order",
subjectId: "50000000-0000-4000-8000-000000000001",
workflow: "water_quality_complaint",
occurredAt: "2026-09-22T10:00:00+08:00",
},
});
expect(
await store.findByTrigger(
context,
"peripheral.work_order.created:project:order:1",
),
).toEqual(record);
expect(
await store.findByTrigger(
{ ...context, actorKey: "other-actor", userId: "other-user" },
"peripheral.work_order.created:project:order:1",
),
).toBeNull();
});
it("keeps ordinary chat, task execution, and task collaboration distinct", async () => {
const context = {
actorKey: "actor-purpose",
projectId: "project-purpose",
projectKey: "project-key-purpose",
userId: "user-purpose",
};
await store.ensure({ ...context, sessionId: "ordinary" });
await store.ensure({
...context,
sessionId: "execution",
purpose: "task_execution",
taskId: "50000000-0000-4000-8000-000000000001",
requestId: "60000000-0000-4000-8000-000000000001",
});
await store.ensure({
...context,
sessionId: "collaboration",
purpose: "task_collaboration",
taskId: "50000000-0000-4000-8000-000000000001",
});
expect((await store.list(context, { purpose: "ordinary_chat" })).map((item) => item.sessionId)).toEqual(["ordinary"]);
expect((await store.list(context, { purpose: "task_execution" })).map((item) => item.sessionId)).toEqual(["execution"]);
expect((await store.findByTask(context, "50000000-0000-4000-8000-000000000001", "task_collaboration"))?.sessionId).toBe("collaboration");
expect((await store.findByRequest(context, "60000000-0000-4000-8000-000000000001"))?.sessionId).toBe("execution");
});
it("updates its scoped index after writes and deletion without rereading unrelated records", async () => {
const context = { actorKey: "actor-a", projectKey: "project-a", userId: "user-a" };
const { record } = await store.ensure({ ...context, sessionId: "first" });
expect((await store.list(context)).map((item) => item.sessionId)).toEqual(["first"]);
// A listing already built the index; subsequent calls must not rescan files.
await writeFile(join(tempDir, "unrelated.json"), "not valid JSON");
await store.touch(record, { title: "updated title" });
const listed = await store.list(context);
expect(listed[0]?.title).toBe("updated title");
listed[0]!.actorKey = "other-actor";
expect((await store.list(context))[0]?.actorKey).toBe("actor-a");
await store.ensure({ ...context, sessionId: "second" });
expect((await store.list(context)).map((item) => item.sessionId).sort()).toEqual(["first", "second"]);
await store.remove(record);
expect((await store.list(context)).map((item) => item.sessionId)).toEqual(["second"]);
expect(await store.get(context, "first")).toBeNull();
expect((await store.get(context, "first", { includeDeleted: true }))?.deletedAt).toBeDefined();
expect(await store.get({ ...context, userId: "user-b" }, "first", { includeDeleted: true })).toBeNull();
await expect(store.touch(record, { title: "revived" })).rejects.toThrow("session not found");
await expect(store.ensure({ ...context, sessionId: "first" })).rejects.toThrow("session not found");
});
});