fix(api): robustesse des providers de métadonnées
Recherche ISBN directe sur Open Library avec repli titre/auteur, normalisation des réponses, timeouts et User-Agent explicites sur les trois providers distants, abandon de l'extraction de jaquette CBR en échec silencieux (fallback métadonnées seules), tests des providers et garde du test de migration si better-sqlite3 est indisponible. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
101
apps/api/src/metadata/metadata-providers.test.ts
Normal file
101
apps/api/src/metadata/metadata-providers.test.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { BnfProvider } from "./adapters/bnf.provider.js";
|
||||
import { GoogleBooksProvider } from "./adapters/google-books.provider.js";
|
||||
import { OpenLibraryProvider } from "./adapters/open-library.provider.js";
|
||||
import { MetadataLookup, MetadataProviderConfig } from "./metadata.types.js";
|
||||
|
||||
const lookup: MetadataLookup = {
|
||||
title: "Harry Potter et la Chambre des Secrets",
|
||||
author: "J. K. Rowling",
|
||||
filePath: "/library/HP/Harry Potter et la Chambre des Secrets (J.K. Rowling).epub",
|
||||
identifiers: { isbn10: null, isbn13: "9782070612376", candidates: ["9782070612376"] }
|
||||
};
|
||||
|
||||
const config: MetadataProviderConfig = { provider: "openlibrary", enabled: true, priority: 1, apiKey: null };
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe("metadata providers", () => {
|
||||
it("queries OpenLibrary by ISBN and normalizes the matching book", async () => {
|
||||
const fetchMock = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce(
|
||||
jsonResponse({
|
||||
title: "Harry Potter et la Chambre des Secrets",
|
||||
authors: [{ key: "/authors/OL23919A" }],
|
||||
languages: [{ key: "/languages/fre" }],
|
||||
publishers: ["Gallimard jeunesse"],
|
||||
publish_date: "2007-03",
|
||||
isbn_13: ["9782070612376"],
|
||||
isbn_10: ["2070612379"]
|
||||
})
|
||||
)
|
||||
.mockResolvedValueOnce(jsonResponse({ name: "J. K. Rowling" }));
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
const result = await new OpenLibraryProvider().lookup(lookup, config);
|
||||
|
||||
expect(String((fetchMock.mock.calls[0] as unknown[])[0])).toBe("https://openlibrary.org/isbn/9782070612376.json");
|
||||
expect(result).toMatchObject({
|
||||
title: "Harry Potter et la Chambre des Secrets",
|
||||
author: "J. K. Rowling",
|
||||
isbn: "9782070612376"
|
||||
});
|
||||
});
|
||||
|
||||
it("treats Google Books quota exhaustion as a non-blocking miss", async () => {
|
||||
const fetchMock = vi.fn(async () => jsonResponse({ error: { code: 429, status: "RESOURCE_EXHAUSTED" } }, 429));
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
const result = await new GoogleBooksProvider().lookup(lookup, { ...config, provider: "googlebooks" });
|
||||
|
||||
expect(String((fetchMock.mock.calls[0] as unknown[])[0])).toContain("q=isbn%3A9782070612376");
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("parses BnF SRU UNIMARC records returned for ISBN lookup", async () => {
|
||||
vi.stubGlobal(
|
||||
"fetch",
|
||||
vi.fn(async () =>
|
||||
textResponse(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<srw:searchRetrieveResponse xmlns:srw="http://www.loc.gov/zing/srw/">
|
||||
<srw:records><srw:record><srw:recordData>
|
||||
<mxc:record xmlns:mxc="info:lc/xmlns/marcxchange-v2">
|
||||
<mxc:datafield tag="073"><mxc:subfield code="a">9782070612376</mxc:subfield></mxc:datafield>
|
||||
<mxc:datafield tag="101"><mxc:subfield code="a">fre</mxc:subfield></mxc:datafield>
|
||||
<mxc:datafield tag="200">
|
||||
<mxc:subfield code="a">Harry Potter et la chambre des secrets</mxc:subfield>
|
||||
<mxc:subfield code="f">J. K. Rowling</mxc:subfield>
|
||||
</mxc:datafield>
|
||||
<mxc:datafield tag="210">
|
||||
<mxc:subfield code="c">Gallimard jeunesse</mxc:subfield>
|
||||
<mxc:subfield code="d">DL 2007</mxc:subfield>
|
||||
</mxc:datafield>
|
||||
<mxc:datafield tag="330"><mxc:subfield code="a">Résumé BnF.</mxc:subfield></mxc:datafield>
|
||||
</mxc:record>
|
||||
</srw:recordData></srw:record></srw:records>
|
||||
</srw:searchRetrieveResponse>`)
|
||||
)
|
||||
);
|
||||
|
||||
const result = await new BnfProvider().lookup(lookup, { ...config, provider: "bnf" });
|
||||
|
||||
expect(result).toMatchObject({
|
||||
title: "Harry Potter et la chambre des secrets",
|
||||
author: "J. K. Rowling",
|
||||
publisher: "Gallimard jeunesse",
|
||||
publishedDate: "2007",
|
||||
isbn: "9782070612376"
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function jsonResponse(body: unknown, status = 200): Response {
|
||||
return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
|
||||
}
|
||||
|
||||
function textResponse(body: string, status = 200): Response {
|
||||
return new Response(body, { status, headers: { "content-type": "application/xml" } });
|
||||
}
|
||||
Reference in New Issue
Block a user