test(youtube): add test for cache lookup failure fallback in media queue

- Handle getCachedMediaPath errors gracefully with logWarn + return false
- New test verifies cache failure triggers immediate generation fallback
This commit is contained in:
2026-06-23 21:55:23 -07:00
parent 028636c76d
commit 9c503c5b14
2 changed files with 82 additions and 1 deletions
@@ -0,0 +1,72 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import type { AnkiConnectConfig } from '../types/anki';
import { PendingYoutubeMediaQueue, type PendingYoutubeMediaQueueDeps } from './pending-youtube-media-queue';
function createDeps(
overrides: Partial<PendingYoutubeMediaQueueDeps> = {},
): PendingYoutubeMediaQueueDeps {
const warnings: unknown[][] = [];
const deps: PendingYoutubeMediaQueueDeps & { warnings: unknown[][] } = {
client: {
notesInfo: async () => [],
updateNoteFields: async () => {},
storeMediaFile: async () => {},
},
mediaGenerator: {
generateAudio: async () => Buffer.from('audio'),
generateScreenshot: async () => Buffer.from('image'),
generateAnimatedImage: async () => Buffer.from('image'),
},
getConfig: () =>
({
media: { generateAudio: true, generateImage: true },
fields: {},
}) as AnkiConnectConfig,
getCurrentVideoPath: () => 'https://www.youtube.com/watch?v=abc123',
getCachedMediaPath: async () => null,
shouldRequireRemoteMediaCache: () => true,
getSubtitleMediaRange: () => ({ startTime: 1, endTime: 2 }),
getResolvedSentenceAudioFieldName: () => 'SentenceAudio',
resolveConfiguredFieldName: () => 'Picture',
mergeFieldValue: (_existing, newValue) => newValue,
getAnimatedImageLeadInSeconds: async () => 0,
generateAudioFilename: () => 'audio.mp3',
generateImageFilename: () => 'image.webp',
formatMiscInfoPatternForMediaPath: () => '',
showStatusNotification: () => {},
showNotification: async () => {},
logInfo: () => {},
logWarn: (...args) => {
warnings.push(args);
},
logError: () => {},
warnings,
...overrides,
};
return deps;
}
test('PendingYoutubeMediaQueue treats cache lookup failures as an immediate generation fallback', async () => {
const deps = createDeps({
getCachedMediaPath: async () => {
throw new Error('cache unavailable');
},
});
const queue = new PendingYoutubeMediaQueue(deps);
const queued = await queue.queueFromNote({
noteId: 42,
noteInfo: { noteId: 42, fields: {} },
label: 'demo',
});
assert.equal(queued, false);
assert.deepEqual((deps as typeof deps & { warnings: unknown[][] }).warnings, [
[
'Failed to read YouTube cache state; falling back to immediate media generation:',
'cache unavailable',
],
]);
});
@@ -93,7 +93,16 @@ export class PendingYoutubeMediaQueue {
return false;
}
const cachedPath = await getCachedMediaPath(sourceUrl, 'video');
let cachedPath: string | null = null;
try {
cachedPath = await getCachedMediaPath(sourceUrl, 'video');
} catch (error) {
this.deps.logWarn(
'Failed to read YouTube cache state; falling back to immediate media generation:',
error instanceof Error ? error.message : String(error),
);
return false;
}
if (cachedPath) {
return false;
}