mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
fix(electron): fail closed on unknown performance completions
This commit is contained in:
1 parent
f9e71a6d8d
commit
0579d253c4
5 files changed
+222
-6
No files matched your search
@@ -168,12 +168,27 @@ export function completePreloadPerformanceCall(
|
||||
): PreloadPerformanceMarkerMetadata {
|
||||
const call = calls.get(event.ipcCallId);
|
||||
if (!call) {
|
||||
const invalidSequence =
|
||||
playlistId === null
|
||||
? undefined
|
||||
: invalidatePreloadPerformanceSequence(
|
||||
calls,
|
||||
sequences,
|
||||
playlistId,
|
||||
PRELOAD_PERFORMANCE_INVALID_REASON.OUT_OF_ORDER
|
||||
);
|
||||
closeInvalidPreloadPerformanceSequenceWhenSettled(
|
||||
calls,
|
||||
sequences,
|
||||
playlistId
|
||||
);
|
||||
return createPreloadPerformanceMarkerMetadata(
|
||||
event,
|
||||
playlistId,
|
||||
operationId,
|
||||
invalidSequence?.operationId ?? operationId,
|
||||
PRELOAD_PERFORMANCE_CORRELATION_STATE.INVALID,
|
||||
PRELOAD_PERFORMANCE_INVALID_REASON.OUT_OF_ORDER
|
||||
invalidSequence?.invalidReason ??
|
||||
PRELOAD_PERFORMANCE_INVALID_REASON.OUT_OF_ORDER
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,48 @@ import {
|
||||
OPERATION_ONE,
|
||||
OPERATION_TWO,
|
||||
PLAYLIST_ONE,
|
||||
PLAYLIST_TWO,
|
||||
type CorrelationHarness,
|
||||
} from './preload-performance-correlation.test-helpers';
|
||||
|
||||
function completeDatabaseSequence(
|
||||
advance: CorrelationHarness['advance'],
|
||||
firstCallId: number,
|
||||
playlistId: string,
|
||||
operationId: string
|
||||
) {
|
||||
return [
|
||||
advance({
|
||||
ipcCallId: firstCallId,
|
||||
method: PRELOAD_PERFORMANCE_METHOD.DB_GET_APP_PLAYLIST,
|
||||
operationId,
|
||||
phase: 'start',
|
||||
playlistId,
|
||||
}),
|
||||
advance({
|
||||
ipcCallId: firstCallId,
|
||||
method: PRELOAD_PERFORMANCE_METHOD.DB_GET_APP_PLAYLIST,
|
||||
operationId,
|
||||
phase: 'success',
|
||||
playlistId,
|
||||
}),
|
||||
advance({
|
||||
ipcCallId: firstCallId + 1,
|
||||
method: PRELOAD_PERFORMANCE_METHOD.DB_UPSERT_APP_PLAYLIST,
|
||||
operationId,
|
||||
phase: 'start',
|
||||
playlistId,
|
||||
}),
|
||||
advance({
|
||||
ipcCallId: firstCallId + 1,
|
||||
method: PRELOAD_PERFORMANCE_METHOD.DB_UPSERT_APP_PLAYLIST,
|
||||
operationId,
|
||||
phase: 'success',
|
||||
playlistId,
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
describe('preload performance marker correlation completions', () => {
|
||||
let advance: CorrelationHarness['advance'];
|
||||
|
||||
@@ -18,6 +57,159 @@ describe('preload performance marker correlation completions', () => {
|
||||
({ advance } = createCorrelationHarness());
|
||||
});
|
||||
|
||||
it.each(['success', 'error'] as const)(
|
||||
'invalidates only the matching playlist after an unknown call ID %s',
|
||||
(phase) => {
|
||||
advance({
|
||||
ipcCallId: 1,
|
||||
method: PRELOAD_PERFORMANCE_METHOD.REFRESH_PLAYLIST,
|
||||
operationId: OPERATION_ONE,
|
||||
phase: 'start',
|
||||
playlistId: PLAYLIST_ONE,
|
||||
});
|
||||
advance({
|
||||
ipcCallId: 10,
|
||||
method: PRELOAD_PERFORMANCE_METHOD.REFRESH_PLAYLIST,
|
||||
operationId: OPERATION_TWO,
|
||||
phase: 'start',
|
||||
playlistId: PLAYLIST_TWO,
|
||||
});
|
||||
|
||||
const unknownCompletion = advance({
|
||||
ipcCallId: 99,
|
||||
method: PRELOAD_PERFORMANCE_METHOD.REFRESH_PLAYLIST,
|
||||
operationId: OPERATION_TWO,
|
||||
phase,
|
||||
playlistId: PLAYLIST_ONE,
|
||||
});
|
||||
const matchingCompletion = advance({
|
||||
ipcCallId: 1,
|
||||
method: PRELOAD_PERFORMANCE_METHOD.REFRESH_PLAYLIST,
|
||||
operationId: OPERATION_ONE,
|
||||
phase: 'success',
|
||||
playlistId: PLAYLIST_ONE,
|
||||
});
|
||||
const samePlaylistPersistence = completeDatabaseSequence(
|
||||
advance,
|
||||
20,
|
||||
PLAYLIST_ONE,
|
||||
OPERATION_ONE
|
||||
);
|
||||
|
||||
const unrelatedRefreshCompletion = advance({
|
||||
ipcCallId: 10,
|
||||
method: PRELOAD_PERFORMANCE_METHOD.REFRESH_PLAYLIST,
|
||||
operationId: OPERATION_TWO,
|
||||
phase: 'success',
|
||||
playlistId: PLAYLIST_TWO,
|
||||
});
|
||||
const unrelatedPersistence = completeDatabaseSequence(
|
||||
advance,
|
||||
30,
|
||||
PLAYLIST_TWO,
|
||||
OPERATION_TWO
|
||||
);
|
||||
|
||||
expect(unknownCompletion).toMatchObject({
|
||||
correlationState: PRELOAD_PERFORMANCE_CORRELATION_STATE.INVALID,
|
||||
invalidReason: PRELOAD_PERFORMANCE_INVALID_REASON.OUT_OF_ORDER,
|
||||
operationId: OPERATION_ONE,
|
||||
playlistId: PLAYLIST_ONE,
|
||||
});
|
||||
expect(matchingCompletion).toMatchObject({
|
||||
correlationState: PRELOAD_PERFORMANCE_CORRELATION_STATE.INVALID,
|
||||
invalidReason: PRELOAD_PERFORMANCE_INVALID_REASON.OUT_OF_ORDER,
|
||||
});
|
||||
expect(
|
||||
[matchingCompletion, ...samePlaylistPersistence].some(
|
||||
({ correlationState }) =>
|
||||
correlationState ===
|
||||
PRELOAD_PERFORMANCE_CORRELATION_STATE.COMPLETE
|
||||
)
|
||||
).toBe(false);
|
||||
expect(unrelatedRefreshCompletion.correlationState).toBe(
|
||||
PRELOAD_PERFORMANCE_CORRELATION_STATE.CORRELATED
|
||||
);
|
||||
expect(unrelatedPersistence.at(-1)).toMatchObject({
|
||||
correlationState:
|
||||
PRELOAD_PERFORMANCE_CORRELATION_STATE.COMPLETE,
|
||||
operationId: OPERATION_TWO,
|
||||
playlistId: PLAYLIST_TWO,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
it('invalidates persistence after a duplicate completion without touching another playlist', () => {
|
||||
advance({
|
||||
ipcCallId: 1,
|
||||
method: PRELOAD_PERFORMANCE_METHOD.REFRESH_PLAYLIST,
|
||||
operationId: OPERATION_ONE,
|
||||
phase: 'start',
|
||||
playlistId: PLAYLIST_ONE,
|
||||
});
|
||||
advance({
|
||||
ipcCallId: 10,
|
||||
method: PRELOAD_PERFORMANCE_METHOD.REFRESH_PLAYLIST,
|
||||
operationId: OPERATION_TWO,
|
||||
phase: 'start',
|
||||
playlistId: PLAYLIST_TWO,
|
||||
});
|
||||
advance({
|
||||
ipcCallId: 1,
|
||||
method: PRELOAD_PERFORMANCE_METHOD.REFRESH_PLAYLIST,
|
||||
operationId: OPERATION_ONE,
|
||||
phase: 'success',
|
||||
playlistId: PLAYLIST_ONE,
|
||||
});
|
||||
|
||||
const duplicateCompletion = advance({
|
||||
ipcCallId: 1,
|
||||
method: PRELOAD_PERFORMANCE_METHOD.REFRESH_PLAYLIST,
|
||||
operationId: OPERATION_ONE,
|
||||
phase: 'success',
|
||||
playlistId: PLAYLIST_ONE,
|
||||
});
|
||||
const samePlaylistPersistence = completeDatabaseSequence(
|
||||
advance,
|
||||
20,
|
||||
PLAYLIST_ONE,
|
||||
OPERATION_ONE
|
||||
);
|
||||
|
||||
advance({
|
||||
ipcCallId: 10,
|
||||
method: PRELOAD_PERFORMANCE_METHOD.REFRESH_PLAYLIST,
|
||||
operationId: OPERATION_TWO,
|
||||
phase: 'success',
|
||||
playlistId: PLAYLIST_TWO,
|
||||
});
|
||||
const unrelatedPersistence = completeDatabaseSequence(
|
||||
advance,
|
||||
30,
|
||||
PLAYLIST_TWO,
|
||||
OPERATION_TWO
|
||||
);
|
||||
|
||||
expect(duplicateCompletion).toMatchObject({
|
||||
correlationState: PRELOAD_PERFORMANCE_CORRELATION_STATE.INVALID,
|
||||
invalidReason: PRELOAD_PERFORMANCE_INVALID_REASON.OUT_OF_ORDER,
|
||||
operationId: OPERATION_ONE,
|
||||
playlistId: PLAYLIST_ONE,
|
||||
});
|
||||
expect(
|
||||
samePlaylistPersistence.some(
|
||||
({ correlationState }) =>
|
||||
correlationState ===
|
||||
PRELOAD_PERFORMANCE_CORRELATION_STATE.COMPLETE
|
||||
)
|
||||
).toBe(false);
|
||||
expect(unrelatedPersistence.at(-1)).toMatchObject({
|
||||
correlationState: PRELOAD_PERFORMANCE_CORRELATION_STATE.COMPLETE,
|
||||
operationId: OPERATION_TWO,
|
||||
playlistId: PLAYLIST_TWO,
|
||||
});
|
||||
});
|
||||
|
||||
it('invalidates a correlated sequence when a target call errors', () => {
|
||||
advance({
|
||||
ipcCallId: 1,
|
||||
|
||||
@@ -16,7 +16,10 @@ export const PlaylistActions = createActionGroup({
|
||||
'Remove Playlist': props<{ playlistId: string }>(),
|
||||
'Update Playlist Meta': props<{ playlist: PlaylistMeta }>(),
|
||||
'Update Playlist': props<{
|
||||
/** Instrumentation-only; never persisted or sent to main/worker IPC. */
|
||||
/**
|
||||
* Instrumentation-only; stripped before the DB invoke.
|
||||
* Never enters the DB worker payload or persisted playlist data.
|
||||
*/
|
||||
operationId?: string;
|
||||
playlist: Playlist;
|
||||
playlistId: string;
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { of } from 'rxjs';
|
||||
import { PlaylistActions } from './actions';
|
||||
import { persistPlaylistUpdate } from './playlist-update.effect-handler';
|
||||
|
||||
describe('PlaylistEffects updatePlaylist', () => {
|
||||
describe('persistPlaylistUpdate', () => {
|
||||
it('forwards the refresh operation ID to playlist persistence', () => {
|
||||
const playlistsService = {
|
||||
updatePlaylist: jest.fn(() => of(undefined)),
|
||||
@@ -686,7 +686,10 @@ export interface ElectronBridgeApi {
|
||||
) => Promise<ElectronBridgePlaylistRow | null>;
|
||||
dbUpsertAppPlaylist: (
|
||||
playlist: Playlist,
|
||||
/** Instrumentation-only; never persisted or sent to main/worker IPC. */
|
||||
/**
|
||||
* Instrumentation-only; stripped before the DB invoke.
|
||||
* Never enters the DB worker payload or persisted playlist data.
|
||||
*/
|
||||
operationId?: string
|
||||
) => Promise<ElectronBridgeResult>;
|
||||
dbUpsertAppPlaylists: (
|
||||
@@ -696,7 +699,10 @@ export interface ElectronBridgeApi {
|
||||
dbGetAppPlaylistMetas: () => Promise<Playlist[]>;
|
||||
dbGetAppPlaylist: (
|
||||
playlistId: string,
|
||||
/** Instrumentation-only; never persisted or sent to main/worker IPC. */
|
||||
/**
|
||||
* Instrumentation-only; stripped before the DB invoke.
|
||||
* Never enters the DB worker payload or persisted playlist data.
|
||||
*/
|
||||
operationId?: string
|
||||
) => Promise<Playlist | null>;
|
||||
dbGetAppPlaylistFavoriteChannels: (
|
||||
|
||||
Reference in new issue
Block a user