tests fixes and sprints work
This commit is contained in:
322
src/Web/StellaOps.Web/tests/e2e/analytics-sbom-lake.spec.ts
Normal file
322
src/Web/StellaOps.Web/tests/e2e/analytics-sbom-lake.spec.ts
Normal file
@@ -0,0 +1,322 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// 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://authority.local',
|
||||
clientId: 'stellaops-ui',
|
||||
authorizeEndpoint: 'https://authority.local/connect/authorize',
|
||||
tokenEndpoint: 'https://authority.local/connect/token',
|
||||
logoutEndpoint: 'https://authority.local/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: 'https://authority.local',
|
||||
scanner: 'https://scanner.local',
|
||||
policy: 'https://scanner.local',
|
||||
concelier: 'https://concelier.local',
|
||||
attestor: 'https://attestor.local',
|
||||
},
|
||||
quickstartMode: true,
|
||||
};
|
||||
|
||||
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('https://authority.local/**', (route) => route.abort());
|
||||
};
|
||||
|
||||
test.describe('SBOM Lake Analytics Console', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await setupSession(page, analyticsSession);
|
||||
await setupAnalyticsMocks(page);
|
||||
});
|
||||
await page.addInitScript((session) => {
|
||||
try {
|
||||
window.sessionStorage.clear();
|
||||
} catch {
|
||||
// Ignore storage errors in restricted contexts.
|
||||
}
|
||||
(window as any).__stellaopsTestSession = session;
|
||||
}, analyticsSession);
|
||||
|
||||
await page.route('**/config.json', (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(mockConfig),
|
||||
})
|
||||
);
|
||||
|
||||
await page.route('https://authority.local/**', (route) => route.abort());
|
||||
|
||||
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)),
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
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('redirects when analytics scope is missing', async ({ page }) => {
|
||||
await page.goto('/analytics/sbom-lake');
|
||||
await expect(page).toHaveURL(/\/console\/profile/);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user