mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-05 03:51:54 +01:00
test(evals): preserve consumed commentary identity response
Dispose only unread browser-session response bodies. Exercise the actual callback with real Node Responses so successful JSON is not followed by locked-stream cancellation. Preserve real reader and owner identity checks, restoration, denied-body disposal, malformed JSON failures, feedback privacy oracles, and original clocks. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
0ecd43035a
commit
b7dfdbbefd
2 files changed
+29
-7
No files matched your search
@@ -1,4 +1,4 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi, type MockInstance } from "vitest";
|
||||
import type { ChatCatalogContext } from "../chat-catalog-contract.js";
|
||||
const install = vi.hoisted(() => vi.fn(async (context: { restoreOwner(): void }) => context.restoreOwner()));
|
||||
vi.mock("./identity-auth.js", () => ({ installIdentitySession: install }));
|
||||
@@ -14,10 +14,16 @@ function fixture() {
|
||||
let apiUser = "owner"; let browserUser = "owner";
|
||||
let source: { id: string; companyId: string; conversationUserId: string; conversationAgentId: string } | undefined;
|
||||
const state = { browserOverride: undefined as string | undefined, sourceOverride: undefined as string | undefined };
|
||||
const response = { ok: true, json: async () => ({ user: { id: state.browserOverride ?? browserUser } }), body: { cancel: vi.fn(async () => undefined) } };
|
||||
vi.stubGlobal("fetch", vi.fn(async () => response));
|
||||
let response: Response;
|
||||
let cancel: MockInstance<ReadableStream["cancel"]>;
|
||||
const transport = vi.fn(async () => {
|
||||
response = Response.json({ user: { id: state.browserOverride ?? browserUser } });
|
||||
cancel = vi.spyOn(response.body!, "cancel");
|
||||
return response;
|
||||
});
|
||||
vi.stubGlobal("fetch", transport);
|
||||
const context = { cookies: vi.fn(async () => [{ name: "session", value: browserUser === "owner" ? "owned-test-cookie" : "reader-test-cookie" }]),
|
||||
request: { get: vi.fn(async () => response) }, restoreOwner() { browserUser = "owner"; } };
|
||||
request: { get: vi.fn(async () => response!) }, restoreOwner() { browserUser = "owner"; } };
|
||||
const api = { baseURL: "http://127.0.0.1:3100", get: vi.fn(async () => ({ user: { id: apiUser } })),
|
||||
setBrowserSession: vi.fn(() => { apiUser = "owner"; }),
|
||||
post: vi.fn(async () => source = { id: "source", companyId: "11111111-1111-4111-8111-111111111111", conversationUserId: state.sourceOverride ?? apiUser, conversationAgentId: "lead" }) };
|
||||
@@ -27,7 +33,7 @@ function fixture() {
|
||||
refreshIssue: vi.fn(async () => undefined), issue: () => source } as unknown as ChatCatalogContext;
|
||||
const person = { id: "casey", installSessionOn: vi.fn(async () => { browserUser = "casey"; }),
|
||||
bindApiSession: vi.fn(() => { apiUser = "casey"; }) } as unknown as Parameters<typeof installCommentaryReader>[1];
|
||||
return { ctx, person, api, context, state, evidence, response, current: () => ({ apiUser, browserUser }) };
|
||||
return { ctx, person, api, context, state, evidence, transport, get response() { return response!; }, get cancel() { return cancel!; }, current: () => ({ apiUser, browserUser }) };
|
||||
}
|
||||
beforeEach(() => { install.mockClear(); });
|
||||
afterEach(() => { vi.unstubAllGlobals(); });
|
||||
@@ -40,7 +46,8 @@ describe("real commentary reader identity lifetime", () => {
|
||||
expect(fetch).toHaveBeenCalledWith(new URL("/api/auth/get-session", f.api.baseURL), expect.objectContaining({ headers: { Cookie: "session=reader-test-cookie", Origin: f.api.baseURL } }));
|
||||
await selected.restore(); await selected.restore();
|
||||
expect(f.current()).toEqual({ apiUser: "owner", browserUser: "owner" });
|
||||
expect(install).toHaveBeenCalledOnce(); expect(f.response.body.cancel).toHaveBeenCalledOnce();
|
||||
expect(install).toHaveBeenCalledOnce(); expect(f.cancel).not.toHaveBeenCalled();
|
||||
expect(f.response.bodyUsed).toBe(true); expect(f.response.body?.locked).toBe(true);
|
||||
});
|
||||
it("restores genuine owner sessions after a browser/API identity mismatch", async () => {
|
||||
const f = fixture(); f.state.browserOverride = "different-reader";
|
||||
@@ -54,6 +61,21 @@ describe("real commentary reader identity lifetime", () => {
|
||||
expect(f.current()).toEqual({ apiUser: "owner", browserUser: "owner" });
|
||||
expect(f.evidence).not.toHaveBeenCalled();
|
||||
});
|
||||
it("disposes an unread denied browser response without leaking its body or creating a reader", async () => {
|
||||
const f = fixture(); const denied = new Response("private denial body must not escape", { status: 403 });
|
||||
const cancel = vi.spyOn(denied.body!, "cancel"); f.transport.mockImplementation(async () => denied);
|
||||
const error = await installCommentaryReader(f.ctx, f.person, "owner").catch(error => error);
|
||||
expect(error.message).toBe("Commentary browser session could not be independently read");
|
||||
expect(error.message).not.toContain("private denial"); expect(cancel).toHaveBeenCalledOnce();
|
||||
expect(f.current()).toEqual({ apiUser: "owner", browserUser: "owner" }); expect(f.evidence).not.toHaveBeenCalled();
|
||||
});
|
||||
it("preserves a malformed successful JSON failure instead of masking it with locked-stream cancellation", async () => {
|
||||
const f = fixture(); const malformed = new Response("invalid JSON");
|
||||
const cancel = vi.spyOn(malformed.body!, "cancel"); f.transport.mockImplementation(async () => malformed);
|
||||
await expect(installCommentaryReader(f.ctx, f.person, "owner")).rejects.toBeInstanceOf(SyntaxError);
|
||||
expect(cancel).not.toHaveBeenCalled(); expect(f.current()).toEqual({ apiUser: "owner", browserUser: "owner" });
|
||||
expect(f.evidence).not.toHaveBeenCalled();
|
||||
});
|
||||
it("rejects substituting the existing private owner for a distinct excluded reader", async () => {
|
||||
const f = fixture(); f.person.id = "owner";
|
||||
await expect(installCommentaryReader(f.ctx, f.person, "owner")).rejects.toThrow("distinct real owner and reader");
|
||||
|
||||
@@ -45,7 +45,7 @@ export async function installCommentaryReader(ctx: ChatCatalogContext, person: P
|
||||
try {
|
||||
if (!response.ok) throw new Error("Commentary browser session could not be independently read");
|
||||
browserPerson = await response.json();
|
||||
} finally { await response.body?.cancel(); }
|
||||
} finally { if (!response.bodyUsed) await response.body?.cancel(); }
|
||||
if (apiPerson.user?.id !== person.id || browserPerson.user?.id !== person.id)
|
||||
throw new Error("Commentary API and browser do not represent the same real reader");
|
||||
const source = await api.post<{ id: string; companyId: string; conversationUserId: string; conversationAgentId: string }>(
|
||||
|
||||
Reference in new issue
Block a user