166 lines
5.5 KiB
TypeScript
166 lines
5.5 KiB
TypeScript
/**
|
|
* Regression tests for per-agent memory isolation helpers.
|
|
*
|
|
* Addresses review feedback: targeted coverage for auth/session state,
|
|
* malformed input, and the resolveUserId priority chain.
|
|
*/
|
|
import { describe, it, expect } from "vitest";
|
|
import {
|
|
extractAgentId,
|
|
effectiveUserId,
|
|
agentUserId,
|
|
resolveUserId,
|
|
} from "./index.ts";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// extractAgentId
|
|
// ---------------------------------------------------------------------------
|
|
describe("extractAgentId", () => {
|
|
it("returns agentId from a well-formed session key", () => {
|
|
expect(extractAgentId("agent:researcher:550e8400-e29b")).toBe("researcher");
|
|
});
|
|
|
|
it("returns undefined for the 'main' sentinel", () => {
|
|
expect(extractAgentId("agent:main:abc-123")).toBeUndefined();
|
|
});
|
|
|
|
it("returns undefined for undefined/null/empty input", () => {
|
|
expect(extractAgentId(undefined)).toBeUndefined();
|
|
expect(extractAgentId("")).toBeUndefined();
|
|
});
|
|
|
|
it("returns undefined for non-agent session keys", () => {
|
|
expect(extractAgentId("user:alice:xyz")).toBeUndefined();
|
|
expect(extractAgentId("some-random-uuid")).toBeUndefined();
|
|
});
|
|
|
|
it("handles keys with extra colons after the UUID portion", () => {
|
|
expect(extractAgentId("agent:beta:uuid:extra:stuff")).toBe("beta");
|
|
});
|
|
|
|
it("returns undefined when agentId segment is empty", () => {
|
|
// pattern: agent::<uuid> — empty agentId
|
|
expect(extractAgentId("agent::some-uuid")).toBeUndefined();
|
|
});
|
|
|
|
it("returns undefined when key is only 'agent:' with no trailing colon", () => {
|
|
expect(extractAgentId("agent:")).toBeUndefined();
|
|
});
|
|
|
|
it("is case-sensitive (Agent != agent)", () => {
|
|
expect(extractAgentId("Agent:researcher:uuid")).toBeUndefined();
|
|
});
|
|
|
|
it("handles whitespace-only agentId as truthy string", () => {
|
|
// " " is a non-empty match — returned as-is (validation is caller's job)
|
|
expect(extractAgentId("agent: :uuid")).toBe(" ");
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// effectiveUserId
|
|
// ---------------------------------------------------------------------------
|
|
describe("effectiveUserId", () => {
|
|
const base = "alice";
|
|
|
|
it("returns base userId when sessionKey is undefined", () => {
|
|
expect(effectiveUserId(base)).toBe("alice");
|
|
expect(effectiveUserId(base, undefined)).toBe("alice");
|
|
});
|
|
|
|
it("returns namespaced userId for agent session keys", () => {
|
|
expect(effectiveUserId(base, "agent:researcher:uuid-1")).toBe(
|
|
"alice:agent:researcher",
|
|
);
|
|
});
|
|
|
|
it("falls back to base for 'main' agent sessions", () => {
|
|
expect(effectiveUserId(base, "agent:main:uuid-2")).toBe("alice");
|
|
});
|
|
|
|
it("falls back to base for non-agent session keys", () => {
|
|
expect(effectiveUserId(base, "plain-session-id")).toBe("alice");
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// agentUserId
|
|
// ---------------------------------------------------------------------------
|
|
describe("agentUserId", () => {
|
|
it("produces the correct namespaced format", () => {
|
|
expect(agentUserId("alice", "researcher")).toBe("alice:agent:researcher");
|
|
});
|
|
|
|
it("handles empty agentId (caller is responsible for validation)", () => {
|
|
expect(agentUserId("alice", "")).toBe("alice:agent:");
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// resolveUserId — priority chain
|
|
// ---------------------------------------------------------------------------
|
|
describe("resolveUserId", () => {
|
|
const base = "alice";
|
|
|
|
it("prefers explicit agentId over everything else", () => {
|
|
expect(
|
|
resolveUserId(
|
|
base,
|
|
{ agentId: "researcher", userId: "bob" },
|
|
"agent:beta:uuid",
|
|
),
|
|
).toBe("alice:agent:researcher");
|
|
});
|
|
|
|
it("uses explicit userId when agentId is absent", () => {
|
|
expect(
|
|
resolveUserId(base, { userId: "bob" }, "agent:beta:uuid"),
|
|
).toBe("bob");
|
|
});
|
|
|
|
it("derives from session key when both agentId and userId are absent", () => {
|
|
expect(
|
|
resolveUserId(base, {}, "agent:gamma:uuid"),
|
|
).toBe("alice:agent:gamma");
|
|
});
|
|
|
|
it("falls back to base userId when nothing else is provided", () => {
|
|
expect(resolveUserId(base, {})).toBe("alice");
|
|
expect(resolveUserId(base, {}, undefined)).toBe("alice");
|
|
});
|
|
|
|
it("ignores empty-string agentId (falsy)", () => {
|
|
expect(resolveUserId(base, { agentId: "" })).toBe("alice");
|
|
});
|
|
|
|
it("ignores empty-string userId (falsy)", () => {
|
|
expect(resolveUserId(base, { userId: "" })).toBe("alice");
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Cross-agent isolation sanity checks
|
|
// ---------------------------------------------------------------------------
|
|
describe("multi-agent isolation", () => {
|
|
const base = "user-42";
|
|
|
|
it("different agents get different namespaces", () => {
|
|
const alphaId = effectiveUserId(base, "agent:alpha:uuid-a");
|
|
const betaId = effectiveUserId(base, "agent:beta:uuid-b");
|
|
expect(alphaId).not.toBe(betaId);
|
|
expect(alphaId).toBe("user-42:agent:alpha");
|
|
expect(betaId).toBe("user-42:agent:beta");
|
|
});
|
|
|
|
it("same agent across sessions yields the same namespace", () => {
|
|
const s1 = effectiveUserId(base, "agent:alpha:session-1");
|
|
const s2 = effectiveUserId(base, "agent:alpha:session-2");
|
|
expect(s1).toBe(s2);
|
|
});
|
|
|
|
it("main session shares the base namespace (no isolation)", () => {
|
|
const mainId = effectiveUserId(base, "agent:main:uuid-m");
|
|
expect(mainId).toBe(base);
|
|
});
|
|
});
|