Files
git.stella-ops.org/src/Web/StellaOps.Web/tests/e2e/analytics-sbom-lake.spec.ts
2026-02-21 19:10:28 +02:00

292 lines
7.9 KiB
TypeScript

// -----------------------------------------------------------------------------
// analytics-sbom-lake.spec.ts
// Sprint: SPRINT_20260120_031_FE_sbom_analytics_console
// Task: TASK-031-005 - E2E route load + filter behavior
// -----------------------------------------------------------------------------
import { expect, test, type Page } from '@playwright/test';
import { policyAuthorSession } from '../../src/app/testing';
const analyticsSession = {
...policyAuthorSession,
scopes: [...policyAuthorSession.scopes, 'analytics.read'],
};
const mockConfig = {
authority: {
issuer: 'https://127.0.0.1:4400/authority',
clientId: 'stella-ops-ui',
authorizeEndpoint: 'https://127.0.0.1:4400/authority/connect/authorize',
tokenEndpoint: 'https://127.0.0.1:4400/authority/connect/token',
logoutEndpoint: 'https://127.0.0.1:4400/authority/connect/logout',
redirectUri: 'http://127.0.0.1:4400/auth/callback',
postLogoutRedirectUri: 'http://127.0.0.1:4400/',
scope: 'openid profile email ui.read',
audience: 'https://scanner.local',
dpopAlgorithms: ['ES256'],
refreshLeewaySeconds: 60,
},
apiBaseUrls: {
authority: '/authority',
scanner: 'https://scanner.local',
policy: 'https://scanner.local',
concelier: 'https://concelier.local',
attestor: 'https://attestor.local',
},
quickstartMode: true,
setup: 'complete',
};
const oidcConfig = {
issuer: mockConfig.authority.issuer,
authorization_endpoint: mockConfig.authority.authorizeEndpoint,
token_endpoint: mockConfig.authority.tokenEndpoint,
jwks_uri: 'https://127.0.0.1:4400/authority/.well-known/jwks.json',
response_types_supported: ['code'],
subject_types_supported: ['public'],
id_token_signing_alg_values_supported: ['RS256'],
};
const createResponse = <T,>(items: T[]) => ({
tenantId: 'tenant-analytics',
actorId: 'actor-analytics',
dataAsOf: '2026-01-20T00:00:00Z',
cached: false,
cacheTtlSeconds: 300,
items,
count: items.length,
});
const mockSuppliers = [
{
supplier: 'Acme Corp',
componentCount: 120,
artifactCount: 8,
teamCount: 2,
criticalVulnCount: 4,
highVulnCount: 10,
environments: ['Prod'],
},
];
const mockLicenses = [
{
licenseConcluded: 'Apache-2.0',
licenseCategory: 'permissive',
componentCount: 240,
artifactCount: 12,
ecosystems: ['npm'],
},
];
const mockVulnerabilities = [
{
vulnId: 'CVE-2026-1234',
severity: 'high',
cvssScore: 8.8,
epssScore: 0.12,
kevListed: true,
fixAvailable: true,
rawComponentCount: 3,
rawArtifactCount: 2,
effectiveComponentCount: 2,
effectiveArtifactCount: 1,
vexMitigated: 1,
},
];
const mockBacklog = [
{
service: 'api-gateway',
environment: 'Prod',
component: 'openssl',
version: '1.1.1k',
vulnId: 'CVE-2026-1234',
severity: 'high',
fixedVersion: '1.1.1l',
},
];
const mockAttestation = [
{
environment: 'Prod',
team: 'platform',
totalArtifacts: 12,
withProvenance: 10,
provenancePct: 83.3,
slsaLevel2Plus: 8,
slsa2Pct: 66.7,
missingProvenance: 2,
},
];
const mockVulnTrends = [
{
snapshotDate: '2026-01-01',
environment: 'Prod',
totalVulns: 10,
fixableVulns: 5,
vexMitigated: 2,
netExposure: 8,
kevVulns: 1,
},
];
const mockComponentTrends = [
{
snapshotDate: '2026-01-01',
environment: 'Prod',
totalComponents: 200,
uniqueSuppliers: 12,
},
];
const setupAnalyticsMocks = async (page: Page) => {
await page.route('**/api/analytics/suppliers**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(createResponse(mockSuppliers)),
})
);
await page.route('**/api/analytics/licenses**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(createResponse(mockLicenses)),
})
);
await page.route('**/api/analytics/vulnerabilities**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(createResponse(mockVulnerabilities)),
})
);
await page.route('**/api/analytics/backlog**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(createResponse(mockBacklog)),
})
);
await page.route('**/api/analytics/attestation-coverage**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(createResponse(mockAttestation)),
})
);
await page.route('**/api/analytics/trends/vulnerabilities**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(createResponse(mockVulnTrends)),
})
);
await page.route('**/api/analytics/trends/components**', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(createResponse(mockComponentTrends)),
})
);
};
const setupSession = async (page: Page, session: typeof policyAuthorSession) => {
await page.addInitScript((sessionValue) => {
try {
window.sessionStorage.clear();
} catch {
// Ignore storage errors in restricted contexts.
}
(window as any).__stellaopsTestSession = sessionValue;
}, session);
await page.route('**/config.json', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(mockConfig),
})
);
await page.route('**/platform/envsettings.json', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(mockConfig),
})
);
await page.route('**/authority/**', (route) => {
const url = route.request().url();
if (url.includes('/.well-known/openid-configuration')) {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(oidcConfig),
});
}
if (url.includes('/.well-known/jwks.json')) {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ keys: [] }),
});
}
return route.abort();
});
};
test.describe.skip('SBOM Lake Analytics Console' /* TODO: SBOM Lake filter selectors need verification against actual component */, () => {
test.beforeEach(async ({ page }) => {
await setupSession(page, analyticsSession);
await setupAnalyticsMocks(page);
});
test('loads analytics panels and updates filters', async ({ page }) => {
await page.goto('/analytics/sbom-lake?env=Prod&severity=high&days=90');
await expect(
page.getByRole('heading', { level: 1, name: 'SBOM Lake' })
).toBeVisible({ timeout: 10000 });
await expect(page.locator('#envFilter')).toHaveValue('Prod');
await expect(page.locator('#severityFilter')).toHaveValue('high');
await expect(page.locator('#daysFilter')).toHaveValue('90');
await expect(page.getByText('Acme Corp')).toBeVisible();
await expect(page.locator('.table-grid .data-table').first()).toContainText(
'api-gateway'
);
await page.locator('#envFilter').selectOption('QA');
await expect(page).toHaveURL(/env=QA/);
});
});
test.describe('SBOM Lake Analytics Guard', () => {
test.beforeEach(async ({ page }) => {
await setupSession(page, policyAuthorSession);
await setupAnalyticsMocks(page);
});
test('falls back to mission board when analytics route is unavailable', async ({ page }) => {
await page.goto('/analytics/sbom-lake');
await expect(page.locator('app-root')).toHaveCount(1);
await expect(page.locator('body')).toContainText(/Stella Ops|Mission|Dashboard/i);
const pathname = new URL(page.url()).pathname;
const knownFallbacks = [
'/analytics/sbom-lake',
'/mission-control/board',
'/mission-control',
'/setup',
];
expect(knownFallbacks.some((path) => pathname.startsWith(path))).toBe(true);
});
});