Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/application/sidebar/sidebarFeatureAccess.test.ts
T
2026-07-08 16:15:37 +02:00

59 lines
2.7 KiB
TypeScript

import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, test } from "vitest";
import { featureKeys, type FeatureKey } from "@hivegrid/entitlements";
// Registry gate for a recurring footgun: AppSidebar gates entries via
// hasFeatureAccess("<key>"), but productAccess is only resolved for the keys in
// appData.ts tenantNavigationFeatureKeys. A key used in the sidebar but missing
// from that list silently hides the entry for ALL tenants/plans (happened to
// files+tasks → fixed in 1017cf58b, then again to dashboard/automation/leads/
// projects/hr/integrations/email-inbox/booking/forms → fixed 2026-07-07 after
// the R-P5 verification). This static contract makes the drift a test failure.
const sidebarSource = readFileSync(resolve(__dirname, "AppSidebar.tsx"), "utf8");
const appDataSource = readFileSync(resolve(__dirname, "../../utils/data/.server/appData.ts"), "utf8");
function usedSidebarFeatureKeys(): string[] {
const keys = new Set<string>();
for (const match of sidebarSource.matchAll(/hasFeatureAccess\("([^"]+)"\)/g)) {
keys.add(match[1]);
}
return [...keys].sort();
}
function resolvedNavigationFeatureKeys(): string[] {
const listMatch = appDataSource.match(/const tenantNavigationFeatureKeys = \[([\s\S]*?)\]/);
if (!listMatch) {
throw new Error("tenantNavigationFeatureKeys list not found in appData.ts");
}
const keys = new Set<string>();
for (const match of listMatch[1].matchAll(/featureKeys\.(\w+)/g)) {
const value = featureKeys[match[1] as keyof typeof featureKeys];
if (!value) {
throw new Error(`Unknown featureKeys entry in tenantNavigationFeatureKeys: ${match[1]}`);
}
keys.add(value);
}
return [...keys].sort();
}
describe("sidebar feature-access registry contract", () => {
test("every hasFeatureAccess key used by AppSidebar is resolved in appData", () => {
const used = usedSidebarFeatureKeys();
const resolved = resolvedNavigationFeatureKeys();
expect(used.length).toBeGreaterThan(0);
const missing = used.filter((key) => !resolved.includes(key));
expect(
missing,
`AppSidebar gates on feature keys that appData.ts tenantNavigationFeatureKeys does not resolve — those sidebar entries are invisible for every tenant. Add them to the list: ${missing.join(", ")}`
).toEqual([]);
});
test("all sidebar gate keys are valid entitlement feature keys", () => {
const validValues = new Set<string>(Object.values(featureKeys));
const invalid = usedSidebarFeatureKeys().filter((key) => !validValues.has(key as FeatureKey));
expect(invalid, `AppSidebar uses gate keys that are no entitlement feature keys: ${invalid.join(", ")}`).toEqual([]);
});
});