test(evals): inspect announcements on normal company surfaces

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-11-03 00:16:27 -06:00
1 parent 51ef450754
commit 0a76dbde9b
2 files changed
+69 -3

No files matched your search

@@ -0,0 +1,58 @@
import { expect, it, vi } from "vitest";
import type { ChatCatalogContext } from "../chat-catalog-contract.js";
import { CHAT_ANNOUNCEMENT_MANIFEST } from "../chat-announcement-fixture.js";
vi.mock("@playwright/test", () => ({ expect: (node: { visible(): boolean; count(): number }) => ({
toBeVisible: async () => { if (!node.visible()) throw Error("Requested normal surface is not visible"); },
toHaveCount: async (count: number) => { if (node.count() !== count) throw Error("Unexpected visible surface count"); },
}) }));
vi.mock("./identity-auth.js", () => ({ createAuthenticatedPerson: async ({ browser }: { browser: { person: unknown } }) => browser.person, enrollPrerequisitePerson: async () => {} }));
vi.mock("./identity-support.js", async importOriginal => ({ ...await importOriginal<typeof import("./identity-support.js")>(), waitForReply: async () => {}, probeAgentPermission: async () => 403 }));
import { navigationBoundaryIdentityCases } from "./identity-navigation-boundaries.js";
function fixture(fault?: "modal" | "unsettled projects" | "missing secondary publication" | "dismissal lost on reload" | "modal after reload" | "unsettled projects after reload") {
const publication = CHAT_ANNOUNCEMENT_MANIFEST.announcement;
let current = "/RUN/chats/source", dismissed = false, reloaded = false;
const visits: string[] = [];
function page(observer = false) {
let path = current;
const node = (visible: () => boolean) => ({ visible, count: () => Number(visible()), getByRole: (role: string, options: { name: string }) => role === "button" ? { click: async () => { expect(options.name).toBe("Dismiss announcement"); dismissed = true; } } : node(visible) });
const modal = () => path === "/BIR/dashboard" || fault === "modal" || (reloaded && fault === "modal after reload");
const p = {
url: () => path,
context: () => ({ browser: () => ({ person }) }),
goto: async (value: string) => { path = value; current = value; visits.push(value); },
reload: async () => { visits.push("reload:" + path); reloaded = true; if (fault === "dismissal lost on reload") dismissed = false; },
getByText: (text: string) => node(() => text === "No projects yet." && path === "/BIR/projects" && fault !== "unsettled projects" && !(reloaded && fault === "unsettled projects after reload") && !modal()),
getByRole: (role: string) => role === "dialog" ? node(modal) : node(() => !modal() && (observer || !dismissed) && !(path.startsWith("/BIR/") && fault === "missing secondary publication")),
waitForResponse: async () => ({ status: () => 204 }), close: async () => {},
};
return p;
}
const person = { id: "riley", dispose: async () => {}, context: { newPage: async () => page(true) }, request: async (_path: string, method?: string) => ({ status: method === "POST" ? 404 : 200, json: async () => publication, body: { cancel: async () => {} } }) };
const ctx = { input: { nonce: "owned", fixtures: { company: { id: "company", issuePrefix: "RUN" }, agent: { id: "ceo" } }, privateSecrets: [], page: page(), api: {
get: async (path: string) => path === "/api/auth/get-session" ? { user: { id: "alex" } } : path === "/api/announcements/current" ? (dismissed ? null : publication) : Promise.reject(Error("Unexpected read")),
post: async (path: string) => { expect(path).toBe("/api/companies"); return { id: "birch", issuePrefix: "BIR" }; }, delete: async () => {},
}, evidence: async () => {} } } as unknown as ChatCatalogContext;
return { ctx, visits, definition: navigationBoundaryIdentityCases[1]! };
}
it("the actual dismissal setup uses a settled ordinary second-company surface, then preserves real reload and independent-person proof", async () => {
const f = fixture(); const state = await f.definition.setup(f.ctx);
expect(state.secondaryVisible).toBe(true); expect(state.invalidAuditCompanyStatus).toBe(404);
await f.definition.act!(f.ctx, state);
expect(f.visits.filter(x => x.startsWith("/BIR/"))).toEqual(["/BIR/projects", "/BIR/projects"]);
expect(f.visits).toContain("reload:/BIR/projects");
expect(state.secondaryAfterReloadHidden).toBe(true); expect(state.observerStillVisible).toBe(true); expect(state.agentDismissStatus).toBe(403);
});
it.each(["modal", "unsettled projects", "missing secondary publication"] as const)("setup refuses %s rather than pretending the announcement was visible", async fault => {
const f = fixture(fault); await expect(f.definition.setup(f.ctx)).rejects.toThrow();
});
it("a publication reappearing after the real secondary reload still fails", async () => {
const f = fixture("dismissal lost on reload"), state = await f.definition.setup(f.ctx);
await expect(f.definition.act!(f.ctx, state)).rejects.toThrow("Unexpected visible surface count");
expect(state.secondaryAfterReloadHidden).not.toBe(true);
});
it.each(["modal after reload", "unsettled projects after reload"] as const)("does not mistake %s for a persisted dismissal", async fault => {
const f = fixture(fault), state = await f.definition.setup(f.ctx);
await expect(f.definition.act!(f.ctx, state)).rejects.toThrow();
expect(state.secondaryAfterReloadHidden).not.toBe(true);
});
@@ -50,6 +50,14 @@ export function gradeNavigationBoundary(value: unknown) {
return c.result();
}
const sources = ["chat-catalog-domains/identity-navigation-boundaries.ts", "chat-catalog-domains/identity-auth.ts", "chat-catalog-domains/identity-private-browser.ts", "chat-catalog-domains/identity-private-browser-worker.ts", "chat-catalog-domains/identity-wizard.ts", "chat-catalog-domains/identity-support.ts", "chat-catalog-domains/internal-support.ts", "chat-scenarios/navigation-preferences.yaml"];
// An empty company's dashboard correctly opens first-agent onboarding, which hides
// optional announcements. Use its normal Projects page for this personal preference.
async function openAnnouncementCompanySurface(ctx: ChatCatalogContext, issuePrefix: string, reload = false) {
await ctx.input.page.goto(`/${issuePrefix}/projects`, { waitUntil: "domcontentloaded" });
if (reload) await ctx.input.page.reload({ waitUntil: "domcontentloaded" });
await expect(ctx.input.page.getByText("No projects yet.", { exact: true })).toBeVisible();
await expect(ctx.input.page.getByRole("dialog")).toHaveCount(0);
}
export const navigationBoundaryIdentityCases: readonly ChatCatalogCase[] = [
identityDefinition("navigation-preferences.broken-link", async ctx => {
const source = await openStandingChat(ctx); const p = await project(ctx);
@@ -106,7 +114,7 @@ export const navigationBoundaryIdentityCases: readonly ChatCatalogCase[] = [
const invalidAuditCompanyStatus = invalid.status; await invalid.body?.cancel();
const source = ctx.input.page.url();
await expect(ctx.input.page.getByRole("complementary", { name: "Paperclip announcements" }).getByRole("heading", { name: publication.title, exact: true })).toBeVisible();
await ctx.input.page.goto(`/${otherCompany.issuePrefix}/dashboard`, { waitUntil: "domcontentloaded" });
await openAnnouncementCompanySurface(ctx, otherCompany.issuePrefix);
await expect(ctx.input.page.getByRole("complementary", { name: "Paperclip announcements" }).getByRole("heading", { name: publication.title, exact: true })).toBeVisible();
const secondary = await ctx.input.api.get<Publication | null>("/api/announcements/current");
await ctx.input.page.goto(source, { waitUntil: "domcontentloaded" });
@@ -114,7 +122,7 @@ export const navigationBoundaryIdentityCases: readonly ChatCatalogCase[] = [
observerBeforePublicationId: observerPublication?.id, invalidAuditCompanyStatus };
} catch (error) { try { await observer.dispose(); } finally { observers.delete(ctx); const id = otherCompanies.get(ctx); if (id) await ctx.input.api.delete(`/api/companies/${id}`, { allowNotFound: true }); otherCompanies.delete(ctx); } throw error; }
}, gradeNavigationBoundary, {
sourceFiles: [...sources, "chat-announcement-fixture.ts", "harness-env.ts", "../../packages/shared/src/announcements.ts", "../../server/src/services/announcement-feed.ts", "../../server/src/routes/announcements.ts"], authentication: "human-session", oracleVersion: "paperclip.chat-catalog.announcement-dismissal.v2",
sourceFiles: [...sources, "chat-announcement-fixture.ts", "harness-env.ts", "../../packages/shared/src/announcements.ts", "../../server/src/services/announcement-feed.ts", "../../server/src/routes/announcements.ts", "../../ui/src/pages/Projects.tsx", "../../ui/src/components/AnnouncementWell.tsx"], authentication: "human-session", oracleVersion: "paperclip.chat-catalog.announcement-dismissal.v2",
async act(ctx, state) {
await waitForReply(ctx); const publication = state.publication as Publication; const source = ctx.input.page.url();
state.beforeHumanPublicationId = (await ctx.input.api.get<Publication | null>("/api/announcements/current"))?.id;
@@ -123,7 +131,7 @@ export const navigationBoundaryIdentityCases: readonly ChatCatalogCase[] = [
await ctx.input.page.getByRole("complementary", { name: "Paperclip announcements" }).getByRole("button", { name: "Dismiss announcement", exact: true }).click();
state.dismissRequestStatus = (await response).status(); state.afterHumanPublication = await ctx.input.api.get("/api/announcements/current");
const other = state.otherCompany as Entity & { issuePrefix: string };
await ctx.input.page.goto(`/${other.issuePrefix}/dashboard`, { waitUntil: "domcontentloaded" }); await ctx.input.page.reload({ waitUntil: "domcontentloaded" });
await openAnnouncementCompanySurface(ctx, other.issuePrefix, true);
await expect(ctx.input.page.getByRole("complementary", { name: "Paperclip announcements" })).toHaveCount(0); state.secondaryAfterReloadHidden = true;
const observer = observers.get(ctx)!; const receipt = await observer.request("/api/announcements/current"); state.observerAfterPublicationId = ((await receipt.json()) as Publication | null)?.id;
const page = await observer.context.newPage();