diff --git a/src/anki-integration/pending-youtube-media-queue.test.ts b/src/anki-integration/pending-youtube-media-queue.test.ts new file mode 100644 index 00000000..fb726f56 --- /dev/null +++ b/src/anki-integration/pending-youtube-media-queue.test.ts @@ -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 { + 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', + ], + ]); +}); diff --git a/src/anki-integration/pending-youtube-media-queue.ts b/src/anki-integration/pending-youtube-media-queue.ts index 2f79fd35..5ec3cb7b 100644 --- a/src/anki-integration/pending-youtube-media-queue.ts +++ b/src/anki-integration/pending-youtube-media-queue.ts @@ -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; }