Files
iptvnator/apps/electron-backend-e2e/src/performance/xtream-main-lifecycle.ts
T
4gray a2fafcfc08 test(performance): add end-to-end Xtream benchmark harness (#1300)
* docs(performance): plan Xtream benchmark

* feat(xtream-mock-server): add deterministic 100k fixture

* style(xtream-mock-server): apply repository formatting

* fix(xtream-mock-server): harden performance fixture data

* feat(xtream-mock-server): add performance control plane

* docs(performance): correct Xtream capture plan

* fix(xtream-mock-server): harden performance controls

* fix(xtream-mock-server): harden control lifecycle

* feat(performance): add Xtream preload markers

* feat(performance): trace Xtream main phases

* feat(performance): mark Xtream store publications

* feat(performance): trace Xtream database phases

* feat(performance): trace Xtream delete cancellation

* feat(performance): capture Xtream phase attribution

* feat(performance): mark Sources Xtream refresh

* test(performance): define Xtream benchmark evidence contracts

* test(performance): add Xtream benchmark runner

* test(performance): surface failure evidence writes

* test(performance): align database read clock

* test(performance): preserve capture failure contracts
2026-07-28 08:08:07 +02:00

112 lines
3.9 KiB
TypeScript

export interface XtreamMainLifecyclePhase {
readonly endEpochMs: number | null;
readonly phase: string;
readonly startEpochMs: number;
}
export interface XtreamMainLifecycleInput {
readonly method: string;
readonly outcome: 'error' | 'success' | null;
readonly phases: readonly XtreamMainLifecyclePhase[];
}
export interface XtreamMainLifecycleValidator {
validate(input: XtreamMainLifecycleInput): string | null;
}
export function createXtreamMainLifecycleValidator(): XtreamMainLifecycleValidator {
const helpers = {
closedPhase(
phases: readonly XtreamMainLifecyclePhase[],
phase: string
): XtreamMainLifecyclePhase | null {
const matches = phases.filter(
(candidate) => candidate.phase === phase
);
const match = matches[0];
return matches.length === 1 && match && match.endEpochMs !== null
? match
: null;
},
isEpoch(value: unknown): value is number {
return (
typeof value === 'number' && Number.isFinite(value) && value > 0
);
},
};
return {
validate(input): string | null {
const phaseNames = input.phases.map(({ phase }) => phase);
if (
new Set(phaseNames).size !== phaseNames.length ||
input.phases.some(
({ endEpochMs, startEpochMs }) =>
!helpers.isEpoch(startEpochMs) ||
(endEpochMs !== null &&
(!helpers.isEpoch(endEpochMs) ||
endEpochMs < startEpochMs))
)
) {
return 'main-phase-inventory-incomplete';
}
if (input.method === 'xtreamCancelSession') {
return helpers.closedPhase(
input.phases,
'xtream.cancel-session'
) &&
phaseNames.every(
(phase) => phase === 'xtream.cancel-session'
)
? null
: 'main-phase-inventory-incomplete';
}
if (
input.method !== 'xtreamRequest' ||
(input.outcome !== 'success' && input.outcome !== 'error')
) {
return 'main-phase-inventory-incomplete';
}
const network = helpers.closedPhase(
input.phases,
'xtream.network.total'
);
const json = helpers.closedPhase(
input.phases,
'xtream.json.transform'
);
const response = helpers.closedPhase(
input.phases,
'xtream.response.ready'
);
if (
!network ||
phaseNames.some(
(phase) =>
phase !== 'xtream.network.total' &&
phase !== 'xtream.json.transform' &&
phase !== 'xtream.response.ready'
) ||
(phaseNames.includes('xtream.json.transform') && !json) ||
(input.outcome === 'success' && (!json || !response))
) {
return 'main-phase-inventory-incomplete';
}
if (
(json &&
(json.startEpochMs < network.startEpochMs ||
Number(json.endEpochMs) >
Number(network.endEpochMs))) ||
(input.outcome === 'success' &&
response !== null &&
response.startEpochMs < Number(network.endEpochMs)) ||
(input.outcome === 'error' &&
phaseNames.includes('xtream.response.ready'))
) {
return 'main-phase-topology-invalid';
}
return null;
},
};
}