Preserve canonical policy and reachability QA routes
This commit is contained in:
@@ -401,16 +401,33 @@ function collectRouteParams(snapshot: ActivatedRouteSnapshot | null): Record<str
|
||||
}
|
||||
|
||||
function resolvePrimaryTab(currentUrl: string): DecisioningPrimaryTab {
|
||||
if (currentUrl.includes('/ops/policy/packs')) {
|
||||
if (currentUrl.includes('/ops/policy/packs') || currentUrl.includes('/ops/policy/baselines')) {
|
||||
return 'packs';
|
||||
}
|
||||
if (currentUrl.includes('/ops/policy/governance')) {
|
||||
if (
|
||||
currentUrl.includes('/ops/policy/governance') ||
|
||||
currentUrl.includes('/ops/policy/risk-budget') ||
|
||||
currentUrl.includes('/ops/policy/budget') ||
|
||||
currentUrl.includes('/ops/policy/trust-weights') ||
|
||||
currentUrl.includes('/ops/policy/staleness') ||
|
||||
currentUrl.includes('/ops/policy/sealed-mode') ||
|
||||
currentUrl.includes('/ops/policy/profiles') ||
|
||||
currentUrl.includes('/ops/policy/validator') ||
|
||||
currentUrl.includes('/ops/policy/conflicts') ||
|
||||
currentUrl.includes('/ops/policy/impact-preview') ||
|
||||
currentUrl.includes('/ops/policy/schema-playground') ||
|
||||
currentUrl.includes('/ops/policy/schema-docs')
|
||||
) {
|
||||
return 'governance';
|
||||
}
|
||||
if (currentUrl.includes('/ops/policy/simulation')) {
|
||||
return 'simulation';
|
||||
}
|
||||
if (currentUrl.includes('/ops/policy/vex')) {
|
||||
if (
|
||||
currentUrl.includes('/ops/policy/vex') ||
|
||||
currentUrl.includes('/ops/policy/waivers') ||
|
||||
currentUrl.includes('/ops/policy/exceptions')
|
||||
) {
|
||||
return 'vex';
|
||||
}
|
||||
if (currentUrl.includes('/ops/policy/gates')) {
|
||||
|
||||
@@ -22,20 +22,165 @@ export const policyDecisioningRoutes: Routes = [
|
||||
(m) => m.PolicyDecisioningOverviewPageComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'risk-budget',
|
||||
title: 'Policy Governance',
|
||||
loadComponent: () =>
|
||||
import('../policy-governance/risk-budget-dashboard.component').then(
|
||||
(m) => m.RiskBudgetDashboardComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'budget',
|
||||
title: 'Policy Governance',
|
||||
loadComponent: () =>
|
||||
import('../policy-governance/risk-budget-dashboard.component').then(
|
||||
(m) => m.RiskBudgetDashboardComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'risk-budget/config',
|
||||
title: 'Policy Governance',
|
||||
loadComponent: () =>
|
||||
import('../policy-governance/risk-budget-config.component').then(
|
||||
(m) => m.RiskBudgetConfigComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'budget/config',
|
||||
title: 'Policy Governance',
|
||||
loadComponent: () =>
|
||||
import('../policy-governance/risk-budget-config.component').then(
|
||||
(m) => m.RiskBudgetConfigComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'trust-weights',
|
||||
title: 'Policy Governance',
|
||||
loadComponent: () =>
|
||||
import('../policy-governance/trust-weighting.component').then(
|
||||
(m) => m.TrustWeightingComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'staleness',
|
||||
title: 'Policy Governance',
|
||||
loadComponent: () =>
|
||||
import('../policy-governance/staleness-config.component').then(
|
||||
(m) => m.StalenessConfigComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'sealed-mode',
|
||||
title: 'Policy Governance',
|
||||
loadComponent: () =>
|
||||
import('../policy-governance/sealed-mode-control.component').then(
|
||||
(m) => m.SealedModeControlComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'sealed-mode/overrides',
|
||||
title: 'Policy Governance',
|
||||
loadComponent: () =>
|
||||
import('../policy-governance/sealed-mode-overrides.component').then(
|
||||
(m) => m.SealedModeOverridesComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'profiles',
|
||||
title: 'Policy Governance',
|
||||
loadComponent: () =>
|
||||
import('../policy-governance/risk-profile-list.component').then(
|
||||
(m) => m.RiskProfileListComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'profiles/new',
|
||||
title: 'Policy Governance',
|
||||
loadComponent: () =>
|
||||
import('../policy-governance/risk-profile-editor.component').then(
|
||||
(m) => m.RiskProfileEditorComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'profiles/:profileId',
|
||||
title: 'Policy Governance',
|
||||
loadComponent: () =>
|
||||
import('../policy-governance/risk-profile-editor.component').then(
|
||||
(m) => m.RiskProfileEditorComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'validator',
|
||||
title: 'Policy Governance',
|
||||
loadComponent: () =>
|
||||
import('../policy-governance/policy-validator.component').then(
|
||||
(m) => m.PolicyValidatorComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'conflicts',
|
||||
title: 'Policy Governance',
|
||||
loadComponent: () =>
|
||||
import('../policy-governance/policy-conflict-dashboard.component').then(
|
||||
(m) => m.PolicyConflictDashboardComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'conflicts/:conflictId/resolve',
|
||||
title: 'Policy Governance',
|
||||
loadComponent: () =>
|
||||
import('../policy-governance/conflict-resolution-wizard.component').then(
|
||||
(m) => m.ConflictResolutionWizardComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'impact-preview',
|
||||
title: 'Policy Governance',
|
||||
loadComponent: () =>
|
||||
import('../policy-governance/impact-preview.component').then(
|
||||
(m) => m.ImpactPreviewComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'schema-playground',
|
||||
title: 'Policy Governance',
|
||||
loadComponent: () =>
|
||||
import('../policy-governance/schema-playground.component').then(
|
||||
(m) => m.SchemaPlaygroundComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'schema-docs',
|
||||
title: 'Policy Governance',
|
||||
loadComponent: () =>
|
||||
import('../policy-governance/schema-docs.component').then(
|
||||
(m) => m.SchemaDocsComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'baselines',
|
||||
pathMatch: 'full',
|
||||
redirectTo: 'overview',
|
||||
title: 'Policy Packs',
|
||||
loadComponent: () =>
|
||||
import('../policy-studio/workspace/policy-workspace.component').then(
|
||||
(m) => m.PolicyWorkspaceComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'waivers',
|
||||
pathMatch: 'full',
|
||||
redirectTo: 'vex/exceptions',
|
||||
title: 'VEX & Exceptions',
|
||||
loadComponent: () =>
|
||||
import('../exceptions/exception-dashboard.component').then(
|
||||
(m) => m.ExceptionDashboardComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'exceptions',
|
||||
pathMatch: 'full',
|
||||
redirectTo: 'vex/exceptions',
|
||||
title: 'VEX & Exceptions',
|
||||
loadComponent: () =>
|
||||
import('../exceptions/exception-dashboard.component').then(
|
||||
(m) => m.ExceptionDashboardComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: 'packs',
|
||||
|
||||
@@ -19,6 +19,23 @@ describe('policyDecisioningRoutes', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('preserves governance aliases as canonical top-level policy routes', () => {
|
||||
const routeByPath = new Map(children.map((route) => [route.path, route] as const));
|
||||
|
||||
expect(routeByPath.get('risk-budget')?.loadComponent).toEqual(jasmine.any(Function));
|
||||
expect(routeByPath.get('baselines')?.loadComponent).toEqual(jasmine.any(Function));
|
||||
expect(routeByPath.get('budget')?.loadComponent).toEqual(jasmine.any(Function));
|
||||
expect(routeByPath.get('risk-budget/config')?.loadComponent).toEqual(jasmine.any(Function));
|
||||
expect(routeByPath.get('budget/config')?.loadComponent).toEqual(jasmine.any(Function));
|
||||
expect(routeByPath.get('trust-weights')?.loadComponent).toEqual(jasmine.any(Function));
|
||||
expect(routeByPath.get('staleness')?.loadComponent).toEqual(jasmine.any(Function));
|
||||
expect(routeByPath.get('sealed-mode')?.loadComponent).toEqual(jasmine.any(Function));
|
||||
expect(routeByPath.get('profiles')?.loadComponent).toEqual(jasmine.any(Function));
|
||||
expect(routeByPath.get('validator')?.loadComponent).toEqual(jasmine.any(Function));
|
||||
expect(routeByPath.get('waivers')?.loadComponent).toEqual(jasmine.any(Function));
|
||||
expect(routeByPath.get('exceptions')?.loadComponent).toEqual(jasmine.any(Function));
|
||||
});
|
||||
|
||||
it('keeps pack authoring subviews inside the packs shell', () => {
|
||||
const packsRoute = children.find((route) => route.path === 'packs');
|
||||
const packPaths = packsRoute?.children?.map((route) => route.path) ?? [];
|
||||
|
||||
@@ -100,6 +100,25 @@ describe('PolicyDecisioningShellComponent', () => {
|
||||
|
||||
expect(navigateByUrlSpy).toHaveBeenCalledWith('/releases/rel-42');
|
||||
});
|
||||
|
||||
it('treats preserved governance aliases as governance tab routes', () => {
|
||||
const fixture = createShell('/ops/policy/risk-budget');
|
||||
const component = fixture.componentInstance;
|
||||
|
||||
expect(component.shellState().kind).toBe('global');
|
||||
expect(component.shellState().activeTab).toBe('governance');
|
||||
expect(component.primaryTabs().find((tab) => tab.id === 'governance')?.route).toEqual([
|
||||
'/ops/policy/governance',
|
||||
]);
|
||||
});
|
||||
|
||||
it('treats preserved baseline and waiver aliases as first-class shell tabs', () => {
|
||||
const packsFixture = createShell('/ops/policy/baselines');
|
||||
expect(packsFixture.componentInstance.shellState().activeTab).toBe('packs');
|
||||
|
||||
const vexFixture = createShell('/ops/policy/waivers');
|
||||
expect(vexFixture.componentInstance.shellState().activeTab).toBe('vex');
|
||||
});
|
||||
});
|
||||
|
||||
function buildSnapshot(
|
||||
|
||||
@@ -193,7 +193,10 @@ const strictRouteExpectations: Partial<Record<(typeof canonicalRoutes)[number],
|
||||
},
|
||||
'/security/reachability': {
|
||||
title: /Reachability/i,
|
||||
texts: ['Reachability Center'],
|
||||
texts: [
|
||||
'Coverage, witnesses, proof-of-exposure artifacts, and sensor gaps stay in one investigation shell.',
|
||||
'Healthy assets',
|
||||
],
|
||||
},
|
||||
'/setup/trust-signing': {
|
||||
title: /Trust/i,
|
||||
@@ -213,15 +216,21 @@ const strictRouteExpectations: Partial<Record<(typeof canonicalRoutes)[number],
|
||||
},
|
||||
'/ops/policy': {
|
||||
title: /Policy/i,
|
||||
texts: ['Policy Governance', 'Risk Budget Overview'],
|
||||
texts: [
|
||||
'Policy Decisioning Studio',
|
||||
'Decisioning Studio now owns policy packs, governance, simulation, VEX conflicts, exceptions, gate review, and audit.',
|
||||
],
|
||||
},
|
||||
'/ops/policy/overview': {
|
||||
title: /Policy/i,
|
||||
texts: ['Policy Governance', 'Risk Budget Overview'],
|
||||
texts: [
|
||||
'Policy Decisioning Studio',
|
||||
'Decisioning Studio now owns policy packs, governance, simulation, VEX conflicts, exceptions, gate review, and audit.',
|
||||
],
|
||||
},
|
||||
'/ops/policy/risk-budget': {
|
||||
title: /Policy/i,
|
||||
texts: ['Policy Governance', 'Risk Budget Overview'],
|
||||
texts: ['Policy Decisioning Studio', 'Risk Budget Overview'],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1602,13 +1611,15 @@ test.describe('Pre-alpha key end-user interactions', () => {
|
||||
await expect(page.locator('#main-content')).toContainText('Attestation Coverage Metrics');
|
||||
});
|
||||
|
||||
test('mission board reachability card opens Reachability Center', async ({ page }) => {
|
||||
test('mission board reachability card opens Reachability workspace', async ({ page }) => {
|
||||
await page.goto('/mission-control/board', { waitUntil: 'domcontentloaded' });
|
||||
await page.locator('#main-content a[href="/security/reachability"]').first().click();
|
||||
|
||||
await expect(page).toHaveURL(/\/security\/reachability$/);
|
||||
await expect(page).toHaveTitle(/Reachability/i);
|
||||
await expect(page.locator('#main-content')).toContainText('Reachability Center');
|
||||
await expect(page.locator('#main-content')).toContainText(
|
||||
'Coverage, witnesses, proof-of-exposure artifacts, and sensor gaps stay in one investigation shell.'
|
||||
);
|
||||
});
|
||||
|
||||
test('setup trust-signing tabs stay under setup routes', async ({ page }) => {
|
||||
|
||||
Reference in New Issue
Block a user