mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-01 06:22:44 -08:00
refactor(main): extract anilist/mpv runtime composers
This commit is contained in:
237
src/main/runtime/composers/anilist-tracking-composer.test.ts
Normal file
237
src/main/runtime/composers/anilist-tracking-composer.test.ts
Normal file
@@ -0,0 +1,237 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import type { AnilistMediaGuess } from '../../../core/services/anilist/anilist-updater';
|
||||
import { composeAnilistTrackingHandlers } from './anilist-tracking-composer';
|
||||
|
||||
test('composeAnilistTrackingHandlers returns callable handlers and forwards calls to deps', async () => {
|
||||
const refreshSavedTokens: string[] = [];
|
||||
let refreshCachedToken: string | null = null;
|
||||
|
||||
let mediaKeyState: string | null = 'media-key';
|
||||
let mediaDurationSecState: number | null = null;
|
||||
let mediaGuessState: AnilistMediaGuess | null = null;
|
||||
let mediaGuessPromiseState: Promise<AnilistMediaGuess | null> | null = null;
|
||||
let lastDurationProbeAtMsState = 0;
|
||||
let requestMpvDurationCalls = 0;
|
||||
let guessAnilistMediaInfoCalls = 0;
|
||||
|
||||
let retryUpdateCalls = 0;
|
||||
let maybeRunUpdateCalls = 0;
|
||||
|
||||
const composed = composeAnilistTrackingHandlers({
|
||||
refreshClientSecretMainDeps: {
|
||||
getResolvedConfig: () => ({ anilist: { accessToken: 'refresh-token' } }),
|
||||
isAnilistTrackingEnabled: () => true,
|
||||
getCachedAccessToken: () => refreshCachedToken,
|
||||
setCachedAccessToken: (token) => {
|
||||
refreshCachedToken = token;
|
||||
},
|
||||
saveStoredToken: (token) => {
|
||||
refreshSavedTokens.push(token);
|
||||
},
|
||||
loadStoredToken: () => null,
|
||||
setClientSecretState: () => {},
|
||||
getAnilistSetupPageOpened: () => false,
|
||||
setAnilistSetupPageOpened: () => {},
|
||||
openAnilistSetupWindow: () => {},
|
||||
now: () => 100,
|
||||
},
|
||||
getCurrentMediaKeyMainDeps: {
|
||||
getCurrentMediaPath: () => ' media-key ',
|
||||
},
|
||||
resetMediaTrackingMainDeps: {
|
||||
setMediaKey: (value) => {
|
||||
mediaKeyState = value;
|
||||
},
|
||||
setMediaDurationSec: (value) => {
|
||||
mediaDurationSecState = value;
|
||||
},
|
||||
setMediaGuess: (value) => {
|
||||
mediaGuessState = value;
|
||||
},
|
||||
setMediaGuessPromise: (value) => {
|
||||
mediaGuessPromiseState = value;
|
||||
},
|
||||
setLastDurationProbeAtMs: (value) => {
|
||||
lastDurationProbeAtMsState = value;
|
||||
},
|
||||
},
|
||||
getMediaGuessRuntimeStateMainDeps: {
|
||||
getMediaKey: () => mediaKeyState,
|
||||
getMediaDurationSec: () => mediaDurationSecState,
|
||||
getMediaGuess: () => mediaGuessState,
|
||||
getMediaGuessPromise: () => mediaGuessPromiseState,
|
||||
getLastDurationProbeAtMs: () => lastDurationProbeAtMsState,
|
||||
},
|
||||
setMediaGuessRuntimeStateMainDeps: {
|
||||
setMediaKey: (value) => {
|
||||
mediaKeyState = value;
|
||||
},
|
||||
setMediaDurationSec: (value) => {
|
||||
mediaDurationSecState = value;
|
||||
},
|
||||
setMediaGuess: (value) => {
|
||||
mediaGuessState = value;
|
||||
},
|
||||
setMediaGuessPromise: (value) => {
|
||||
mediaGuessPromiseState = value;
|
||||
},
|
||||
setLastDurationProbeAtMs: (value) => {
|
||||
lastDurationProbeAtMsState = value;
|
||||
},
|
||||
},
|
||||
resetMediaGuessStateMainDeps: {
|
||||
setMediaGuess: (value) => {
|
||||
mediaGuessState = value;
|
||||
},
|
||||
setMediaGuessPromise: (value) => {
|
||||
mediaGuessPromiseState = value;
|
||||
},
|
||||
},
|
||||
maybeProbeDurationMainDeps: {
|
||||
getState: () => ({
|
||||
mediaKey: mediaKeyState,
|
||||
mediaDurationSec: mediaDurationSecState,
|
||||
mediaGuess: mediaGuessState,
|
||||
mediaGuessPromise: mediaGuessPromiseState,
|
||||
lastDurationProbeAtMs: lastDurationProbeAtMsState,
|
||||
}),
|
||||
setState: (state) => {
|
||||
mediaKeyState = state.mediaKey;
|
||||
mediaDurationSecState = state.mediaDurationSec;
|
||||
mediaGuessState = state.mediaGuess;
|
||||
mediaGuessPromiseState = state.mediaGuessPromise;
|
||||
lastDurationProbeAtMsState = state.lastDurationProbeAtMs;
|
||||
},
|
||||
durationRetryIntervalMs: 0,
|
||||
now: () => 1000,
|
||||
requestMpvDuration: async () => {
|
||||
requestMpvDurationCalls += 1;
|
||||
return 120;
|
||||
},
|
||||
logWarn: () => {},
|
||||
},
|
||||
ensureMediaGuessMainDeps: {
|
||||
getState: () => ({
|
||||
mediaKey: mediaKeyState,
|
||||
mediaDurationSec: mediaDurationSecState,
|
||||
mediaGuess: mediaGuessState,
|
||||
mediaGuessPromise: mediaGuessPromiseState,
|
||||
lastDurationProbeAtMs: lastDurationProbeAtMsState,
|
||||
}),
|
||||
setState: (state) => {
|
||||
mediaKeyState = state.mediaKey;
|
||||
mediaDurationSecState = state.mediaDurationSec;
|
||||
mediaGuessState = state.mediaGuess;
|
||||
mediaGuessPromiseState = state.mediaGuessPromise;
|
||||
lastDurationProbeAtMsState = state.lastDurationProbeAtMs;
|
||||
},
|
||||
resolveMediaPathForJimaku: (value) => value,
|
||||
getCurrentMediaPath: () => '/tmp/media.mkv',
|
||||
getCurrentMediaTitle: () => 'Episode title',
|
||||
guessAnilistMediaInfo: async () => {
|
||||
guessAnilistMediaInfoCalls += 1;
|
||||
return { title: 'Episode title', episode: 7, source: 'guessit' };
|
||||
},
|
||||
},
|
||||
processNextRetryUpdateMainDeps: {
|
||||
nextReady: () => ({ key: 'retry-key', title: 'Retry title', episode: 1 }),
|
||||
refreshRetryQueueState: () => {},
|
||||
setLastAttemptAt: () => {},
|
||||
setLastError: () => {},
|
||||
refreshAnilistClientSecretState: async () => 'retry-token',
|
||||
updateAnilistPostWatchProgress: async () => {
|
||||
retryUpdateCalls += 1;
|
||||
return { status: 'updated', message: 'ok' };
|
||||
},
|
||||
markSuccess: () => {},
|
||||
rememberAttemptedUpdateKey: () => {},
|
||||
markFailure: () => {},
|
||||
logInfo: () => {},
|
||||
now: () => 1,
|
||||
},
|
||||
maybeRunPostWatchUpdateMainDeps: {
|
||||
getInFlight: () => false,
|
||||
setInFlight: () => {},
|
||||
getResolvedConfig: () => ({ tracking: true }),
|
||||
isAnilistTrackingEnabled: () => true,
|
||||
getCurrentMediaKey: () => 'media-key',
|
||||
hasMpvClient: () => true,
|
||||
getTrackedMediaKey: () => 'media-key',
|
||||
resetTrackedMedia: () => {},
|
||||
getWatchedSeconds: () => 500,
|
||||
maybeProbeAnilistDuration: async () => 600,
|
||||
ensureAnilistMediaGuess: async () => ({
|
||||
title: 'Episode title',
|
||||
episode: 2,
|
||||
source: 'guessit',
|
||||
}),
|
||||
hasAttemptedUpdateKey: () => false,
|
||||
processNextAnilistRetryUpdate: async () => ({ ok: true, message: 'ok' }),
|
||||
refreshAnilistClientSecretState: async () => 'run-token',
|
||||
enqueueRetry: () => {},
|
||||
markRetryFailure: () => {},
|
||||
markRetrySuccess: () => {},
|
||||
refreshRetryQueueState: () => {},
|
||||
updateAnilistPostWatchProgress: async () => {
|
||||
maybeRunUpdateCalls += 1;
|
||||
return { status: 'updated', message: 'updated from maybeRun' };
|
||||
},
|
||||
rememberAttemptedUpdateKey: () => {},
|
||||
showMpvOsd: () => {},
|
||||
logInfo: () => {},
|
||||
logWarn: () => {},
|
||||
minWatchSeconds: 10,
|
||||
minWatchRatio: 0.5,
|
||||
},
|
||||
});
|
||||
|
||||
assert.equal(typeof composed.refreshAnilistClientSecretState, 'function');
|
||||
assert.equal(typeof composed.getCurrentAnilistMediaKey, 'function');
|
||||
assert.equal(typeof composed.resetAnilistMediaTracking, 'function');
|
||||
assert.equal(typeof composed.getAnilistMediaGuessRuntimeState, 'function');
|
||||
assert.equal(typeof composed.setAnilistMediaGuessRuntimeState, 'function');
|
||||
assert.equal(typeof composed.resetAnilistMediaGuessState, 'function');
|
||||
assert.equal(typeof composed.maybeProbeAnilistDuration, 'function');
|
||||
assert.equal(typeof composed.ensureAnilistMediaGuess, 'function');
|
||||
assert.equal(typeof composed.processNextAnilistRetryUpdate, 'function');
|
||||
assert.equal(typeof composed.maybeRunAnilistPostWatchUpdate, 'function');
|
||||
|
||||
const refreshed = await composed.refreshAnilistClientSecretState({ force: true });
|
||||
assert.equal(refreshed, 'refresh-token');
|
||||
assert.deepEqual(refreshSavedTokens, ['refresh-token']);
|
||||
|
||||
assert.equal(composed.getCurrentAnilistMediaKey(), 'media-key');
|
||||
composed.resetAnilistMediaTracking('next-key');
|
||||
assert.equal(mediaKeyState, 'next-key');
|
||||
assert.equal(mediaDurationSecState, null);
|
||||
|
||||
composed.setAnilistMediaGuessRuntimeState({
|
||||
mediaKey: 'media-key',
|
||||
mediaDurationSec: 90,
|
||||
mediaGuess: { title: 'Known', episode: 3, source: 'fallback' },
|
||||
mediaGuessPromise: null,
|
||||
lastDurationProbeAtMs: 11,
|
||||
});
|
||||
assert.equal(composed.getAnilistMediaGuessRuntimeState().mediaDurationSec, 90);
|
||||
|
||||
composed.resetAnilistMediaGuessState();
|
||||
assert.equal(composed.getAnilistMediaGuessRuntimeState().mediaGuess, null);
|
||||
|
||||
mediaKeyState = 'media-key';
|
||||
mediaDurationSecState = null;
|
||||
const probedDuration = await composed.maybeProbeAnilistDuration('media-key');
|
||||
assert.equal(probedDuration, 120);
|
||||
assert.equal(requestMpvDurationCalls, 1);
|
||||
|
||||
mediaGuessState = null;
|
||||
await composed.ensureAnilistMediaGuess('media-key');
|
||||
assert.equal(guessAnilistMediaInfoCalls, 1);
|
||||
|
||||
const retryResult = await composed.processNextAnilistRetryUpdate();
|
||||
assert.deepEqual(retryResult, { ok: true, message: 'ok' });
|
||||
assert.equal(retryUpdateCalls, 1);
|
||||
|
||||
await composed.maybeRunAnilistPostWatchUpdate();
|
||||
assert.equal(maybeRunUpdateCalls, 1);
|
||||
});
|
||||
Reference in New Issue
Block a user