Files
sencho/frontend/src/lib/routing/reachability.test.ts
T
Anso c6d9fb98e5 fix(sso): enforce hub-only SSO config when remote node is active (#1865)
SSO configuration is control-plane state and must not follow the active
remote node. Add /api/sso/ to hub-only prefixes with case-insensitive
matching, hide the Settings section on remotes, and use localOnly on
every SSOSection fetch as defense in depth.
2026-08-29 20:10:26 -04:00

160 lines
6.3 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import {
authzReady,
isViewHidden,
isFleetTabHidden,
isSettingsSectionHidden,
normalizeHiddenView,
type ReachabilityContext,
} from './reachability';
function ctx(over: Partial<ReachabilityContext> = {}): ReachabilityContext {
return {
isAdmin: true,
isPaid: true,
can: () => true,
isRemote: false,
hasFleetCapability: true,
containerLabelsEnabled: true,
permissionsStatus: 'ready',
licenseStatus: 'ready',
experimental: false,
experimentalReady: true,
scheduledOpsAccessible: false,
...over,
};
}
describe('reachability', () => {
it('does not hide views while authz is loading or failed', () => {
const loading = ctx({
permissionsStatus: 'loading',
can: () => false,
isPaid: false,
});
expect(authzReady(loading)).toBe(false);
expect(isViewHidden('audit-log', loading)).toBe(false);
const failed = ctx({ permissionsStatus: 'error', can: () => false, isAdmin: false });
expect(authzReady(failed)).toBe(false);
expect(isViewHidden('fleet', failed)).toBe(false);
expect(normalizeHiddenView('fleet', failed)).toBe('fleet');
});
it('hides hub-only views on remote nodes when ready', () => {
const remote = ctx({ isRemote: true });
expect(isViewHidden('audit-log', remote)).toBe(true);
expect(normalizeHiddenView('audit-log', remote)).toBe('dashboard');
});
it('hides admin-only operator views for non-admins when ready', () => {
const viewer = ctx({ isAdmin: false });
expect(isViewHidden('global-observability', viewer)).toBe(true);
expect(isViewHidden('auto-updates', viewer)).toBe(true);
expect(isViewHidden('scheduled-ops', viewer)).toBe(true);
});
it('hides fleet and networking without node:read when ready', () => {
const noNodeRead = ctx({ can: () => false });
expect(isViewHidden('fleet', noNodeRead)).toBe(true);
expect(isViewHidden('networking', noNodeRead)).toBe(true);
});
it('gates host-console on system:console only (any tier, any experimental state)', () => {
const licenseError = ctx({ licenseStatus: 'error', can: (a) => a === 'system:console' });
expect(isViewHidden('host-console', licenseError)).toBe(false);
const noConsole = ctx({ can: () => false, isPaid: false, experimental: false });
expect(isViewHidden('host-console', noConsole)).toBe(true);
expect(normalizeHiddenView('host-console', noConsole)).toBe('dashboard');
const community = ctx({
isPaid: false,
experimental: false,
experimentalReady: true,
scheduledOpsAccessible: false,
can: (a) => a === 'system:console',
});
expect(isViewHidden('host-console', community)).toBe(false);
});
it('gates audit-log on system:audit only (Community and paid)', () => {
expect(
isViewHidden('audit-log', ctx({ isPaid: false, can: (a) => a === 'system:audit' })),
).toBe(false);
const noAuditCommunity = ctx({ isPaid: false, can: () => false });
expect(isViewHidden('audit-log', noAuditCommunity)).toBe(true);
expect(normalizeHiddenView('audit-log', noAuditCommunity)).toBe('dashboard');
expect(isViewHidden('audit-log', ctx({ isPaid: true, can: () => false }))).toBe(true);
});
it('hides routing fleet tab only after experimentalReady when off; secrets always visible for admin', () => {
const loading = ctx({ experimental: false, experimentalReady: false });
expect(isFleetTabHidden('routing', loading)).toBe(false);
expect(isFleetTabHidden('secrets', loading)).toBe(false);
const off = ctx({ experimental: false, experimentalReady: true });
expect(isFleetTabHidden('routing', off)).toBe(true);
expect(isFleetTabHidden('secrets', off)).toBe(false);
expect(isFleetTabHidden('deployments', off)).toBe(false);
expect(isFleetTabHidden('federation', off)).toBe(false);
expect(isFleetTabHidden('actions', off)).toBe(false);
});
it('hides secrets fleet tab for non-admin after authz ready', () => {
// Cold load: permissions not ready, don't hide yet (deep link survives)
const loading = ctx({ isAdmin: false, permissionsStatus: 'loading' });
expect(isFleetTabHidden('secrets', loading)).toBe(false);
// Permissions settled: non-admin deep link normalizes to overview
const ready = ctx({ isAdmin: false, permissionsStatus: 'ready' });
expect(isFleetTabHidden('secrets', ready)).toBe(true);
});
it('does not hide fleet-mesh settings for experimental off', () => {
const off = ctx({ experimental: false, experimentalReady: true,
scheduledOpsAccessible: false, isAdmin: true });
expect(isSettingsSectionHidden('fleet-mesh', off)).toBe(false);
});
it('defers settings permission hides until authz is ready', () => {
const loading = ctx({
permissionsStatus: 'loading',
isAdmin: false,
can: () => false,
});
expect(isSettingsSectionHidden('webhooks', loading)).toBe(false);
expect(isSettingsSectionHidden('license', loading)).toBe(false);
});
it('hides requiredPermission sections when the operator lacks the permission', () => {
const nodeAdmin = ctx({
isAdmin: false,
can: (a) => a === 'node:read' || a === 'node:manage',
});
expect(isSettingsSectionHidden('webhooks', nodeAdmin)).toBe(true);
expect(isSettingsSectionHidden('license', nodeAdmin)).toBe(true);
expect(isSettingsSectionHidden('users', nodeAdmin)).toBe(true);
expect(isSettingsSectionHidden('api-tokens', nodeAdmin)).toBe(true);
expect(isSettingsSectionHidden('registries', nodeAdmin)).toBe(true);
expect(isSettingsSectionHidden('nodes', nodeAdmin)).toBe(false);
expect(isSettingsSectionHidden('host-alerts', nodeAdmin)).toBe(false);
expect(isSettingsSectionHidden('developer', nodeAdmin)).toBe(true);
expect(isSettingsSectionHidden('data-retention', nodeAdmin)).toBe(true);
expect(isSettingsSectionHidden('image-updates', nodeAdmin)).toBe(true);
});
it('hides adminOnly settings sections for non-admins', () => {
const nodeAdmin = ctx({ isAdmin: false, can: () => true });
expect(isSettingsSectionHidden('sso', nodeAdmin)).toBe(true);
expect(isSettingsSectionHidden('recovery', nodeAdmin)).toBe(true);
});
it('hides SSO for admins when a remote node is active', () => {
const adminRemote = ctx({ isAdmin: true, isRemote: true });
expect(isSettingsSectionHidden('sso', adminRemote)).toBe(true);
});
});