test fixes and new product advisories work
This commit is contained in:
762
src/Web/StellaOps.Web/tests/e2e/doctor-registry.spec.ts
Normal file
762
src/Web/StellaOps.Web/tests/e2e/doctor-registry.spec.ts
Normal file
@@ -0,0 +1,762 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// doctor-registry.spec.ts
|
||||
// Sprint: SPRINT_0127_001_0002_oci_registry_compatibility
|
||||
// Tasks: REG-UI-01
|
||||
// Description: E2E tests for Doctor Registry UI components
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
import { expect, test, type Page } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* E2E Tests for Doctor Registry UI Components
|
||||
* Task REG-UI-01: Registry health card, capability matrix, check details
|
||||
*/
|
||||
|
||||
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 doctor:read',
|
||||
audience: 'https://doctor.local',
|
||||
},
|
||||
apiBaseUrls: {
|
||||
authority: 'https://authority.local',
|
||||
doctor: 'https://doctor.local',
|
||||
},
|
||||
quickstartMode: true,
|
||||
};
|
||||
|
||||
// Mock Doctor report with registry check results
|
||||
const mockDoctorReport = {
|
||||
runId: 'run-registry-001',
|
||||
status: 'completed',
|
||||
startedAt: '2026-01-27T10:00:00Z',
|
||||
completedAt: '2026-01-27T10:01:30Z',
|
||||
durationMs: 90000,
|
||||
summary: {
|
||||
passed: 4,
|
||||
info: 1,
|
||||
warnings: 2,
|
||||
failed: 1,
|
||||
skipped: 0,
|
||||
total: 8,
|
||||
},
|
||||
overallSeverity: 'fail',
|
||||
results: [
|
||||
// Harbor Registry - healthy
|
||||
{
|
||||
checkId: 'integration.registry.v2-endpoint',
|
||||
pluginId: 'integration.registry',
|
||||
category: 'integration',
|
||||
severity: 'pass',
|
||||
diagnosis: 'V2 endpoint accessible and responding correctly',
|
||||
evidence: {
|
||||
description: 'Registry V2 API probe results',
|
||||
data: {
|
||||
registry_url: 'https://harbor.example.com',
|
||||
registry_name: 'Harbor Production',
|
||||
status_code: '200',
|
||||
response_time_ms: '45',
|
||||
server_header: 'Harbor',
|
||||
},
|
||||
},
|
||||
durationMs: 150,
|
||||
executedAt: '2026-01-27T10:00:05Z',
|
||||
},
|
||||
{
|
||||
checkId: 'integration.registry.auth-config',
|
||||
pluginId: 'integration.registry',
|
||||
category: 'integration',
|
||||
severity: 'pass',
|
||||
diagnosis: 'Authentication configured correctly',
|
||||
evidence: {
|
||||
description: 'Authentication validation results',
|
||||
data: {
|
||||
registry_url: 'https://harbor.example.com',
|
||||
auth_method: 'bearer',
|
||||
token_valid: 'true',
|
||||
},
|
||||
},
|
||||
durationMs: 85,
|
||||
executedAt: '2026-01-27T10:00:10Z',
|
||||
},
|
||||
{
|
||||
checkId: 'integration.registry.referrers-api',
|
||||
pluginId: 'integration.registry',
|
||||
category: 'integration',
|
||||
severity: 'pass',
|
||||
diagnosis: 'OCI 1.1 Referrers API fully supported',
|
||||
evidence: {
|
||||
description: 'Referrers API probe results',
|
||||
data: {
|
||||
registry_url: 'https://harbor.example.com',
|
||||
referrers_supported: 'true',
|
||||
api_version: 'OCI 1.1',
|
||||
},
|
||||
},
|
||||
durationMs: 200,
|
||||
executedAt: '2026-01-27T10:00:15Z',
|
||||
},
|
||||
// Generic OCI Registry - degraded (no referrers API)
|
||||
{
|
||||
checkId: 'integration.registry.v2-endpoint',
|
||||
pluginId: 'integration.registry',
|
||||
category: 'integration',
|
||||
severity: 'pass',
|
||||
diagnosis: 'V2 endpoint accessible',
|
||||
evidence: {
|
||||
description: 'Registry V2 API probe results',
|
||||
data: {
|
||||
registry_url: 'https://registry.example.com',
|
||||
registry_name: 'Generic OCI Registry',
|
||||
status_code: '200',
|
||||
response_time_ms: '120',
|
||||
},
|
||||
},
|
||||
durationMs: 180,
|
||||
executedAt: '2026-01-27T10:00:30Z',
|
||||
},
|
||||
{
|
||||
checkId: 'integration.registry.referrers-api',
|
||||
pluginId: 'integration.registry',
|
||||
category: 'integration',
|
||||
severity: 'warn',
|
||||
diagnosis: 'Referrers API not supported, using tag-based fallback',
|
||||
evidence: {
|
||||
description: 'Referrers API probe results',
|
||||
data: {
|
||||
registry_url: 'https://registry.example.com',
|
||||
referrers_supported: 'false',
|
||||
fallback_required: 'true',
|
||||
http_status: '404',
|
||||
},
|
||||
},
|
||||
likelyCauses: [
|
||||
'Registry does not support OCI Distribution Spec 1.1',
|
||||
'Referrers API endpoint not implemented',
|
||||
],
|
||||
remediation: {
|
||||
requiresBackup: false,
|
||||
steps: [
|
||||
{
|
||||
order: 1,
|
||||
description: 'Upgrade registry to a version supporting OCI 1.1',
|
||||
command: 'helm upgrade registry oci-registry --version 2.0.0',
|
||||
commandType: 'shell',
|
||||
},
|
||||
],
|
||||
},
|
||||
durationMs: 250,
|
||||
executedAt: '2026-01-27T10:00:45Z',
|
||||
},
|
||||
// Broken Registry - unhealthy
|
||||
{
|
||||
checkId: 'integration.registry.v2-endpoint',
|
||||
pluginId: 'integration.registry',
|
||||
category: 'integration',
|
||||
severity: 'fail',
|
||||
diagnosis: 'V2 endpoint unreachable - connection refused',
|
||||
evidence: {
|
||||
description: 'Registry V2 API probe results',
|
||||
data: {
|
||||
registry_url: 'https://broken.example.com',
|
||||
registry_name: 'Broken Registry',
|
||||
error: 'Connection refused',
|
||||
error_code: 'ECONNREFUSED',
|
||||
},
|
||||
},
|
||||
likelyCauses: [
|
||||
'Registry service is not running',
|
||||
'Firewall blocking connection',
|
||||
'Incorrect registry URL',
|
||||
],
|
||||
remediation: {
|
||||
requiresBackup: false,
|
||||
steps: [
|
||||
{
|
||||
order: 1,
|
||||
description: 'Verify registry service is running',
|
||||
command: 'docker ps | grep registry',
|
||||
commandType: 'shell',
|
||||
},
|
||||
{
|
||||
order: 2,
|
||||
description: 'Check firewall rules',
|
||||
command: 'iptables -L -n | grep 5000',
|
||||
commandType: 'shell',
|
||||
},
|
||||
],
|
||||
},
|
||||
durationMs: 3000,
|
||||
executedAt: '2026-01-27T10:01:00Z',
|
||||
},
|
||||
// Capability check - info severity
|
||||
{
|
||||
checkId: 'integration.registry.capabilities',
|
||||
pluginId: 'integration.registry',
|
||||
category: 'integration',
|
||||
severity: 'info',
|
||||
diagnosis: 'Registry capability matrix generated',
|
||||
evidence: {
|
||||
description: 'OCI capability probe results',
|
||||
data: {
|
||||
registry_url: 'https://harbor.example.com',
|
||||
supports_chunked_upload: 'true',
|
||||
supports_cross_repo_mount: 'true',
|
||||
supports_manifest_delete: 'true',
|
||||
supports_blob_delete: 'true',
|
||||
capability_score: '6/7',
|
||||
},
|
||||
},
|
||||
durationMs: 500,
|
||||
executedAt: '2026-01-27T10:01:15Z',
|
||||
},
|
||||
// TLS certificate warning
|
||||
{
|
||||
checkId: 'integration.registry.tls-cert',
|
||||
pluginId: 'integration.registry',
|
||||
category: 'integration',
|
||||
severity: 'warn',
|
||||
diagnosis: 'TLS certificate expires in 14 days',
|
||||
evidence: {
|
||||
description: 'TLS certificate validation results',
|
||||
data: {
|
||||
registry_url: 'https://registry.example.com',
|
||||
expires_at: '2026-02-10T00:00:00Z',
|
||||
days_remaining: '14',
|
||||
issuer: "Let's Encrypt",
|
||||
},
|
||||
},
|
||||
likelyCauses: ['Certificate renewal not configured', 'Certbot job failed'],
|
||||
remediation: {
|
||||
requiresBackup: false,
|
||||
steps: [
|
||||
{
|
||||
order: 1,
|
||||
description: 'Renew certificate',
|
||||
command: 'certbot renew --quiet',
|
||||
commandType: 'shell',
|
||||
},
|
||||
],
|
||||
},
|
||||
durationMs: 100,
|
||||
executedAt: '2026-01-27T10:01:20Z',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mockPlugins = {
|
||||
plugins: [
|
||||
{
|
||||
pluginId: 'integration.registry',
|
||||
displayName: 'Registry Integration',
|
||||
category: 'integration',
|
||||
version: '1.0.0',
|
||||
checkCount: 5,
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
};
|
||||
|
||||
const mockChecks = {
|
||||
checks: [
|
||||
{
|
||||
checkId: 'integration.registry.v2-endpoint',
|
||||
name: 'V2 Endpoint Check',
|
||||
description: 'Verify OCI registry V2 API endpoint accessibility',
|
||||
pluginId: 'integration.registry',
|
||||
category: 'integration',
|
||||
defaultSeverity: 'fail',
|
||||
tags: ['registry', 'oci', 'connectivity'],
|
||||
estimatedDurationMs: 5000,
|
||||
},
|
||||
{
|
||||
checkId: 'integration.registry.auth-config',
|
||||
name: 'Authentication Config',
|
||||
description: 'Validate registry authentication configuration',
|
||||
pluginId: 'integration.registry',
|
||||
category: 'integration',
|
||||
defaultSeverity: 'fail',
|
||||
tags: ['registry', 'oci', 'auth'],
|
||||
estimatedDurationMs: 3000,
|
||||
},
|
||||
{
|
||||
checkId: 'integration.registry.referrers-api',
|
||||
name: 'Referrers API Support',
|
||||
description: 'Detect OCI 1.1 Referrers API support',
|
||||
pluginId: 'integration.registry',
|
||||
category: 'integration',
|
||||
defaultSeverity: 'warn',
|
||||
tags: ['registry', 'oci', 'referrers', 'oci-1.1'],
|
||||
estimatedDurationMs: 5000,
|
||||
},
|
||||
{
|
||||
checkId: 'integration.registry.capabilities',
|
||||
name: 'Capability Probe',
|
||||
description: 'Probe registry OCI capabilities',
|
||||
pluginId: 'integration.registry',
|
||||
category: 'integration',
|
||||
defaultSeverity: 'info',
|
||||
tags: ['registry', 'oci', 'capabilities'],
|
||||
estimatedDurationMs: 10000,
|
||||
},
|
||||
{
|
||||
checkId: 'integration.registry.tls-cert',
|
||||
name: 'TLS Certificate',
|
||||
description: 'Validate TLS certificate validity',
|
||||
pluginId: 'integration.registry',
|
||||
category: 'integration',
|
||||
defaultSeverity: 'warn',
|
||||
tags: ['registry', 'tls', 'security'],
|
||||
estimatedDurationMs: 2000,
|
||||
},
|
||||
],
|
||||
total: 5,
|
||||
};
|
||||
|
||||
test.describe('REG-UI-01: Doctor Registry Health Card', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await setupBasicMocks(page);
|
||||
await setupAuthenticatedSession(page);
|
||||
await setupDoctorMocks(page);
|
||||
});
|
||||
|
||||
test('registry health panel displays after doctor run', async ({ page }) => {
|
||||
await page.goto('/doctor');
|
||||
|
||||
// Wait for doctor page to load
|
||||
await expect(page.getByRole('heading', { name: /doctor/i })).toBeVisible({ timeout: 10000 });
|
||||
|
||||
// Registry health section should be visible after results load
|
||||
const registrySection = page.locator('text=/registry.*health|configured.*registries/i');
|
||||
if ((await registrySection.count()) > 0) {
|
||||
await expect(registrySection.first()).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
});
|
||||
|
||||
test('registry cards show health indicators', async ({ page }) => {
|
||||
await page.goto('/doctor');
|
||||
|
||||
// Look for health status indicators (healthy/degraded/unhealthy)
|
||||
const healthIndicators = page.locator(
|
||||
'text=/healthy|degraded|unhealthy|pass|warn|fail/i'
|
||||
);
|
||||
|
||||
if ((await healthIndicators.count()) > 0) {
|
||||
await expect(healthIndicators.first()).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
});
|
||||
|
||||
test('registry cards display registry names', async ({ page }) => {
|
||||
await page.goto('/doctor');
|
||||
|
||||
// Check for registry names from mock data
|
||||
const harborRegistry = page.getByText(/harbor.*production|harbor\.example\.com/i);
|
||||
if ((await harborRegistry.count()) > 0) {
|
||||
await expect(harborRegistry.first()).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
});
|
||||
|
||||
test('clicking registry card shows details', async ({ page }) => {
|
||||
await page.goto('/doctor');
|
||||
|
||||
// Find and click a registry card
|
||||
const registryCard = page.locator('[class*="registry-card"], [class*="health-card"]').first();
|
||||
|
||||
if (await registryCard.isVisible({ timeout: 5000 }).catch(() => false)) {
|
||||
await registryCard.click();
|
||||
|
||||
// Details panel should appear
|
||||
const detailsPanel = page.locator(
|
||||
'[class*="details"], [class*="check-details"], [class*="registry-details"]'
|
||||
);
|
||||
if ((await detailsPanel.count()) > 0) {
|
||||
await expect(detailsPanel.first()).toBeVisible({ timeout: 5000 });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('REG-UI-01: Doctor Registry Capability Matrix', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await setupBasicMocks(page);
|
||||
await setupAuthenticatedSession(page);
|
||||
await setupDoctorMocks(page);
|
||||
});
|
||||
|
||||
test('capability matrix displays after doctor run', async ({ page }) => {
|
||||
await page.goto('/doctor');
|
||||
|
||||
// Look for capability matrix
|
||||
const capabilityMatrix = page.locator(
|
||||
'text=/capability.*matrix|oci.*capabilities/i, [class*="capability-matrix"]'
|
||||
);
|
||||
|
||||
if ((await capabilityMatrix.count()) > 0) {
|
||||
await expect(capabilityMatrix.first()).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
});
|
||||
|
||||
test('capability matrix shows OCI features', async ({ page }) => {
|
||||
await page.goto('/doctor');
|
||||
|
||||
// Check for OCI capability names
|
||||
const ociFeatures = [
|
||||
/v2.*endpoint|v2.*api/i,
|
||||
/referrers.*api|referrers/i,
|
||||
/chunked.*upload/i,
|
||||
/manifest.*delete/i,
|
||||
];
|
||||
|
||||
for (const feature of ociFeatures) {
|
||||
const featureElement = page.locator(`text=${feature.source}`);
|
||||
if ((await featureElement.count()) > 0) {
|
||||
// At least one OCI feature should be visible
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('capability matrix shows supported/unsupported indicators', async ({ page }) => {
|
||||
await page.goto('/doctor');
|
||||
|
||||
// Look for checkmark/x indicators or supported/unsupported text
|
||||
const indicators = page.locator(
|
||||
'text=/supported|unsupported|partial|✓|✗|yes|no/i'
|
||||
);
|
||||
|
||||
if ((await indicators.count()) > 0) {
|
||||
await expect(indicators.first()).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
});
|
||||
|
||||
test('capability rows are expandable', async ({ page }) => {
|
||||
await page.goto('/doctor');
|
||||
|
||||
// Find expandable capability row
|
||||
const expandableRow = page.locator(
|
||||
'[class*="capability-row"], [class*="expandable"], tr[class*="capability"]'
|
||||
).first();
|
||||
|
||||
if (await expandableRow.isVisible({ timeout: 5000 }).catch(() => false)) {
|
||||
await expandableRow.click();
|
||||
|
||||
// Description should appear
|
||||
const description = page.locator('[class*="description"], [class*="expanded"]');
|
||||
if ((await description.count()) > 0) {
|
||||
await expect(description.first()).toBeVisible({ timeout: 3000 });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('REG-UI-01: Doctor Registry Check Details', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await setupBasicMocks(page);
|
||||
await setupAuthenticatedSession(page);
|
||||
await setupDoctorMocks(page);
|
||||
});
|
||||
|
||||
test('check results display for registry checks', async ({ page }) => {
|
||||
await page.goto('/doctor');
|
||||
|
||||
// Look for check results
|
||||
const checkResults = page.locator(
|
||||
'[class*="check-result"], [class*="check-item"], text=/integration\.registry/i'
|
||||
);
|
||||
|
||||
if ((await checkResults.count()) > 0) {
|
||||
await expect(checkResults.first()).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
});
|
||||
|
||||
test('check results show severity indicators', async ({ page }) => {
|
||||
await page.goto('/doctor');
|
||||
|
||||
// Look for severity badges/icons
|
||||
const severityIndicators = page.locator(
|
||||
'[class*="severity"], [class*="pass"], [class*="warn"], [class*="fail"]'
|
||||
);
|
||||
|
||||
if ((await severityIndicators.count()) > 0) {
|
||||
await expect(severityIndicators.first()).toBeVisible({ timeout: 10000 });
|
||||
}
|
||||
});
|
||||
|
||||
test('expanding check shows evidence', async ({ page }) => {
|
||||
await page.goto('/doctor');
|
||||
|
||||
// Find and click a check result
|
||||
const checkResult = page.locator(
|
||||
'[class*="check-result"], [class*="check-item"]'
|
||||
).first();
|
||||
|
||||
if (await checkResult.isVisible({ timeout: 5000 }).catch(() => false)) {
|
||||
await checkResult.click();
|
||||
|
||||
// Evidence section should appear
|
||||
const evidence = page.locator(
|
||||
'[class*="evidence"], text=/evidence|registry_url|status_code/i'
|
||||
);
|
||||
|
||||
if ((await evidence.count()) > 0) {
|
||||
await expect(evidence.first()).toBeVisible({ timeout: 5000 });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('failed checks show remediation steps', async ({ page }) => {
|
||||
await page.goto('/doctor');
|
||||
|
||||
// Look for failed check
|
||||
const failedCheck = page.locator('[class*="fail"], [class*="severity-fail"]').first();
|
||||
|
||||
if (await failedCheck.isVisible({ timeout: 5000 }).catch(() => false)) {
|
||||
await failedCheck.click();
|
||||
|
||||
// Remediation should be visible
|
||||
const remediation = page.locator(
|
||||
'text=/remediation|steps|command|verify|check/i'
|
||||
);
|
||||
|
||||
if ((await remediation.count()) > 0) {
|
||||
await expect(remediation.first()).toBeVisible({ timeout: 5000 });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('evidence displays key-value pairs', async ({ page }) => {
|
||||
await page.goto('/doctor');
|
||||
|
||||
// Find and expand a check
|
||||
const checkResult = page.locator('[class*="check-result"], [class*="check-item"]').first();
|
||||
|
||||
if (await checkResult.isVisible({ timeout: 5000 }).catch(() => false)) {
|
||||
await checkResult.click();
|
||||
|
||||
// Evidence data should show key-value pairs
|
||||
const evidenceKeys = ['registry_url', 'status_code', 'response_time'];
|
||||
for (const key of evidenceKeys) {
|
||||
const keyElement = page.locator(`text=/${key}/i`);
|
||||
if ((await keyElement.count()) > 0) {
|
||||
// At least one evidence key should be present
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('REG-UI-01: Doctor Registry Integration', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await setupBasicMocks(page);
|
||||
await setupAuthenticatedSession(page);
|
||||
await setupDoctorMocks(page);
|
||||
});
|
||||
|
||||
test('running doctor shows registry checks in progress', async ({ page }) => {
|
||||
// Mock SSE for progress updates
|
||||
await page.route('**/api/doctor/runs/*/progress*', (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'text/event-stream',
|
||||
body: `data: {"eventType":"check-started","checkId":"integration.registry.v2-endpoint","completed":0,"total":5}\n\n`,
|
||||
})
|
||||
);
|
||||
|
||||
await page.goto('/doctor');
|
||||
|
||||
// Click run button if visible
|
||||
const runButton = page.getByRole('button', { name: /run|check|quick|normal|full/i });
|
||||
if (await runButton.first().isVisible({ timeout: 5000 }).catch(() => false)) {
|
||||
// Don't actually run - just verify button exists
|
||||
await expect(runButton.first()).toBeEnabled();
|
||||
}
|
||||
});
|
||||
|
||||
test('registry filter shows only registry checks', async ({ page }) => {
|
||||
await page.goto('/doctor');
|
||||
|
||||
// Look for category filter
|
||||
const categoryFilter = page.locator(
|
||||
'select[id*="category"], [class*="filter"] select, [class*="category-filter"]'
|
||||
);
|
||||
|
||||
if (await categoryFilter.isVisible({ timeout: 5000 }).catch(() => false)) {
|
||||
// Select integration category
|
||||
await categoryFilter.selectOption({ label: /integration/i });
|
||||
|
||||
// Should filter to registry checks
|
||||
const registryChecks = page.locator('text=/integration\.registry/i');
|
||||
if ((await registryChecks.count()) > 0) {
|
||||
await expect(registryChecks.first()).toBeVisible({ timeout: 5000 });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('severity filter highlights failed registry checks', async ({ page }) => {
|
||||
await page.goto('/doctor');
|
||||
|
||||
// Look for severity filter
|
||||
const failFilter = page.locator(
|
||||
'input[type="checkbox"][id*="fail"], label:has-text("fail") input, [class*="severity-fail"] input'
|
||||
);
|
||||
|
||||
if (await failFilter.first().isVisible({ timeout: 5000 }).catch(() => false)) {
|
||||
await failFilter.first().check();
|
||||
|
||||
// Should show only failed checks
|
||||
const failedChecks = page.locator('[class*="severity-fail"], [class*="fail"]');
|
||||
if ((await failedChecks.count()) > 0) {
|
||||
await expect(failedChecks.first()).toBeVisible({ timeout: 5000 });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('health summary shows correct counts', async ({ page }) => {
|
||||
await page.goto('/doctor');
|
||||
|
||||
// Look for health summary counts
|
||||
const summarySection = page.locator('[class*="summary"], [class*="health-summary"]');
|
||||
|
||||
if (await summarySection.isVisible({ timeout: 5000 }).catch(() => false)) {
|
||||
// Should show counts from mock data
|
||||
// 1 healthy (Harbor), 1 degraded (Generic OCI), 1 unhealthy (Broken)
|
||||
const healthyCount = page.locator('text=/healthy.*[0-9]|[0-9].*healthy/i');
|
||||
const unhealthyCount = page.locator('text=/unhealthy.*[0-9]|[0-9].*unhealthy/i');
|
||||
|
||||
if ((await healthyCount.count()) > 0) {
|
||||
await expect(healthyCount.first()).toBeVisible();
|
||||
}
|
||||
if ((await unhealthyCount.count()) > 0) {
|
||||
await expect(unhealthyCount.first()).toBeVisible();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Helper functions
|
||||
|
||||
async function setupBasicMocks(page: Page) {
|
||||
page.on('console', (message) => {
|
||||
if (message.type() === 'error') {
|
||||
console.log('[browser:error]', message.text());
|
||||
}
|
||||
});
|
||||
|
||||
await page.route('**/config.json', (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(mockConfig),
|
||||
})
|
||||
);
|
||||
|
||||
// Block actual auth requests
|
||||
await page.route('https://authority.local/**', (route) => {
|
||||
if (route.request().url().includes('authorize')) {
|
||||
return route.abort();
|
||||
}
|
||||
return route.fulfill({ status: 400, body: 'blocked' });
|
||||
});
|
||||
}
|
||||
|
||||
async function setupAuthenticatedSession(page: Page) {
|
||||
const mockToken = {
|
||||
access_token: 'mock-doctor-access-token',
|
||||
id_token: 'mock-id-token',
|
||||
token_type: 'Bearer',
|
||||
expires_in: 3600,
|
||||
scope: 'openid profile email doctor:read',
|
||||
};
|
||||
|
||||
await page.addInitScript((tokenData) => {
|
||||
(window as any).__stellaopsTestSession = {
|
||||
isAuthenticated: true,
|
||||
accessToken: tokenData.access_token,
|
||||
idToken: tokenData.id_token,
|
||||
expiresAt: Date.now() + tokenData.expires_in * 1000,
|
||||
};
|
||||
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = function (input: RequestInfo | URL, init?: RequestInit) {
|
||||
const headers = new Headers(init?.headers);
|
||||
if (!headers.has('Authorization')) {
|
||||
headers.set('Authorization', `Bearer ${tokenData.access_token}`);
|
||||
}
|
||||
return originalFetch(input, { ...init, headers });
|
||||
};
|
||||
}, mockToken);
|
||||
}
|
||||
|
||||
async function setupDoctorMocks(page: Page) {
|
||||
// Mock Doctor plugins list
|
||||
await page.route('**/api/doctor/plugins*', (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(mockPlugins),
|
||||
})
|
||||
);
|
||||
|
||||
// Mock Doctor checks list
|
||||
await page.route('**/api/doctor/checks*', (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(mockChecks),
|
||||
})
|
||||
);
|
||||
|
||||
// Mock start run
|
||||
await page.route('**/api/doctor/runs', (route) => {
|
||||
if (route.request().method() === 'POST') {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ runId: mockDoctorReport.runId }),
|
||||
});
|
||||
}
|
||||
return route.continue();
|
||||
});
|
||||
|
||||
// Mock get run result
|
||||
await page.route('**/api/doctor/runs/*', (route) => {
|
||||
if (route.request().method() === 'GET') {
|
||||
return route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(mockDoctorReport),
|
||||
});
|
||||
}
|
||||
return route.continue();
|
||||
});
|
||||
|
||||
// Mock latest report endpoint
|
||||
await page.route('**/api/doctor/reports/latest*', (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify(mockDoctorReport),
|
||||
})
|
||||
);
|
||||
|
||||
// Mock dashboard data if Doctor is on dashboard
|
||||
await page.route('**/api/dashboard*', (route) =>
|
||||
route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
doctor: {
|
||||
lastRun: mockDoctorReport.completedAt,
|
||||
summary: mockDoctorReport.summary,
|
||||
},
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user