Add comprehensive security tests for OWASP A02, A05, A07, and A08 categories
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled
- Implemented tests for Cryptographic Failures (A02) to ensure proper handling of sensitive data, secure algorithms, and key management. - Added tests for Security Misconfiguration (A05) to validate production configurations, security headers, CORS settings, and feature management. - Developed tests for Authentication Failures (A07) to enforce strong password policies, rate limiting, session management, and MFA support. - Created tests for Software and Data Integrity Failures (A08) to verify artifact signatures, SBOM integrity, attestation chains, and feed updates.
This commit is contained in:
359
src/Web/StellaOps.Web/tests/fixtures/ttfs/deterministic-fixtures.ts
vendored
Normal file
359
src/Web/StellaOps.Web/tests/fixtures/ttfs/deterministic-fixtures.ts
vendored
Normal file
@@ -0,0 +1,359 @@
|
||||
// =============================================================================
|
||||
// deterministic-fixtures.ts
|
||||
// Deterministic test fixtures for TTFS testing
|
||||
// Part of Task T15: Create deterministic test fixtures
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Frozen timestamp used across all fixtures.
|
||||
* ISO 8601 format: 2025-12-04T12:00:00.000Z
|
||||
*/
|
||||
export const FROZEN_TIMESTAMP = '2025-12-04T12:00:00.000Z';
|
||||
export const FROZEN_TIMESTAMP_MS = new Date(FROZEN_TIMESTAMP).getTime();
|
||||
|
||||
/**
|
||||
* Deterministic seed for reproducible random generation.
|
||||
*/
|
||||
export const DETERMINISTIC_SEED = 42;
|
||||
|
||||
/**
|
||||
* Pre-generated deterministic UUIDs.
|
||||
*/
|
||||
export const FIXTURE_IDS = {
|
||||
TENANT_ID: '11111111-1111-1111-1111-111111111111',
|
||||
RUN_ID: '22222222-2222-2222-2222-222222222222',
|
||||
JOB_ID: '33333333-3333-3333-3333-333333333333',
|
||||
SOURCE_ID: '44444444-4444-4444-4444-444444444444',
|
||||
SIGNATURE_ID: '55555555-5555-5555-5555-555555555555',
|
||||
TENANT_ID_STRING: 'test-tenant-deterministic',
|
||||
CORRELATION_ID: 'corr-deterministic-001',
|
||||
SIGNAL_ID: 'sig-deterministic-001',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Deterministic digest values.
|
||||
*/
|
||||
export const DIGESTS = {
|
||||
/** 64-character lowercase hex digest (SHA-256). */
|
||||
PAYLOAD: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
|
||||
/** Image digest reference. */
|
||||
IMAGE: 'sha256:abc123def456789012345678901234567890123456789012345678901234abcd',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* FirstSignal kind values.
|
||||
*/
|
||||
export type FirstSignalKind =
|
||||
| 'queued'
|
||||
| 'started'
|
||||
| 'phase'
|
||||
| 'blocked'
|
||||
| 'failed'
|
||||
| 'succeeded'
|
||||
| 'canceled'
|
||||
| 'unavailable';
|
||||
|
||||
/**
|
||||
* FirstSignal phase values.
|
||||
*/
|
||||
export type FirstSignalPhase =
|
||||
| 'resolve'
|
||||
| 'fetch'
|
||||
| 'restore'
|
||||
| 'analyze'
|
||||
| 'policy'
|
||||
| 'report'
|
||||
| 'unknown';
|
||||
|
||||
/**
|
||||
* FirstSignal scope interface.
|
||||
*/
|
||||
export interface FirstSignalScope {
|
||||
type: 'repo' | 'image' | 'artifact';
|
||||
id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* LastKnownOutcome interface.
|
||||
*/
|
||||
export interface LastKnownOutcome {
|
||||
signatureId: string;
|
||||
errorCode?: string;
|
||||
token: string;
|
||||
excerpt?: string;
|
||||
confidence: 'low' | 'medium' | 'high';
|
||||
firstSeenAt: string;
|
||||
hitCount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* NextAction interface.
|
||||
*/
|
||||
export interface NextAction {
|
||||
type: 'open_logs' | 'open_job' | 'docs' | 'retry' | 'cli_command';
|
||||
label: string;
|
||||
target: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* FirstSignalDiagnostics interface.
|
||||
*/
|
||||
export interface FirstSignalDiagnostics {
|
||||
cacheHit: boolean;
|
||||
source: 'snapshot' | 'failure_index' | 'cold_start';
|
||||
correlationId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* FirstSignal interface.
|
||||
*/
|
||||
export interface FirstSignal {
|
||||
version: '1.0';
|
||||
signalId: string;
|
||||
jobId: string;
|
||||
timestamp: string;
|
||||
kind: FirstSignalKind;
|
||||
phase: FirstSignalPhase;
|
||||
scope: FirstSignalScope;
|
||||
summary: string;
|
||||
etaSeconds?: number;
|
||||
lastKnownOutcome?: LastKnownOutcome;
|
||||
nextActions?: NextAction[];
|
||||
diagnostics: FirstSignalDiagnostics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-built FirstSignal fixtures for different scenarios.
|
||||
*/
|
||||
export const FIRST_SIGNAL_FIXTURES: Record<string, FirstSignal> = {
|
||||
queued: {
|
||||
version: '1.0',
|
||||
signalId: FIXTURE_IDS.SIGNAL_ID,
|
||||
jobId: FIXTURE_IDS.JOB_ID,
|
||||
timestamp: FROZEN_TIMESTAMP,
|
||||
kind: 'queued',
|
||||
phase: 'resolve',
|
||||
scope: { type: 'image', id: DIGESTS.IMAGE },
|
||||
summary: 'Job queued, waiting for available worker',
|
||||
etaSeconds: 120,
|
||||
diagnostics: {
|
||||
cacheHit: true,
|
||||
source: 'snapshot',
|
||||
correlationId: FIXTURE_IDS.CORRELATION_ID,
|
||||
},
|
||||
},
|
||||
|
||||
failed: {
|
||||
version: '1.0',
|
||||
signalId: FIXTURE_IDS.SIGNAL_ID,
|
||||
jobId: FIXTURE_IDS.JOB_ID,
|
||||
timestamp: FROZEN_TIMESTAMP,
|
||||
kind: 'failed',
|
||||
phase: 'analyze',
|
||||
scope: { type: 'image', id: DIGESTS.IMAGE },
|
||||
summary: 'Analysis failed: dependency resolution error',
|
||||
lastKnownOutcome: {
|
||||
signatureId: FIXTURE_IDS.SIGNATURE_ID,
|
||||
errorCode: 'EDEPNOTFOUND',
|
||||
token: 'EDEPNOTFOUND',
|
||||
excerpt: 'Could not resolve dependency @types/node@^18.0.0',
|
||||
confidence: 'high',
|
||||
firstSeenAt: '2025-12-01T10:00:00.000Z',
|
||||
hitCount: 15,
|
||||
},
|
||||
nextActions: [
|
||||
{ type: 'open_logs', label: 'View Logs', target: `/logs/${FIXTURE_IDS.JOB_ID}` },
|
||||
{ type: 'retry', label: 'Retry Job', target: `/retry/${FIXTURE_IDS.JOB_ID}` },
|
||||
],
|
||||
diagnostics: {
|
||||
cacheHit: false,
|
||||
source: 'failure_index',
|
||||
correlationId: FIXTURE_IDS.CORRELATION_ID,
|
||||
},
|
||||
},
|
||||
|
||||
succeeded: {
|
||||
version: '1.0',
|
||||
signalId: FIXTURE_IDS.SIGNAL_ID,
|
||||
jobId: FIXTURE_IDS.JOB_ID,
|
||||
timestamp: FROZEN_TIMESTAMP,
|
||||
kind: 'succeeded',
|
||||
phase: 'report',
|
||||
scope: { type: 'image', id: DIGESTS.IMAGE },
|
||||
summary: 'Scan completed: 3 critical, 12 high, 45 medium findings',
|
||||
nextActions: [
|
||||
{ type: 'open_job', label: 'View Results', target: `/jobs/${FIXTURE_IDS.JOB_ID}` },
|
||||
],
|
||||
diagnostics: {
|
||||
cacheHit: true,
|
||||
source: 'snapshot',
|
||||
correlationId: FIXTURE_IDS.CORRELATION_ID,
|
||||
},
|
||||
},
|
||||
|
||||
blocked: {
|
||||
version: '1.0',
|
||||
signalId: FIXTURE_IDS.SIGNAL_ID,
|
||||
jobId: FIXTURE_IDS.JOB_ID,
|
||||
timestamp: FROZEN_TIMESTAMP,
|
||||
kind: 'blocked',
|
||||
phase: 'policy',
|
||||
scope: { type: 'image', id: DIGESTS.IMAGE },
|
||||
summary: 'Blocked by policy: critical-vuln-gate',
|
||||
nextActions: [
|
||||
{ type: 'docs', label: 'Policy Details', target: '/docs/policies/critical-vuln-gate' },
|
||||
],
|
||||
diagnostics: {
|
||||
cacheHit: true,
|
||||
source: 'snapshot',
|
||||
correlationId: FIXTURE_IDS.CORRELATION_ID,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* API response fixtures for TTFS measurement.
|
||||
*/
|
||||
export const API_RESPONSE_FIXTURES = {
|
||||
firstSignalSuccess: (signal: FirstSignal) => ({
|
||||
runId: FIXTURE_IDS.RUN_ID,
|
||||
firstSignal: signal,
|
||||
summaryEtag: 'W/"deterministic-etag-001"',
|
||||
}),
|
||||
|
||||
firstSignalNotFound: {
|
||||
error: 'Run not found',
|
||||
code: 'RUN_NOT_FOUND',
|
||||
},
|
||||
|
||||
firstSignalUnavailable: {
|
||||
runId: FIXTURE_IDS.RUN_ID,
|
||||
firstSignal: null,
|
||||
summaryEtag: null,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Timing fixtures for TTFS measurement.
|
||||
*/
|
||||
export const TIMING_FIXTURES = {
|
||||
cacheHit: {
|
||||
ttfsMs: 120,
|
||||
cacheStatus: 'hit' as const,
|
||||
},
|
||||
coldStart: {
|
||||
ttfsMs: 850,
|
||||
cacheStatus: 'miss' as const,
|
||||
},
|
||||
sloBreachP50: {
|
||||
ttfsMs: 2500, // > 2000ms P50 target
|
||||
cacheStatus: 'miss' as const,
|
||||
},
|
||||
sloBreachP95: {
|
||||
ttfsMs: 6000, // > 5000ms P95 target
|
||||
cacheStatus: 'miss' as const,
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Seeded random number generator for deterministic test data.
|
||||
*/
|
||||
export class SeededRandom {
|
||||
private seed: number;
|
||||
|
||||
constructor(seed: number = DETERMINISTIC_SEED) {
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pseudo-random number between 0 and 1.
|
||||
*/
|
||||
next(): number {
|
||||
// Simple LCG implementation
|
||||
this.seed = (this.seed * 1103515245 + 12345) % 2147483648;
|
||||
return this.seed / 2147483648;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a pseudo-random integer between 0 and max (exclusive).
|
||||
*/
|
||||
nextInt(max: number): number {
|
||||
return Math.floor(this.next() * max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a deterministic UUID.
|
||||
*/
|
||||
nextUuid(): string {
|
||||
const hex = () => this.nextInt(16).toString(16);
|
||||
return [
|
||||
Array(8).fill(0).map(hex).join(''),
|
||||
Array(4).fill(0).map(hex).join(''),
|
||||
'4' + Array(3).fill(0).map(hex).join(''),
|
||||
((this.nextInt(4) + 8).toString(16)) + Array(3).fill(0).map(hex).join(''),
|
||||
Array(12).fill(0).map(hex).join(''),
|
||||
].join('-');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Jest/Vitest setup helper to freeze time and random.
|
||||
*/
|
||||
export function setupDeterministicEnvironment(): () => void {
|
||||
const originalDate = global.Date;
|
||||
const originalRandom = Math.random;
|
||||
const rng = new SeededRandom(DETERMINISTIC_SEED);
|
||||
|
||||
// Freeze Date
|
||||
const FrozenDate = class extends originalDate {
|
||||
constructor(...args: ConstructorParameters<typeof Date>) {
|
||||
if (args.length === 0) {
|
||||
super(FROZEN_TIMESTAMP_MS);
|
||||
} else {
|
||||
super(...args);
|
||||
}
|
||||
}
|
||||
|
||||
static now(): number {
|
||||
return FROZEN_TIMESTAMP_MS;
|
||||
}
|
||||
} as DateConstructor;
|
||||
|
||||
global.Date = FrozenDate;
|
||||
Math.random = () => rng.next();
|
||||
|
||||
// Return cleanup function
|
||||
return () => {
|
||||
global.Date = originalDate;
|
||||
Math.random = originalRandom;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Playwright setup helper to freeze browser time.
|
||||
*/
|
||||
export async function setupPlaywrightDeterministic(page: import('@playwright/test').Page): Promise<void> {
|
||||
await page.addInitScript(`{
|
||||
const FROZEN_TIME = ${FROZEN_TIMESTAMP_MS};
|
||||
const OriginalDate = Date;
|
||||
|
||||
Date = class extends OriginalDate {
|
||||
constructor(...args) {
|
||||
if (args.length === 0) {
|
||||
super(FROZEN_TIME);
|
||||
} else {
|
||||
super(...args);
|
||||
}
|
||||
}
|
||||
static now() { return FROZEN_TIME; }
|
||||
};
|
||||
|
||||
// Also freeze performance.now relative to start
|
||||
const perfStart = performance.now();
|
||||
const originalPerfNow = performance.now.bind(performance);
|
||||
performance.now = () => originalPerfNow() - perfStart;
|
||||
}`);
|
||||
|
||||
// Disable animations for deterministic screenshots
|
||||
await page.emulateMedia({ reducedMotion: 'reduce' });
|
||||
}
|
||||
Reference in New Issue
Block a user