test: add deterministic resilience matrix

This commit is contained in:
2026-09-02 07:24:48 +03:00
parent 5abd1c19ae
commit 8089d061ec
21 changed files with 809 additions and 22 deletions

View File

@@ -103,8 +103,8 @@ export function createAudioController(options: AudioControllerOptions): AudioCon
if (!graph) return;
try {
await graph.resume();
} catch {
if (firstStart) await fail();
} catch (error) {
if (firstStart || (error instanceof Error && error.name === 'InvalidStateError')) await fail();
else {
desired = 'suspended';
resumeIntent = 'user-required';

View File

@@ -1,12 +1,44 @@
import type { AudioContextLifecycleState, AudioGraph } from '@/audio/types';
import { isTestFault, recordTestFaultEvent } from '@/testing/fault-hooks';
declare const __PORTFOLIO_TEST_BUILD__: boolean;
const TEST_BUILD = typeof __PORTFOLIO_TEST_BUILD__ !== 'undefined' && __PORTFOLIO_TEST_BUILD__;
type AudioContextFactory = () => AudioContext;
export function createBrowserSynthEngine(): AudioGraph {
if (TEST_BUILD && isTestFault('F10-audio-start-rejection')) {
recordTestFaultEvent('audio-start-rejected');
throw new DOMException('Injected audio start rejection', 'NotAllowedError');
}
const AudioContextConstructor = window.AudioContext
?? (window as typeof window & { webkitAudioContext?: typeof AudioContext }).webkitAudioContext;
if (!AudioContextConstructor) throw new Error('Web Audio is unavailable');
return createSynthEngine(() => new AudioContextConstructor());
const graph = createSynthEngine(() => new AudioContextConstructor());
if (!TEST_BUILD) return graph;
let resumeCount = 0;
return {
...graph,
async resume() {
resumeCount += 1;
if (isTestFault('F11-audio-resume-rejection') && resumeCount > 1) {
recordTestFaultEvent('audio-resume-rejected');
throw new DOMException('Injected audio resume rejection', 'NotAllowedError');
}
if (isTestFault('F12-audio-graph-invalid') && resumeCount > 1) {
recordTestFaultEvent('audio-graph-invalid');
throw new DOMException('Injected invalid audio graph', 'InvalidStateError');
}
await graph.resume();
},
async suspend() {
if (isTestFault('F13-audio-suspend-rejection')) {
recordTestFaultEvent('audio-suspend-rejected');
throw new DOMException('Injected audio suspend rejection', 'InvalidStateError');
}
await graph.suspend();
},
};
}
export function createSynthEngine(createContext: AudioContextFactory): AudioGraph {