225 lines
8.0 KiB
TypeScript
225 lines
8.0 KiB
TypeScript
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: "alpha-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(["alpha-feature", "billing"]);
|
|
}
|
|
});
|
|
|
|
it("narrows to the declared subset", () => {
|
|
expect(
|
|
selectCompiledProductFeatures(COMPILED, "billing").map((f) => f.featureId),
|
|
).toEqual(["billing"]);
|
|
expect(
|
|
selectCompiledProductFeatures(COMPILED, " billing , alpha-feature ").map(
|
|
(f) => f.featureId,
|
|
),
|
|
).toEqual(["alpha-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(
|
|
["alpha-feature", "billing"],
|
|
["alpha-feature"],
|
|
{ "alpha-feature": "DISABLED" },
|
|
);
|
|
expect(statuses).toEqual([
|
|
{ featureId: "alpha-feature", state: "DISABLED_BY_CONFIG" },
|
|
{ featureId: "billing", state: "NOT_INSTALLED" },
|
|
]);
|
|
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(
|
|
["alpha-feature"],
|
|
["alpha-feature"],
|
|
{ analytics: "DISABLED" },
|
|
);
|
|
expect(activeProductFeatureIds(statuses)).toEqual(["alpha-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: { "alpha-feature": "DISABLED" },
|
|
}).FEATURE_OVERRIDES,
|
|
).toEqual({ "alpha-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: { "alpha-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 routes that exist in the composed registry", () => {
|
|
const owned = Object.keys(ROUTE_FEATURE_OWNER);
|
|
for (const routeId of owned) {
|
|
expect(Object.keys(ROUTE_REGISTRY), routeId).toContain(routeId);
|
|
}
|
|
expect(
|
|
owned.every((routeId) => ROUTE_FEATURE_OWNER[routeId] !== undefined),
|
|
).toBe(true);
|
|
});
|
|
});
|