Files
ReadMeABook/tests/utils/audiobook-matcher.test.ts
T
kikootwo a97979358f Implement file hash-based library matching and remove fuzzy ASIN matching
Adds file hash-based matching for Audiobookshelf library items to ensure 100% accurate ASIN assignment for RMAB-organized content. Removes fuzzy matching from library availability checks, making all matching ASIN-only to eliminate false positives and race conditions. Updates database schema, processors, and matcher utilities; adds new tests and documentation for the new matching strategy. Removes obsolete scripts, Dockerfile, and related tests; updates docker-compose for test environments.
2026-01-28 11:42:00 -05:00

144 lines
4.1 KiB
TypeScript

/**
* Component: Audiobook Matcher Tests
* Documentation: documentation/integrations/audible.md
*/
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { createPrismaMock } from '../helpers/prisma';
const prismaMock = createPrismaMock();
vi.mock('@/lib/db', () => ({
prisma: prismaMock,
}));
describe('audiobook-matcher', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('returns ASIN exact match from dedicated field', async () => {
prismaMock.plexLibrary.findMany.mockResolvedValue([
{
plexGuid: 'guid-1',
plexRatingKey: 'rating-1',
title: 'Test Book',
author: 'Test Author',
asin: 'B00TEST123',
isbn: null,
},
]);
const { findPlexMatch } = await import('@/lib/utils/audiobook-matcher');
const match = await findPlexMatch({
asin: 'B00TEST123',
title: 'Test Book',
author: 'Test Author',
});
expect(match?.plexGuid).toBe('guid-1');
});
it('rejects candidates with mismatched ASINs in plexGuid', async () => {
prismaMock.plexLibrary.findMany.mockResolvedValue([
{
plexGuid: 'com.plexapp.agents.audible://B00WRONG999',
plexRatingKey: null,
title: 'Test Book',
author: 'Test Author',
asin: null,
isbn: null,
},
]);
const { findPlexMatch } = await import('@/lib/utils/audiobook-matcher');
const match = await findPlexMatch({
asin: 'B00RIGHT123',
title: 'Test Book',
author: 'Test Author',
});
expect(match).toBeNull();
});
it('returns null when no ASIN match exists (fuzzy matching removed)', async () => {
prismaMock.plexLibrary.findMany.mockResolvedValue([]);
const { findPlexMatch } = await import('@/lib/utils/audiobook-matcher');
const match = await findPlexMatch({
asin: 'B00TEST999',
title: 'Great Book',
author: 'Different Author',
narrator: 'Jane Narrator',
});
expect(match).toBeNull();
});
it('matches library items by ASIN or ISBN only (no fuzzy fallback)', async () => {
const items = [
{ id: '1', externalId: 'g1', title: 'Alpha', author: 'Author A', asin: 'ASIN1' },
{ id: '2', externalId: 'g2', title: 'Beta', author: 'Author B', isbn: '978-1-23456-789-7' },
{ id: '3', externalId: 'g3', title: 'Gamma Book', author: 'Author C' },
];
const { matchAudiobook } = await import('@/lib/utils/audiobook-matcher');
const asinMatch = matchAudiobook({ title: 'x', author: 'y', asin: 'ASIN1' }, items);
expect(asinMatch?.externalId).toBe('g1');
const isbnMatch = matchAudiobook({ title: 'x', author: 'y', isbn: '9781234567897' }, items);
expect(isbnMatch?.externalId).toBe('g2');
const noMatch = matchAudiobook({ title: 'Gamma Book', author: 'Author C' }, items);
expect(noMatch).toBeNull();
});
it('enriches audiobooks with availability and request status', async () => {
prismaMock.plexLibrary.findMany
.mockResolvedValueOnce([
{
plexGuid: 'guid-1',
plexRatingKey: null,
title: 'Book One',
author: 'Author One',
asin: 'ASIN1',
isbn: null,
},
])
.mockResolvedValueOnce([]);
prismaMock.audiobook.findMany.mockResolvedValue([
{
id: 'a1',
audibleAsin: 'ASIN1',
requests: [
{
id: 'r1',
status: 'downloading',
userId: 'other-user',
user: { plexUsername: 'OtherUser' },
},
],
},
]);
const { enrichAudiobooksWithMatches } = await import('@/lib/utils/audiobook-matcher');
const results = await enrichAudiobooksWithMatches(
[
{ asin: 'ASIN1', title: 'Book One', author: 'Author One' },
{ asin: 'ASIN2', title: 'Book Two', author: 'Author Two' },
],
'current-user'
);
expect(results[0].isAvailable).toBe(true);
expect(results[0].isRequested).toBe(true);
expect(results[0].requestedByUsername).toBe('OtherUser');
expect(results[1].isAvailable).toBe(false);
expect(results[1].isRequested).toBe(false);
});
});