← back to Kickbacks Ai Upstream
test/consent.test.ts
134 lines
/** Regression tests for wave-2I-F03 — first-run consent prompt.
*
* The prompt is one-time per (extension instance × tos_version), only
* fires for signed-in users who have not accepted the live ToS, and
* never throws into activation.
*/
import { describe, it, expect, vi } from "vitest";
import { ConsentClient } from "../src/consent/client";
import { maybePromptForConsent } from "../src/consent/prompt";
const baseState = {
telemetry_opt_in: false,
tos_accepted_version: null,
current_tos_version: "2026-05-17",
};
function makeFetch(getResp: any, postResp: any = { ok: true, json: async () => ({ tos_version: "2026-05-17", accepted_at: "now" }) }) {
return (async (url: string, init?: any) => {
if (init?.method === "POST") return postResp;
return getResp;
}) as unknown as typeof fetch;
}
function makeCtx(initial: Record<string, unknown> = {}) {
const store = new Map(Object.entries(initial));
return {
globalState: {
get: (k: string) => store.get(k),
update: async (k: string, v: unknown) => { store.set(k, v); },
},
_store: store,
} as any;
}
function makeVsc() {
const calls: any[] = [];
return {
calls,
window: {
showInformationMessage: vi.fn(async (..._args: any[]) => {
calls.push(_args);
return undefined; // dismissed by default
}),
},
env: { openExternal: vi.fn(async () => true) },
Uri: { parse: (s: string) => s },
} as any;
}
describe("ConsentClient", () => {
it("returns parsed state on 200", async () => {
const c = new ConsentClient("http://x", () => "tok",
makeFetch({ ok: true, json: async () => baseState }));
expect(await c.read()).toEqual({
telemetryOptIn: false,
tosAcceptedVersion: null,
currentTosVersion: "2026-05-17",
});
});
it("null when signed out", async () => {
const c = new ConsentClient("http://x", () => null,
makeFetch({ ok: true, json: async () => baseState }));
expect(await c.read()).toBeNull();
});
it("null on bad json (no current_tos_version)", async () => {
const c = new ConsentClient("http://x", () => "tok",
makeFetch({ ok: true, json: async () => ({}) }));
expect(await c.read()).toBeNull();
});
it("accept POSTs and returns version", async () => {
const c = new ConsentClient("http://x", () => "tok",
makeFetch({ ok: true, json: async () => baseState }));
expect(await c.accept()).toEqual({ tosVersion: "2026-05-17", acceptedAt: "now" });
});
});
describe("maybePromptForConsent", () => {
it("no-ops when signed out (read returns null)", async () => {
const client = new ConsentClient("http://x", () => null, makeFetch({ ok: true, json: async () => baseState }));
const vsc = makeVsc();
await maybePromptForConsent({ client, ctx: makeCtx(), vsc });
expect(vsc.window.showInformationMessage).not.toHaveBeenCalled();
});
it("no-ops when already consented to current version", async () => {
const accepted = { ...baseState, telemetry_opt_in: true, tos_accepted_version: "2026-05-17" };
const client = new ConsentClient("http://x", () => "tok", makeFetch({ ok: true, json: async () => accepted }));
const vsc = makeVsc();
await maybePromptForConsent({ client, ctx: makeCtx(), vsc });
expect(vsc.window.showInformationMessage).not.toHaveBeenCalled();
});
it("does not re-prompt if globalState already marked shown for this version", async () => {
const client = new ConsentClient("http://x", () => "tok", makeFetch({ ok: true, json: async () => baseState }));
const vsc = makeVsc();
const ctx = makeCtx({ "vibe-ads.consent.promptShownForVersion": "2026-05-17" });
await maybePromptForConsent({ client, ctx, vsc });
expect(vsc.window.showInformationMessage).not.toHaveBeenCalled();
});
it("prompts when fresh + signed in + not consented", async () => {
const client = new ConsentClient("http://x", () => "tok", makeFetch({ ok: true, json: async () => baseState }));
const vsc = makeVsc();
const ctx = makeCtx();
await maybePromptForConsent({ client, ctx, vsc });
expect(vsc.window.showInformationMessage).toHaveBeenCalledOnce();
// dismissed by default → globalState marks shown for this version
expect(ctx._store.get("vibe-ads.consent.promptShownForVersion")).toBe("2026-05-17");
});
it("Agree path POSTs and marks shown only after success", async () => {
const client = new ConsentClient("http://x", () => "tok", makeFetch(
{ ok: true, json: async () => baseState },
{ ok: true, json: async () => ({ tos_version: "2026-05-17", accepted_at: "2026-05-17T00:00:00Z" }) },
));
const vsc = makeVsc();
vsc.window.showInformationMessage = vi.fn(async () => "Agree") as any;
const ctx = makeCtx();
await maybePromptForConsent({ client, ctx, vsc });
expect(ctx._store.get("vibe-ads.consent.promptShownForVersion")).toBe("2026-05-17");
});
it("Privacy Policy path opens URL and does NOT mark shown", async () => {
const client = new ConsentClient("http://x", () => "tok", makeFetch({ ok: true, json: async () => baseState }));
const vsc = makeVsc();
vsc.window.showInformationMessage = vi.fn(async () => "Privacy Policy") as any;
const ctx = makeCtx();
await maybePromptForConsent({ client, ctx, vsc });
expect(vsc.env.openExternal).toHaveBeenCalledOnce();
// Not marked — so next session re-surfaces (gentle, not pushy)
expect(ctx._store.has("vibe-ads.consent.promptShownForVersion")).toBe(false);
});
});