import { describe, expect, it } from "vitest"; import { activeProductFeatureIds, resolveProductFeatures, selectCompiledProductFeatures, } from "../../src/contracts/product-features.ts"; import { runtimeConfigV2ArtifactSchema } from "../../src/contracts/release-artifacts.ts"; import { COMPILED_PRODUCT_FEATURE_IDS, INSTALLED_PRODUCT_FEATURE_IDS, } from "../../src/features/installed-product-manifest.ts"; import { ROUTE_FEATURE_OWNER, ROUTE_REGISTRY, } from "../../src/features/installed-feature-contracts.ts"; const COMPILED = Object.freeze([ Object.freeze({ featureId: "reference-feature" }), Object.freeze({ featureId: "billing" }), ]); describe("build-time product selection", () => { it("keeps everything when nothing is declared", () => { for (const declared of [undefined, "", " "]) { expect( selectCompiledProductFeatures(COMPILED, declared).map((f) => f.featureId), String(declared), ).toEqual(["reference-feature", "billing"]); } }); it("narrows to the declared subset", () => { expect( selectCompiledProductFeatures(COMPILED, "billing").map((f) => f.featureId), ).toEqual(["billing"]); expect( selectCompiledProductFeatures(COMPILED, " billing , reference-feature ").map( (f) => f.featureId, ), ).toEqual(["reference-feature", "billing"]); }); it("selects nothing only when asked explicitly", () => { // A blank value keeps everything on purpose: an unset CI variable expands // to a blank string, and that must not be how a build ships no features. expect(selectCompiledProductFeatures(COMPILED, "none")).toEqual([]); expect(selectCompiledProductFeatures(COMPILED, " none ")).toEqual([]); expect(selectCompiledProductFeatures(COMPILED, "").length).toBe(2); // A value that parses to no names at all is a typo, not an instruction. expect(() => selectCompiledProductFeatures(COMPILED, ",")).toThrow( /names no feature/u, ); }); it("refuses to name a feature this build does not contain", () => { // The whole point of the direction rule: an environment value can subtract // from the source tree and must never be able to add to it. Accepting an // unknown id silently would let a deployment believe it had switched on // something that is not in the bundle. expect(() => selectCompiledProductFeatures(COMPILED, "analytics")).toThrow( /does not contain: analytics/u, ); expect(() => selectCompiledProductFeatures(COMPILED, "billing,analytics"), ).toThrow(/analytics/u); }); it("refuses a duplicated feature id in the manifest", () => { expect(() => selectCompiledProductFeatures( [{ featureId: "a" }, { featureId: "a" }], undefined, ), ).toThrow(/duplicate product feature id/u); }); }); describe("runtime product feature resolution", () => { it("reports active, disabled and not-installed distinctly", () => { const statuses = resolveProductFeatures( ["reference-feature", "billing"], ["reference-feature"], { "reference-feature": "DISABLED" }, ); expect(statuses).toEqual([ { featureId: "billing", state: "NOT_INSTALLED" }, { featureId: "reference-feature", state: "DISABLED_BY_CONFIG" }, ]); expect(activeProductFeatureIds(statuses)).toEqual([]); }); it("cannot switch on a feature the build left out", () => { // `DEFAULT` on an uninstalled feature is not an instruction to install it. const statuses = resolveProductFeatures(["billing"], [], { billing: "DEFAULT", }); expect(statuses).toEqual([{ featureId: "billing", state: "NOT_INSTALLED" }]); expect(activeProductFeatureIds(statuses)).toEqual([]); }); it("ignores an override naming a feature this build never declared", () => { // A shared runtime document may cover several builds, so a stale key is // inert rather than fatal. const statuses = resolveProductFeatures( ["reference-feature"], ["reference-feature"], { analytics: "DISABLED" }, ); expect(activeProductFeatureIds(statuses)).toEqual(["reference-feature"]); }); it("leaves an installed feature active without an override", () => { const statuses = resolveProductFeatures( COMPILED_PRODUCT_FEATURE_IDS, INSTALLED_PRODUCT_FEATURE_IDS, ); expect(activeProductFeatureIds(statuses)).toEqual([ ...INSTALLED_PRODUCT_FEATURE_IDS, ]); }); }); describe("runtime config carries the switch", () => { const base = { APP_ENV: "local" as const, API_BASE_URL: "http://localhost:8080/", TELEMETRY_ENABLED: false, AUTH_MODE: "demo" as const, CONFIG_SCHEMA_VERSION: "2.0" as const, RELEASE_MANIFEST_URL: "/release-manifest.json", }; it("defaults to disabling nothing", () => { const parsed = runtimeConfigV2ArtifactSchema.parse(base); expect(parsed.FEATURE_OVERRIDES).toEqual({}); }); it("accepts only DEFAULT or DISABLED", () => { expect( runtimeConfigV2ArtifactSchema.parse({ ...base, FEATURE_OVERRIDES: { "reference-feature": "DISABLED" }, }).FEATURE_OVERRIDES, ).toEqual({ "reference-feature": "DISABLED" }); // There is no "ENABLED": the vocabulary itself is what makes the rule // unbreakable, not a check somewhere downstream. expect( runtimeConfigV2ArtifactSchema.safeParse({ ...base, FEATURE_OVERRIDES: { "reference-feature": "ENABLED" }, }).success, ).toBe(false); }); it("refuses a malformed feature id", () => { for (const featureId of ["Reference", "reference_feature", "", "-x"]) { expect( runtimeConfigV2ArtifactSchema.safeParse({ ...base, FEATURE_OVERRIDES: { [featureId]: "DISABLED" }, }).success, featureId, ).toBe(false); } }); }); describe("every installed registry consults the manifest", () => { /** * The manifest only means something if each registry actually asks it. A new * registry that spreads a feature in directly would reintroduce exactly the * coupling this file exists to remove, and nothing else would notice. */ it("gates every feature contribution on the selection", async () => { const { readdir, readFile } = await import("node:fs/promises"); const nodePath = (await import("node:path")).default; const root = "src/features"; const registries = (await readdir(root)).filter((entry) => /^installed-.*\.tsx?$/u.test(entry), ); expect(registries.length).toBeGreaterThan(3); const exempt = new Set([ // The manifest is the selection. "installed-product-manifest.ts", // Capabilities have their own ยง3.5 selection file and override vocabulary. "installed-runtime-capabilities.ts", // Message keys stay total on purpose; see the file for why. "installed-feature-messages.ts", ]); for (const registry of registries) { if (exempt.has(registry)) continue; const source = await readFile(nodePath.join(root, registry), "utf8"); expect( /INSTALLED_PRODUCT_FEATURE(S|_IDS)/u.test(source), `${registry} must compose from the product manifest`, ).toBe(true); } }); }); describe("route ownership", () => { it("attributes every feature route to its feature and no platform route", () => { for (const featureId of INSTALLED_PRODUCT_FEATURE_IDS) { expect(Object.values(ROUTE_FEATURE_OWNER)).toContain(featureId); } // Platform routes have no owner, so disabling a feature can never withdraw // the shell's own navigation. for (const routeId of ["APP_HOME", "NOT_FOUND", "EXAMPLES_PLATFORM"]) { expect(ROUTE_FEATURE_OWNER[routeId], routeId).toBeUndefined(); expect(Object.keys(ROUTE_REGISTRY)).toContain(routeId); } }); it("owns exactly the routes the registry received from features", () => { const owned = Object.keys(ROUTE_FEATURE_OWNER); expect(owned.length).toBeGreaterThan(0); for (const routeId of owned) { expect(Object.keys(ROUTE_REGISTRY), routeId).toContain(routeId); } }); });