Files
ss-tools/frontend/src/lib/components/layout/__tests__/projectSections.test.ts
busya f0d7b79523 fix(rbac): single admin authority, gate privileged surfaces, enforce session on dual-auth
- is_admin flag is the sole admin authority; remove role-name fallbacks (dependencies, reports, tasks, agent lifecycle, security badge, frontend)
- normalize (resource, ACTION) across REST/MCP/catalog; strict admin role/permission parsing (400 on unknown)
- gate LLM provider CRUD/status/test on admin:settings; require auth on agent llm-status; quarantine listing requires scenario:automation MANAGE
- enforce logical-session revocation/idle in require_api_key_or_jwt
- last-admin lockout guards for user/role CRUD; block non-admin from granting is_admin
- frontend: flag-only admin, route permission map from the nav registry, write-control gating, 403 dedupe
- tests: orthogonal edge coverage (flag-only admin, lockout boundaries, session states, normalization)
2026-09-22 21:14:35 +03:00

63 lines
3.2 KiB
TypeScript

// #region Tests.ProjectSections.Guard [C:2] [TYPE Module] [SEMANTICS test, sidebar, features, manifest, guard]
// @SEMANTICS: tests, sidebar, features, manifest
// @BRIEF: Guard — nav feature-ids, the projectSections.json manifest, and what buildSidebarSections
// surfaces must stay in sync. Catches a raw requiredFeature string that bypassed the manifest
// and a manifest id that no nav node uses anymore.
// @LAYER Tests
// @RELATION DEPENDS_ON -> [EXT:frontend:SidebarNavigation]
// @RELATION BINDS_TO -> [Core.ConfigModels.FeaturesConfig]
// @TEST_INVARIANT: NavFeatureManifestSync -> VERIFIED_BY: nav_ids_match_manifest, admin_view_surfaces_manifest, every_node_has_context.
import { describe, it, expect } from "vitest";
import { buildSidebarSections, getTogglableFeatureNodes } from "../sidebarNavigation";
import manifest from "../projectSections.json";
const adminUser = { roles: [{ name: "Admin", is_admin: true, permissions: [] }] };
// #region Tests.ProjectSections.Guard.NavFeatureIdsFromBuiltSections [TYPE Function]
// @BRIEF: Collect feature ids surfaced by buildSidebarSections for an admin with all flags enabled.
function navFeatureIdsFromBuiltSections(): string[] {
const seen = new Set<string>();
for (const section of buildSidebarSections({ nav: {} }, adminUser, {})) {
for (const category of section.categories) {
if (category.requiredFeature) seen.add(category.requiredFeature);
for (const sub of category.subItems || []) {
if (sub.requiredFeature) seen.add(sub.requiredFeature);
}
}
}
return [...seen].sort();
}
// #endregion Tests.ProjectSections.Guard.NavFeatureIdsFromBuiltSections
describe("projectSections manifest guard", () => {
it("nav_ids_match_manifest: getTogglableFeatureNodes covers exactly the manifest ids", () => {
const fromRegistry = getTogglableFeatureNodes().map((node) => node.featureId).sort();
expect(fromRegistry).toEqual([...manifest.navFeatures].sort());
});
it("admin_view_surfaces_manifest: buildSidebarSections exposes every manifest flag", () => {
expect(navFeatureIdsFromBuiltSections()).toEqual([...manifest.navFeatures].sort());
});
it("every_node_has_context: each togglable node carries section and category context", () => {
const nodes = getTogglableFeatureNodes();
expect(nodes.length).toBeGreaterThan(0);
for (const node of nodes) {
expect(node.featureId, "featureId must be set").toBeTruthy();
expect(node.labelKey, `labelKey missing for ${node.featureId}`).toBeTruthy();
expect(node.sectionId, `sectionId missing for ${node.featureId}`).toBeTruthy();
expect(node.sectionLabelKey, `sectionLabelKey missing for ${node.featureId}`).toBeTruthy();
expect(node.categoryId, `categoryId missing for ${node.featureId}`).toBeTruthy();
expect(node.categoryLabelKey, `categoryLabelKey missing for ${node.featureId}`).toBeTruthy();
}
});
it("groups follow nav order: resources → operations → system", () => {
const nodes = getTogglableFeatureNodes();
const sectionOrder = [...new Set(nodes.map((node) => node.sectionId))];
expect(sectionOrder).toEqual(["resources", "operations", "system"]);
});
});
// #endregion Tests.ProjectSections.Guard