/** * Unit Tests for Enrichment API Routes * * Tests business logic for inline handlers in worker/src/routes/enrich.ts: * - POST /api/enrich/queue/batch (line 313-372) * - POST /api/enrich/batch-direct (line 470-631) * - POST /api/harvest/covers (line 687-823) * * Following Pragmatic Miniflare approach: pure TypeScript business logic focus */ import { describe, it, expect } from 'vitest'; describe('POST /api/enrich/queue/batch', () => { describe('Response Schema', () => { it('should have all required fields in success response', () => { const response = { queued: 95, failed: 5, errors: [ { isbn: 'invalid', error: 'Invalid ISBN format' }, { isbn: '123', error: 'Invalid ISBN format' }, ], }; expect(response).toHaveProperty('queued'); expect(response).toHaveProperty('failed'); expect(response).toHaveProperty('errors'); expect(response.errors).toBeInstanceOf(Array); }); it('should have error array with isbn and error fields', () => { const errors = [ { isbn: '9780439064873', error: 'Queue send failed' }, { isbn: 'invalid', error: 'Invalid ISBN format' }, ]; errors.forEach((error) => { expect(error).toHaveProperty('isbn'); expect(error).toHaveProperty('error'); expect(typeof error.isbn).toBe('string'); expect(typeof error.error).toBe('string'); }); }); }); describe('ISBN Normalization', () => { it('should normalize ISBN-13 with hyphens', () => { const isbn = '978-0-439-06487-3'; const normalized = isbn.replace(/[-\s]/g, '').toUpperCase(); expect(normalized).toBe('9780439064873'); expect(normalized).toHaveLength(13); }); it('should normalize ISBN-10 to ISBN-10', () => { const isbn = '0-439-06487-2'; const normalized = isbn.replace(/[-\s]/g, '').toUpperCase(); expect(normalized).toBe('0439064872'); expect(normalized).toHaveLength(10); }); it('should handle ISBN with spaces', () => { const isbn = '978 0 439 06487 3'; const normalized = isbn.replace(/[-\s]/g, '').toUpperCase(); expect(normalized).toBe('9780439064873'); }); it('should validate ISBN-13 format', () => { const validISBN = '9780439064873'; const isValid = /^[0-9]{13}$/.test(validISBN); expect(isValid).toBe(true); }); it('should validate ISBN-10 format', () => { const validISBN = '0439064872'; const isValid = /^[0-9]{9}[0-9X]$/.test(validISBN); expect(isValid).toBe(true); }); it('should reject invalid ISBN formats', () => { const invalidISBNs = ['invalid', '123', '', null, undefined]; invalidISBNs.forEach((isbn) => { if (!isbn) { expect(isbn).toBeFalsy(); } else { const cleaned = isbn.replace(/[-\s]/g, '').toUpperCase(); const isValid = /^[0-9]{13}$/.test(cleaned) || /^[0-9]{9}[0-9X]$/.test(cleaned); expect(isValid).toBe(false); } }); }); }); describe('Batch Size Limits', () => { it('should enforce max 100 books per batch', () => { const maxBatchSize = 100; const testBatch = Array(150).fill({ isbn: '9780439064873' }); // Schema should reject > 100 expect(testBatch.length).toBeGreaterThan(maxBatchSize); }); it('should accept exactly 100 books', () => { const batchSize = 100; const testBatch = Array(batchSize).fill({ isbn: '9780439064873' }); expect(testBatch.length).toBe(100); }); it('should accept single book', () => { const testBatch = [{ isbn: '9780439064873' }]; expect(testBatch.length).toBe(1); }); }); describe('Queue Message Format', () => { it('should include all required queue fields', () => { const queueMessage = { isbn: '9780439064873', entity_type: 'edition', entity_key: '9780439064873', providers_to_try: ['isbndb', 'google-books', 'openlibrary'], priority: 'normal', source: 'user-import', title: 'Harry Potter', author: 'J.K. Rowling', queued_at: new Date().toISOString(), }; expect(queueMessage).toHaveProperty('isbn'); expect(queueMessage).toHaveProperty('entity_type'); expect(queueMessage).toHaveProperty('entity_key'); expect(queueMessage).toHaveProperty('providers_to_try'); expect(queueMessage).toHaveProperty('priority'); expect(queueMessage).toHaveProperty('source'); expect(queueMessage).toHaveProperty('queued_at'); }); it('should default priority to normal', () => { const priority = 'normal'; expect(priority).toBe('normal'); }); it('should default source to unknown', () => { const source = 'unknown'; expect(source).toBe('unknown'); }); it('should format queued_at as ISO 8601', () => { const queuedAt = new Date().toISOString(); const date = new Date(queuedAt); expect(date.toISOString()).toBe(queuedAt); }); }); describe('Error Handling', () => { it('should track failed ISBNs with error messages', () => { const failed = [ { isbn: 'invalid', error: 'Invalid ISBN format' }, { isbn: '9780439064873', error: 'Queue send failed' }, ]; expect(failed).toHaveLength(2); expect(failed[0].error).toBe('Invalid ISBN format'); expect(failed[1].error).toBe('Queue send failed'); }); it('should count successful vs failed operations', () => { const total = 100; const queued = 95; const failed = 5; expect(queued + failed).toBe(total); }); it('should use undefined as fallback for null ISBN', () => { const isbn = null; const fallback = isbn || 'undefined'; expect(fallback).toBe('undefined'); }); }); describe('Error Response Format', () => { it('should have consistent error response shape', () => { const error = { success: false, error: 'Queue operation failed', message: 'ENRICHMENT_QUEUE is not defined', }; expect(error.success).toBe(false); expect(error).toHaveProperty('error'); expect(error).toHaveProperty('message'); }); }); }); describe('POST /api/enrich/batch-direct', () => { describe('Response Schema', () => { it('should have all required fields in success response', () => { const response = { requested: 100, found: 95, enriched: 93, failed: 2, not_found: 5, covers_queued: 87, errors: [], api_calls: 1, duration_ms: 2340, quota: { used_today: 150, remaining: 14850, limit: 15000, last_reset: '2025-12-30', next_reset_in_hours: 12.5, buffer_remaining: 12850, can_make_calls: true, }, }; // Verify all required fields exist expect(response).toHaveProperty('requested'); expect(response).toHaveProperty('found'); expect(response).toHaveProperty('enriched'); expect(response).toHaveProperty('failed'); expect(response).toHaveProperty('not_found'); expect(response).toHaveProperty('covers_queued'); expect(response).toHaveProperty('errors'); expect(response).toHaveProperty('api_calls'); expect(response).toHaveProperty('duration_ms'); expect(response).toHaveProperty('quota'); }); it('should include quota status with all required fields', () => { const quota = { used_today: 150, remaining: 14850, limit: 15000, last_reset: '2025-12-30', next_reset_in_hours: 12.5, buffer_remaining: 12850, can_make_calls: true, }; expect(quota).toHaveProperty('used_today'); expect(quota).toHaveProperty('remaining'); expect(quota).toHaveProperty('limit'); expect(quota).toHaveProperty('last_reset'); expect(quota).toHaveProperty('next_reset_in_hours'); expect(quota).toHaveProperty('buffer_remaining'); expect(quota).toHaveProperty('can_make_calls'); }); }); describe('Batch Efficiency', () => { it('should use 1 API call for 1000 ISBNs', () => { const isbnCount = 1000; const apiCalls = 1; // ISBNdb Premium batch efficiency expect(apiCalls).toBe(1); expect(isbnCount / apiCalls).toBe(1000); }); it('should use 1 API call regardless of ISBN count', () => { const testCases = [1, 10, 100, 500, 1000]; testCases.forEach((count) => { const apiCalls = 1; // Always 1 call for batch expect(apiCalls).toBe(1); }); }); }); describe('ISBN Validation', () => { it('should separate valid and invalid ISBNs', () => { const isbns = [ '9780439064873', // Valid ISBN-13 '0439064872', // Valid ISBN-10 'invalid', // Invalid '123', // Invalid '978-0-439-06487-3', // Valid with hyphens ]; const valid: string[] = []; const invalid: string[] = []; isbns.forEach((isbn) => { const cleaned = isbn.replace(/[-\s]/g, '').toUpperCase(); const isValid = /^[0-9]{13}$/.test(cleaned) || /^[0-9]{9}[0-9X]$/.test(cleaned); if (isValid) { valid.push(cleaned); } else { invalid.push(isbn); } }); expect(valid).toHaveLength(3); expect(invalid).toHaveLength(2); expect(invalid).toContain('invalid'); expect(invalid).toContain('123'); }); it('should return 400 when no valid ISBNs', () => { const valid: string[] = []; const invalid = ['invalid', '123', 'bad-isbn']; if (valid.length === 0) { const response = { success: false, error: 'No valid ISBNs provided', invalid, }; expect(response.success).toBe(false); expect(response.error).toBe('No valid ISBNs provided'); expect(response.invalid).toEqual(invalid); } }); }); describe('Quota Checking', () => { it('should check quota before batch operation', () => { const quotaCheck = { allowed: true, reason: 'Sufficient quota available', status: { used_today: 100, remaining: 14900, buffer_remaining: 12900, limit: 15000, last_reset: '2025-12-30', next_reset_in_hours: 12.5, can_make_calls: true, }, }; expect(quotaCheck.allowed).toBe(true); expect(quotaCheck.status.remaining).toBeGreaterThan(0); }); it('should return 429 when quota exhausted', () => { const quotaCheck = { allowed: false, reason: 'Daily quota limit reached', status: { used_today: 15000, remaining: 0, buffer_remaining: -2000, limit: 15000, last_reset: '2025-12-30', next_reset_in_hours: 12.5, can_make_calls: false, }, }; if (!quotaCheck.allowed) { const response = { success: false, error: 'Quota exhausted', message: quotaCheck.reason, quota: quotaCheck.status, }; expect(response.success).toBe(false); expect(response.error).toBe('Quota exhausted'); expect(response.quota.can_make_calls).toBe(false); } }); it('should reserve 1 call for batch (not per-ISBN)', () => { const isbnCount = 1000; const quotaReservation = 1; // Single batch call expect(quotaReservation).toBe(1); expect(quotaReservation).not.toBe(isbnCount); }); }); describe('Work Key Generation', () => { it('should generate work keys in correct format', () => { // Simulating crypto.randomUUID().slice(0, 8) const uuid = '550e8400-e29b-41d4-a716-446655440000'; const workKey = `/works/isbndb-${uuid.slice(0, 8)}`; expect(workKey).toMatch(/^\/works\/isbndb-[a-f0-9]{8}$/); expect(workKey).toBe('/works/isbndb-550e8400'); }); it('should have 8-character UUID suffix', () => { const workKey = '/works/isbndb-550e8400'; const suffix = workKey.split('-').pop(); expect(suffix).toHaveLength(8); }); it('should start with /works/isbndb-', () => { const workKey = '/works/isbndb-550e8400'; expect(workKey).toMatch(/^\/works\/isbndb-/); }); }); describe('Cover Queueing', () => { it('should queue cover when URL available', () => { const coverUrls = { original: 'https://images.isbndb.com/covers/12/34/1234567890.jpg', large: 'https://images.isbndb.com/covers/12/34/1234567890-L.jpg', }; const shouldQueue = !!(coverUrls.original || coverUrls.large); expect(shouldQueue).toBe(true); }); it('should prioritize image_original over image', () => { const coverUrls = { original: 'https://images.isbndb.com/covers/original.jpg', large: 'https://images.isbndb.com/covers/large.jpg', }; const selectedUrl = coverUrls.original || coverUrls.large; expect(selectedUrl).toBe(coverUrls.original); }); it('should fallback to large if original missing', () => { const coverUrls = { large: 'https://images.isbndb.com/covers/large.jpg', }; const selectedUrl = (coverUrls as Record).original || coverUrls.large; expect(selectedUrl).toBe(coverUrls.large); }); it('should not queue when no cover URL', () => { const coverUrls = undefined; const shouldQueue = !!(coverUrls?.original || coverUrls?.large); expect(shouldQueue).toBe(false); }); }); describe('Result Counting', () => { it('should calculate not_found from requested - found', () => { const requested = 100; const found = 95; const notFound = requested - found; expect(notFound).toBe(5); }); it('should track enriched vs failed', () => { const found = 95; const enriched = 93; const failed = 2; expect(enriched + failed).toBe(found); }); it('should track covers_queued separately from enriched', () => { const enriched = 93; const coversQueued = 87; // Some may not have cover URLs expect(coversQueued).toBeLessThanOrEqual(enriched); }); }); describe('Duration Calculation', () => { it('should calculate duration from start to end', () => { const startTime = 1000; const endTime = 3340; const durationMs = endTime - startTime; expect(durationMs).toBe(2340); }); it('should measure in milliseconds', () => { const durationMs = 2340; expect(durationMs).toBeGreaterThan(0); expect(typeof durationMs).toBe('number'); }); }); describe('Error Tracking', () => { it('should track errors with ISBN and message', () => { const errors = [ { isbn: '9781234567890', error: 'Database operation failed' }, { isbn: '9789876543210', error: 'Foreign key constraint violation' }, ]; errors.forEach((error) => { expect(error).toHaveProperty('isbn'); expect(error).toHaveProperty('error'); }); }); it('should continue processing after individual failures', () => { const requested = 100; const failed = 2; const enriched = 93; // (found: 95 - failed: 2) expect(enriched).toBe(requested - 5 - failed); // 5 not found, 2 failed }); }); describe('Error Response Format', () => { it('should have consistent error response shape', () => { const error = { success: false, error: 'Batch enrichment failed', message: 'ISBNdb API timeout', }; expect(error.success).toBe(false); expect(error).toHaveProperty('error'); expect(error).toHaveProperty('message'); }); }); }); describe('POST /api/harvest/covers', () => { describe('Response Schema', () => { it('should have all required fields in success response', () => { const response = { queried: 1000, found_in_isbndb: 847, covers_queued: 823, editions_updated: 823, no_cover_url: 24, api_calls: 1, duration_ms: 1250, next_offset: 1000, estimated_remaining: 28572585, }; expect(response).toHaveProperty('queried'); expect(response).toHaveProperty('found_in_isbndb'); expect(response).toHaveProperty('covers_queued'); expect(response).toHaveProperty('editions_updated'); expect(response).toHaveProperty('no_cover_url'); expect(response).toHaveProperty('api_calls'); expect(response).toHaveProperty('duration_ms'); expect(response).toHaveProperty('next_offset'); expect(response).toHaveProperty('estimated_remaining'); }); }); describe('Batch Size Limits', () => { it('should enforce max 1000 batch size', () => { const maxBatchSize = 1000; const requestedBatch = 1500; // Schema should enforce max expect(maxBatchSize).toBe(1000); expect(requestedBatch).toBeGreaterThan(maxBatchSize); }); it('should default batch_size to 1000', () => { const defaultBatchSize = 1000; expect(defaultBatchSize).toBe(1000); }); it('should accept minimum batch_size of 1', () => { const minBatchSize = 1; expect(minBatchSize).toBeGreaterThanOrEqual(1); }); }); describe('Pagination Logic', () => { it('should calculate next_offset from offset + batch_size', () => { const offset = 5000; const batchSize = 1000; const nextOffset = offset + batchSize; expect(nextOffset).toBe(6000); }); it('should start at offset 0 by default', () => { const defaultOffset = 0; expect(defaultOffset).toBe(0); }); it('should allow custom offset', () => { const customOffset = 10000; expect(customOffset).toBeGreaterThanOrEqual(0); }); it('should track pagination across multiple calls', () => { // First call let offset = 0; const batchSize = 1000; let nextOffset = offset + batchSize; expect(nextOffset).toBe(1000); // Second call offset = nextOffset; nextOffset = offset + batchSize; expect(nextOffset).toBe(2000); // Third call offset = nextOffset; nextOffset = offset + batchSize; expect(nextOffset).toBe(3000); }); }); describe('English ISBN Filtering', () => { it('should filter for 978-0 prefix (English)', () => { const isbn = '9780439064873'; const isEnglish = isbn.startsWith('9780') || isbn.startsWith('9781'); expect(isEnglish).toBe(true); }); it('should filter for 978-1 prefix (English)', () => { const isbn = '9781492666868'; const isEnglish = isbn.startsWith('9780') || isbn.startsWith('9781'); expect(isEnglish).toBe(true); }); it('should reject non-English prefixes', () => { const foreignISBNs = [ '9782123456789', // French '9783123456789', // German '9784123456789', // Japanese '9787123456789', // Chinese ]; foreignISBNs.forEach((isbn) => { const isEnglish = isbn.startsWith('9780') || isbn.startsWith('9781'); expect(isEnglish).toBe(false); }); }); it('should validate ISBN-13 length', () => { const validISBN = '9780439064873'; const invalidISBN = '978043906487'; // 12 chars expect(validISBN).toHaveLength(13); expect(invalidISBN).toHaveLength(12); }); it('should use LIKE pattern in SQL', () => { // SQL: WHERE (isbn LIKE '9780%' OR isbn LIKE '9781%') const patterns = ['9780%', '9781%']; const testISBN = '9780439064873'; const matches = patterns.some((pattern) => { const regex = new RegExp('^' + pattern.replace('%', '.*')); return regex.test(testISBN); }); expect(matches).toBe(true); }); }); describe('Cover URL Priority', () => { it('should prioritize image_original over image', () => { const coverUrls = { original: 'https://images.isbndb.com/covers/original.jpg', large: 'https://images.isbndb.com/covers/large.jpg', }; const selectedUrl = coverUrls.original || coverUrls.large; expect(selectedUrl).toBe(coverUrls.original); }); it('should fallback to large when original missing', () => { const coverUrls = { large: 'https://images.isbndb.com/covers/large.jpg', }; const selectedUrl = (coverUrls as Record).original || coverUrls.large; expect(selectedUrl).toBe(coverUrls.large); }); it('should track no_cover_url when neither available', () => { const coverUrls = undefined; const coverUrl = coverUrls?.original || coverUrls?.large; if (!coverUrl) { const noCoverUrl = 1; expect(noCoverUrl).toBe(1); } }); }); describe('Queue Flag', () => { it('should default queue_covers to false', () => { const defaultQueueCovers = false; expect(defaultQueueCovers).toBe(false); }); it('should allow queue_covers override', () => { const queueCovers = true; expect(queueCovers).toBe(true); }); it('should only queue when queue_covers is true', () => { const queueCovers = false; const shouldQueue = queueCovers; expect(shouldQueue).toBe(false); }); }); describe('Edition Updates', () => { it('should track editions_updated separately from covers_queued', () => { const editionsUpdated = 823; const coversQueued = 823; const queueCovers = true; if (queueCovers) { expect(coversQueued).toBe(editionsUpdated); } else { expect(coversQueued).toBe(0); } }); it('should update all cover size URLs', () => { const coverUrls = { large: 'https://images.isbndb.com/covers/large.jpg', medium: 'https://images.isbndb.com/covers/medium.jpg', small: 'https://images.isbndb.com/covers/small.jpg', original: 'https://images.isbndb.com/covers/original.jpg', }; expect(coverUrls).toHaveProperty('large'); expect(coverUrls).toHaveProperty('medium'); expect(coverUrls).toHaveProperty('small'); expect(coverUrls).toHaveProperty('original'); }); it('should fallback cover sizes to main URL', () => { const mainUrl = 'https://images.isbndb.com/covers/main.jpg'; const coverUrls = { large: undefined, medium: undefined, small: undefined, }; const large = coverUrls.large || mainUrl; const medium = coverUrls.medium || mainUrl; const small = coverUrls.small || mainUrl; expect(large).toBe(mainUrl); expect(medium).toBe(mainUrl); expect(small).toBe(mainUrl); }); it('should set cover_source to isbndb', () => { const coverSource = 'isbndb'; expect(coverSource).toBe('isbndb'); }); }); describe('Result Counting', () => { it('should calculate found_in_isbndb from batch response', () => { const batchDataSize = 847; // Map.size const foundInIsbndb = batchDataSize; expect(foundInIsbndb).toBe(847); }); it('should count no_cover_url when URL missing', () => { const foundInIsbndb = 847; const editionsUpdated = 823; const noCoverUrl = foundInIsbndb - editionsUpdated; expect(noCoverUrl).toBe(24); }); it('should use 1 API call for batch', () => { const apiCalls = 1; expect(apiCalls).toBe(1); }); }); describe('Empty Results', () => { it('should handle no editions to process', () => { const isbns: string[] = []; const queried = isbns.length; if (queried === 0) { const response = { queried: 0, found_in_isbndb: 0, covers_queued: 0, editions_updated: 0, no_cover_url: 0, api_calls: 0, duration_ms: 100, next_offset: 5000, // Keep same offset message: 'No more editions to process', }; expect(response.queried).toBe(0); expect(response.api_calls).toBe(0); expect(response.message).toBe('No more editions to process'); } }); }); describe('Duration Calculation', () => { it('should measure total operation duration', () => { const startTime = 1000; const endTime = 2250; const durationMs = endTime - startTime; expect(durationMs).toBe(1250); }); }); describe('Estimated Remaining', () => { it('should include estimated_remaining count', () => { const estimatedRemaining = 28572585; expect(estimatedRemaining).toBeGreaterThan(0); expect(typeof estimatedRemaining).toBe('number'); }); it('should be optional field', () => { const responseWithoutEstimate = { queried: 1000, found_in_isbndb: 847, covers_queued: 823, editions_updated: 823, no_cover_url: 24, api_calls: 1, duration_ms: 1250, next_offset: 1000, }; expect(responseWithoutEstimate).not.toHaveProperty('estimated_remaining'); }); }); describe('Error Handling', () => { it('should continue processing after individual failures', () => { const foundInIsbndb = 847; const editionsUpdated = 823; const failed = foundInIsbndb - editionsUpdated; // Implicit failures // no_cover_url accounts for these expect(failed).toBe(24); }); }); describe('Error Response Format', () => { it('should have consistent error response shape', () => { const error = { success: false, error: 'Cover harvest failed', message: 'Database connection timeout', }; expect(error.success).toBe(false); expect(error).toHaveProperty('error'); expect(error).toHaveProperty('message'); }); }); describe('Database Query Logic', () => { it('should filter by primary_provider = openlibrary', () => { const primaryProvider = 'openlibrary'; expect(primaryProvider).toBe('openlibrary'); }); it('should filter by cover_url_large IS NULL', () => { const coverUrlLarge = null; expect(coverUrlLarge).toBeNull(); }); it('should order by created_at DESC for newest first', () => { const dates = [ new Date('2025-12-30'), new Date('2025-12-29'), new Date('2025-12-28'), ]; const sortedDesc = [...dates].sort((a, b) => b.getTime() - a.getTime()); expect(sortedDesc[0]).toEqual(dates[0]); // Newest first expect(sortedDesc[2]).toEqual(dates[2]); // Oldest last }); it('should apply OFFSET and LIMIT for pagination', () => { const offset = 5000; const limit = 1000; // SQL: OFFSET ${offset} LIMIT ${limit} expect(offset).toBe(5000); expect(limit).toBe(1000); }); }); describe('Cover Queue Message Format', () => { it('should include required queue fields', () => { const queueMessage = { isbn: '9780439064873', provider_url: 'https://images.isbndb.com/covers/original.jpg', priority: 'normal', source: 'cover-harvest', }; expect(queueMessage).toHaveProperty('isbn'); expect(queueMessage).toHaveProperty('provider_url'); expect(queueMessage).toHaveProperty('priority'); expect(queueMessage).toHaveProperty('source'); }); it('should set source to cover-harvest', () => { const source = 'cover-harvest'; expect(source).toBe('cover-harvest'); }); it('should set priority to normal', () => { const priority = 'normal'; expect(priority).toBe('normal'); }); }); }); describe('Cross-Route Business Logic', () => { describe('ISBN Normalization Consistency', () => { it('should normalize identically across all routes', () => { const isbn = '978-0-439-06487-3'; const normalized = isbn.replace(/[-\s]/g, '').toUpperCase(); // Same normalization used in all three routes expect(normalized).toBe('9780439064873'); }); it('should handle ISBN-10 consistently', () => { const isbn10 = '0-439-06487-2'; const normalized = isbn10.replace(/[-\s]/g, '').toUpperCase(); expect(normalized).toBe('0439064872'); expect(normalized).toHaveLength(10); }); }); describe('Error Response Consistency', () => { it('should use consistent error format across routes', () => { const errorFormats = [ { success: false, error: 'Queue operation failed', message: 'Details' }, { success: false, error: 'Batch enrichment failed', message: 'Details' }, { success: false, error: 'Cover harvest failed', message: 'Details' }, ]; errorFormats.forEach((error) => { expect(error).toHaveProperty('success'); expect(error).toHaveProperty('error'); expect(error).toHaveProperty('message'); expect(error.success).toBe(false); }); }); }); describe('Quota Awareness', () => { it('should understand 1 batch call = 1 quota call', () => { const isbnCount = 1000; const quotaCost = 1; // Per-request billing, not per-ISBN expect(quotaCost).toBe(1); expect(quotaCost).not.toBe(isbnCount); }); }); describe('Cover URL Prioritization', () => { it('should consistently prioritize original > large', () => { const testCases = [ { urls: { original: 'orig.jpg', large: 'large.jpg' }, expected: 'orig.jpg', }, { urls: { large: 'large.jpg' }, expected: 'large.jpg', }, { urls: {}, expected: undefined, }, ]; testCases.forEach(({ urls, expected }) => { const selected = (urls as Record).original || (urls as Record).large; expect(selected).toBe(expected); }); }); }); });