mirror of
https://github.com/bookorbit/bookorbit.git
synced 2026-11-03 08:21:39 -06:00
feat(metadata): fallback to search if lookup by id fails
Update the metadata fetch service to perform a fallback search when a provider's lookup by ID returns no result or an irrelevant candidate. This ensures that metadata retrieval can gracefully recover from incorrect or stale provider IDs. Closes #182
This commit is contained in:
@@ -186,7 +186,7 @@ describe('MetadataFetchService', () => {
|
||||
expect(google.search).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns no result when lookupById returns null for an existing provider id', async () => {
|
||||
it('falls back to provider search when lookupById returns null for an existing provider id', async () => {
|
||||
const google: IdentifiableProvider = {
|
||||
key: MetadataProviderKey.GOOGLE,
|
||||
label: 'Google',
|
||||
@@ -200,9 +200,36 @@ describe('MetadataFetchService', () => {
|
||||
service.search({ title: 'Dune', existingProviderIds: { [MetadataProviderKey.GOOGLE]: 'missing' } }).pipe(toArray()),
|
||||
);
|
||||
|
||||
expect(results).toEqual([]);
|
||||
expect(results).toEqual([candidate(MetadataProviderKey.GOOGLE, 'search-id', 'Dune')]);
|
||||
expect(google.lookupById).toHaveBeenCalledWith('missing', expect.anything());
|
||||
expect(google.search).not.toHaveBeenCalled();
|
||||
expect(google.search).toHaveBeenCalledTimes(1);
|
||||
expect(google.search).toHaveBeenCalledWith(expect.objectContaining({ title: 'Dune' }));
|
||||
});
|
||||
|
||||
it('falls back to provider search when lookupById returns an irrelevant candidate', async () => {
|
||||
const google: IdentifiableProvider = {
|
||||
key: MetadataProviderKey.GOOGLE,
|
||||
label: 'Google',
|
||||
identifiable: true,
|
||||
search: vi.fn().mockResolvedValue([candidate(MetadataProviderKey.GOOGLE, 'search-id', 'Dune')]),
|
||||
lookupById: vi.fn().mockResolvedValue(candidate(MetadataProviderKey.GOOGLE, 'stored-id', 'Completely Unrelated')),
|
||||
};
|
||||
registry.select.mockReturnValue([google]);
|
||||
|
||||
const results = await firstValueFrom(
|
||||
service
|
||||
.search({
|
||||
title: 'Dune',
|
||||
author: 'Frank Herbert',
|
||||
existingProviderIds: { [MetadataProviderKey.GOOGLE]: 'stored-id' },
|
||||
})
|
||||
.pipe(toArray()),
|
||||
);
|
||||
|
||||
expect(results).toEqual([candidate(MetadataProviderKey.GOOGLE, 'search-id', 'Dune')]);
|
||||
expect(google.lookupById).toHaveBeenCalledWith('stored-id', expect.anything());
|
||||
expect(google.search).toHaveBeenCalledTimes(1);
|
||||
expect(google.search).toHaveBeenCalledWith(expect.objectContaining({ title: 'Dune', author: 'Frank Herbert' }));
|
||||
});
|
||||
|
||||
it('isolates provider failures so one provider error does not fail the full stream', async () => {
|
||||
|
||||
@@ -114,9 +114,16 @@ export class MetadataFetchService {
|
||||
const existingProviderId = params.existingProviderIds?.[provider.key];
|
||||
if (isIdentifiable(provider) && existingProviderId) {
|
||||
const lookupResult = await provider.lookupById(existingProviderId, params.signal);
|
||||
return lookupResult ? [lookupResult] : [];
|
||||
if (lookupResult) {
|
||||
const rankedLookup = filterAndRank([lookupResult], params, 1);
|
||||
if (rankedLookup.length > 0) return rankedLookup;
|
||||
}
|
||||
}
|
||||
|
||||
return this.searchAndRankProvider(provider, params);
|
||||
}
|
||||
|
||||
private async searchAndRankProvider(provider: MetadataProvider, params: MetadataSearchParams): Promise<MetadataCandidate[]> {
|
||||
const primary = filterAndRank(await provider.search(params), params);
|
||||
const hasIsbn = hasText(params.isbn);
|
||||
if (!hasIsbn) return primary;
|
||||
|
||||
Reference in New Issue
Block a user