mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-07 10:06:21 +00:00
Review caught that the same anti-pattern was still live in the Studio binding: canaryEventProperties destructured only `enabled` and dropped the reason. Its own doc comment promised 'identical shape to the CLI, so a rollout spanning both reads as one flag' — which the CLI-only fix had just made false. This matters beyond symmetry. A CLI-launched Studio adopts the CLI's decisions and shares its bucket seed, so a cohort flip can surface on either surface. Emitting attribution on only one leaves Studio-observed flips unattributable and makes the two flip counts irreconcilable — and Studio is the surface most likely to expose a shared-seed-with-diverging-id pattern, which is the open question the reason exists to answer. Also adds the no_unit_id emission test the CLI side advertised but never asserted, and a Studio pair pinning that a URL override and a cohort roll produce the same assignment with different reasons. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
351 lines
14 KiB
TypeScript
351 lines
14 KiB
TypeScript
// @vitest-environment happy-dom
|
|
|
|
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
|
|
import { evaluateCanary } from "@hyperframes/core/canary";
|
|
|
|
// Pin the registry: real entries move as rollouts ramp, and these tests are
|
|
// about the BINDING (does the browser supply the right three inputs?), not
|
|
// about whichever canaries happen to be live today.
|
|
// The policy reads import.meta.env.DEV, which vitest sets true — without
|
|
// this every case would resolve to telemetry_opt_out. Controlled explicitly
|
|
// so each test states the privacy posture it is exercising.
|
|
const policyState = { allowed: true };
|
|
vi.mock("./policy", () => ({
|
|
browserTelemetryAllowed: () => policyState.allowed,
|
|
}));
|
|
|
|
vi.mock("@hyperframes/core/canary-registry", async () => {
|
|
const actual = await vi.importActual<typeof import("@hyperframes/core/canary-registry")>(
|
|
"@hyperframes/core/canary-registry",
|
|
);
|
|
const defs = [
|
|
{
|
|
name: "on-everywhere",
|
|
percentage: 100,
|
|
description: "",
|
|
owner: "t",
|
|
sunsetAfter: "2099-01-01",
|
|
},
|
|
{
|
|
name: "off-everywhere",
|
|
percentage: 0,
|
|
description: "",
|
|
owner: "t",
|
|
sunsetAfter: "2099-01-01",
|
|
},
|
|
];
|
|
return { ...actual, CANARIES: defs, findCanary: (n: string) => defs.find((d) => d.name === n) };
|
|
});
|
|
|
|
const {
|
|
isCanaryEnabled,
|
|
resolveCanary,
|
|
canaryEventProperties,
|
|
canaryParamName,
|
|
__resetStudioCanaryCacheForTests,
|
|
} = await import("./canary");
|
|
const { resolveStudioDistinctId, __resetStudioDistinctIdForTests } = await import("./distinctId");
|
|
|
|
function setSearch(search: string): void {
|
|
window.history.replaceState({}, "", `/${search}`);
|
|
}
|
|
|
|
beforeEach(() => {
|
|
policyState.allowed = true;
|
|
localStorage.clear();
|
|
sessionStorage.clear();
|
|
setSearch("");
|
|
delete window.__HF_CLI_DISTINCT_ID;
|
|
delete window.__HF_CLI_BUCKET_SEED;
|
|
Object.defineProperty(navigator, "webdriver", { value: false, configurable: true });
|
|
__resetStudioCanaryCacheForTests();
|
|
__resetStudioDistinctIdForTests();
|
|
});
|
|
|
|
afterEach(() => {
|
|
setSearch("");
|
|
__resetStudioCanaryCacheForTests();
|
|
__resetStudioDistinctIdForTests();
|
|
});
|
|
|
|
describe("studio canary binding", () => {
|
|
it("reads the percentage from the shared registry", () => {
|
|
expect(isCanaryEnabled("on-everywhere")).toBe(true);
|
|
expect(isCanaryEnabled("off-everywhere")).toBe(false);
|
|
});
|
|
|
|
it("an unregistered name is off, not a throw — a typo must not break the editor", () => {
|
|
expect(isCanaryEnabled("nope")).toBe(false);
|
|
expect(resolveCanary("nope").reason).toBe("out_of_cohort");
|
|
});
|
|
|
|
it("derives the query param from the canary name", () => {
|
|
expect(canaryParamName("de-parallel-router")).toBe("hf_canary_de_parallel_router");
|
|
});
|
|
});
|
|
|
|
describe("URL override", () => {
|
|
it("turns a canary on and off from the query string", () => {
|
|
setSearch("?hf_canary_off_everywhere=on");
|
|
expect(resolveCanary("off-everywhere")).toMatchObject({ enabled: true, reason: "forced_on" });
|
|
|
|
__resetStudioCanaryCacheForTests();
|
|
setSearch("?hf_canary_on_everywhere=off");
|
|
expect(resolveCanary("on-everywhere")).toMatchObject({ enabled: false, reason: "forced_off" });
|
|
});
|
|
|
|
it("survives losing the query string, so in-app navigation keeps the override", () => {
|
|
setSearch("?hf_canary_off_everywhere=on");
|
|
expect(isCanaryEnabled("off-everywhere")).toBe(true);
|
|
|
|
// Navigate away from the param — a real SPA drops it constantly.
|
|
__resetStudioCanaryCacheForTests();
|
|
setSearch("");
|
|
expect(isCanaryEnabled("off-everywhere")).toBe(true);
|
|
});
|
|
|
|
it("is session-scoped, not persisted to localStorage", () => {
|
|
// A URL-borne override must not silently pin a browser into a cohort
|
|
// forever; closing the tab is the reset.
|
|
setSearch("?hf_canary_off_everywhere=on");
|
|
expect(isCanaryEnabled("off-everywhere")).toBe(true);
|
|
expect(JSON.stringify(localStorage).includes("canary")).toBe(false);
|
|
expect(sessionStorage.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
it("=reset clears a stored override", () => {
|
|
setSearch("?hf_canary_off_everywhere=on");
|
|
expect(isCanaryEnabled("off-everywhere")).toBe(true);
|
|
|
|
__resetStudioCanaryCacheForTests();
|
|
setSearch("?hf_canary_off_everywhere=reset");
|
|
expect(isCanaryEnabled("off-everywhere")).toBe(false);
|
|
|
|
__resetStudioCanaryCacheForTests();
|
|
setSearch("");
|
|
expect(isCanaryEnabled("off-everywhere")).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("automated browsers", () => {
|
|
it("are excluded from percentage enrolment", () => {
|
|
Object.defineProperty(navigator, "webdriver", { value: true, configurable: true });
|
|
expect(resolveCanary("on-everywhere")).toMatchObject({ enabled: false, reason: "excluded" });
|
|
});
|
|
|
|
it("still honour an explicit override, so a canary can be tested under automation", () => {
|
|
Object.defineProperty(navigator, "webdriver", { value: true, configurable: true });
|
|
setSearch("?hf_canary_on_everywhere=on");
|
|
expect(resolveCanary("on-everywhere")).toMatchObject({ enabled: true, reason: "forced_on" });
|
|
});
|
|
});
|
|
|
|
describe("cohort identity", () => {
|
|
it("buckets on the CLI's bucket seed when injected — the unit that survives config wipes", () => {
|
|
// The CLI buckets on its bucketSeed (inherited across config wipes via
|
|
// the install-state file), so a CLI-launched Studio must bucket on the
|
|
// SAME seed or the two surfaces would split one machine across cohorts.
|
|
const cliId = "db0c1f4a-b95e-4c35-90c6-1a15bd76f717";
|
|
const cliSeed = "5f1c9d2e-0000-4000-8000-aaaaaaaaaaaa";
|
|
window.__HF_CLI_DISTINCT_ID = cliId;
|
|
window.__HF_CLI_BUCKET_SEED = cliSeed;
|
|
__resetStudioDistinctIdForTests();
|
|
__resetStudioCanaryCacheForTests();
|
|
|
|
// Telemetry identity still adopts the DISTINCT id — the seed only buckets.
|
|
expect(resolveStudioDistinctId()).toBe(cliId);
|
|
const viaBinding = resolveCanary("on-everywhere").bucket;
|
|
const bySeed = evaluateCanary({
|
|
feature: "on-everywhere",
|
|
unitId: cliSeed,
|
|
percentage: 100,
|
|
}).bucket;
|
|
expect(viaBinding).toBe(bySeed);
|
|
});
|
|
|
|
it("buckets on the Studio distinct id when no seed is injected (standalone Studio)", () => {
|
|
const cliId = "db0c1f4a-b95e-4c35-90c6-1a15bd76f717";
|
|
window.__HF_CLI_DISTINCT_ID = cliId;
|
|
__resetStudioDistinctIdForTests();
|
|
__resetStudioCanaryCacheForTests();
|
|
|
|
expect(resolveStudioDistinctId()).toBe(cliId);
|
|
const viaBinding = resolveCanary("on-everywhere").bucket;
|
|
const direct = evaluateCanary({
|
|
feature: "on-everywhere",
|
|
unitId: cliId,
|
|
percentage: 100,
|
|
}).bucket;
|
|
expect(viaBinding).toBe(direct);
|
|
});
|
|
|
|
it("memoizes so a decision cannot change mid-session", () => {
|
|
expect(isCanaryEnabled("off-everywhere")).toBe(false);
|
|
// A late override must NOT flip a component that already rendered.
|
|
setSearch("?hf_canary_off_everywhere=on");
|
|
expect(isCanaryEnabled("off-everywhere")).toBe(false);
|
|
__resetStudioCanaryCacheForTests();
|
|
expect(isCanaryEnabled("off-everywhere")).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("telemetry", () => {
|
|
it("emits the same PostHog flag-shaped properties as the CLI", () => {
|
|
expect(canaryEventProperties()).toEqual({
|
|
"$feature/canary-on-everywhere": "true",
|
|
"$feature/canary-off-everywhere": "false",
|
|
canary_reason_on_everywhere: "in_cohort",
|
|
canary_reason_off_everywhere: "out_of_cohort",
|
|
});
|
|
|
|
__resetStudioCanaryCacheForTests();
|
|
setSearch("?hf_canary_on_everywhere=off");
|
|
expect(canaryEventProperties()["$feature/canary-on-everywhere"]).toBe("false");
|
|
});
|
|
});
|
|
|
|
describe("telemetry opt-out is canary opt-out", () => {
|
|
// The studio opt-out lever, per telemetry/config.ts.
|
|
const OPT_OUT_KEY = "hyperframes-studio:telemetryDisabled";
|
|
|
|
it("does not enrol an opted-out browser profile", () => {
|
|
policyState.allowed = false;
|
|
localStorage.setItem(OPT_OUT_KEY, "1");
|
|
// on-everywhere is at 100% — it would be on for everyone otherwise.
|
|
expect(resolveCanary("on-everywhere")).toEqual({
|
|
enabled: false,
|
|
reason: "telemetry_opt_out",
|
|
});
|
|
});
|
|
|
|
it("never buckets an opted-out profile — no cohort is assigned at all", () => {
|
|
policyState.allowed = false;
|
|
localStorage.setItem(OPT_OUT_KEY, "1");
|
|
expect(resolveCanary("on-everywhere").bucket).toBeUndefined();
|
|
});
|
|
|
|
it("still honours an explicit URL override", () => {
|
|
policyState.allowed = false;
|
|
localStorage.setItem(OPT_OUT_KEY, "1");
|
|
setSearch("?hf_canary_off_everywhere=on");
|
|
expect(resolveCanary("off-everywhere")).toEqual({ enabled: true, reason: "forced_on" });
|
|
});
|
|
|
|
it("reports every canary as false when opted out", () => {
|
|
policyState.allowed = false;
|
|
localStorage.setItem(OPT_OUT_KEY, "1");
|
|
expect(canaryEventProperties()).toEqual({
|
|
"$feature/canary-on-everywhere": "false",
|
|
"$feature/canary-off-everywhere": "false",
|
|
canary_reason_on_everywhere: "telemetry_opt_out",
|
|
canary_reason_off_everywhere: "telemetry_opt_out",
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("CLI-launched Studio adopts the CLI's decisions", () => {
|
|
const OPT_OUT_KEY = "hyperframes-studio:telemetryDisabled";
|
|
const cohort = (enabled: boolean) => ({ enabled, forced: false });
|
|
const forced = (enabled: boolean) => ({ enabled, forced: true });
|
|
|
|
afterEach(() => {
|
|
delete window.__HF_CLI_CANARY_DECISIONS;
|
|
});
|
|
|
|
// The divergence this exists for: CLI telemetry off resolves every canary
|
|
// to telemetry_opt_out, but Studio's opt-out is a SEPARATE localStorage
|
|
// flag it cannot see — left to itself it would evaluate and could enrol.
|
|
it("stays off when the CLI opted out, even though Studio's own flag is unset", () => {
|
|
expect(localStorage.getItem(OPT_OUT_KEY)).toBeNull();
|
|
window.__HF_CLI_CANARY_DECISIONS = { "on-everywhere": cohort(false) };
|
|
expect(resolveCanary("on-everywhere").enabled).toBe(false);
|
|
});
|
|
|
|
// HF_CANARY_* never crosses into the browser, so before this the CLI was
|
|
// forced on and Studio silently guessed from the percentage.
|
|
it("turns on when the CLI forced it on, with no URL param present", () => {
|
|
window.__HF_CLI_CANARY_DECISIONS = { "off-everywhere": forced(true) };
|
|
expect(resolveCanary("off-everywhere").enabled).toBe(true);
|
|
});
|
|
|
|
it("beats a contradicting URL override — one render must not run half-enrolled", () => {
|
|
window.__HF_CLI_CANARY_DECISIONS = { "on-everywhere": forced(false) };
|
|
setSearch("?hf_canary_on_everywhere=on");
|
|
expect(resolveCanary("on-everywhere").enabled).toBe(false);
|
|
});
|
|
|
|
it("beats the seed-derived bucket", () => {
|
|
window.__HF_CLI_BUCKET_SEED = "5f1c9d2e-0000-4000-8000-aaaaaaaaaaaa";
|
|
window.__HF_CLI_CANARY_DECISIONS = { "on-everywhere": cohort(false) };
|
|
expect(resolveCanary("on-everywhere").enabled).toBe(false);
|
|
});
|
|
|
|
it("falls back to local evaluation for a canary the CLI did not publish", () => {
|
|
window.__HF_CLI_CANARY_DECISIONS = { "off-everywhere": cohort(true) };
|
|
expect(resolveCanary("on-everywhere").enabled).toBe(true);
|
|
});
|
|
|
|
it("ignores a malformed entry rather than trusting it", () => {
|
|
window.__HF_CLI_CANARY_DECISIONS = {
|
|
"on-everywhere": { enabled: "false" },
|
|
} as unknown as Record<string, { enabled?: boolean; forced?: boolean }>;
|
|
// Falls through to local evaluation: on-everywhere is at 100%.
|
|
expect(resolveCanary("on-everywhere").enabled).toBe(true);
|
|
});
|
|
|
|
// Miguel's P1: a percentage roll from the CLI must NOT be able to enrol a
|
|
// browser profile that opted out. The two surfaces have independent
|
|
// opt-outs, and CLI telemetry being on says nothing about this profile.
|
|
describe("precedence against Studio's own opt-out", () => {
|
|
beforeEach(() => {
|
|
policyState.allowed = false;
|
|
localStorage.setItem(OPT_OUT_KEY, "1");
|
|
});
|
|
|
|
it("refuses a CLI COHORT enrolment when this profile opted out", () => {
|
|
window.__HF_CLI_CANARY_DECISIONS = { "off-everywhere": cohort(true) };
|
|
expect(resolveCanary("off-everywhere")).toEqual({
|
|
enabled: false,
|
|
reason: "telemetry_opt_out",
|
|
});
|
|
});
|
|
|
|
it("honours a CLI FORCED enrolment even when this profile opted out", () => {
|
|
// An explicit HF_CANARY_* override is a deliberate operator choice —
|
|
// the documented escalation channel, same as a local URL override.
|
|
window.__HF_CLI_CANARY_DECISIONS = { "off-everywhere": forced(true) };
|
|
expect(resolveCanary("off-everywhere")).toEqual({ enabled: true, reason: "forced_on" });
|
|
});
|
|
|
|
it("honours a CLI forced-OFF when this profile opted out", () => {
|
|
window.__HF_CLI_CANARY_DECISIONS = { "on-everywhere": forced(false) };
|
|
expect(resolveCanary("on-everywhere")).toEqual({ enabled: false, reason: "forced_off" });
|
|
});
|
|
|
|
it("still refuses cohort enrolment with no CLI decision at all", () => {
|
|
expect(resolveCanary("on-everywhere").reason).toBe("telemetry_opt_out");
|
|
});
|
|
});
|
|
});
|
|
|
|
// A CLI-launched Studio shares the CLI's bucket seed, so a cohort flip can
|
|
// surface on either surface. Attribution on only one of them makes the two
|
|
// flip counts irreconcilable — which is why this is not a CLI-only property.
|
|
describe("Studio attribution matches the CLI", () => {
|
|
it("distinguishes a local URL override from a cohort roll at the same value", () => {
|
|
setSearch("?hf_canary_off_everywhere=on");
|
|
const props = canaryEventProperties();
|
|
expect(props["$feature/canary-off-everywhere"]).toBe("true");
|
|
expect(props["canary_reason_off_everywhere"]).toBe("forced_on");
|
|
// Same assignment `on-everywhere` reaches by an ordinary roll.
|
|
expect(props["$feature/canary-on-everywhere"]).toBe("true");
|
|
expect(props["canary_reason_on_everywhere"]).toBe("in_cohort");
|
|
});
|
|
|
|
it("never puts a reason inside the $feature namespace", () => {
|
|
for (const [key, value] of Object.entries(canaryEventProperties())) {
|
|
if (key.startsWith("$feature/")) expect(value).toMatch(/^(true|false)$/);
|
|
}
|
|
});
|
|
});
|