fix(web): admin — scan initial après création, retrait des fallbacks mock
La création d'une bibliothèque déclenche un scan initial avec état de réessai en cas d'échec; suppression des fallbacks mock sur create/scan pour ne masquer aucune erreur réelle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -31,6 +31,26 @@ describe("api fallback helpers", () => {
|
||||
expect(headers.has("Content-Type")).toBe(false);
|
||||
});
|
||||
|
||||
it("surfaces create library API errors without fallback", async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue(
|
||||
new Response(JSON.stringify({ message: "Library path does not exist" }), {
|
||||
status: 400,
|
||||
statusText: "Bad Request",
|
||||
headers: { "Content-Type": "application/json" }
|
||||
})
|
||||
);
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
await expect(api.createLibrary({ name: "Books", path: "/missing", enabled: true })).rejects.toThrow("Library path does not exist");
|
||||
});
|
||||
|
||||
it("does not fallback when scan enqueue fails", async () => {
|
||||
const fetchMock = vi.fn().mockRejectedValue(new Error("offline"));
|
||||
vi.stubGlobal("fetch", fetchMock);
|
||||
|
||||
await expect(api.scanLibrary(42)).rejects.toThrow("offline");
|
||||
});
|
||||
|
||||
it("sends metadata source updates to the admin endpoint", async () => {
|
||||
const fetchMock = vi.fn().mockResolvedValue(
|
||||
new Response(JSON.stringify({ isbnPriorityEnabled: false, sources: [] }), {
|
||||
|
||||
@ -185,15 +185,14 @@ export const api = {
|
||||
async createLibrary(input: CreateLibraryDto): Promise<LibraryDto> {
|
||||
return request<LibraryDto>("/admin/libraries", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(input),
|
||||
fallback: { id: Date.now(), createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), ...input }
|
||||
body: JSON.stringify(input)
|
||||
});
|
||||
},
|
||||
async deleteLibrary(id: number): Promise<void> {
|
||||
await request<{ ok: true }>(`/admin/libraries/${id}`, { method: "DELETE" });
|
||||
},
|
||||
async scanLibrary(id: number): Promise<JobDto> {
|
||||
return request<JobDto>(`/admin/libraries/${id}/scan`, { method: "POST", fallback: mockJobs[0] });
|
||||
return request<JobDto>(`/admin/libraries/${id}/scan`, { method: "POST" });
|
||||
},
|
||||
async jobs(): Promise<JobDto[]> {
|
||||
return request<JobDto[]>("/admin/jobs", { fallback: mockJobs });
|
||||
|
||||
@ -13,6 +13,7 @@ export function AdminPage() {
|
||||
const [path, setPath] = useState("/library");
|
||||
const [error, setError] = useState<string>();
|
||||
const [success, setSuccess] = useState<string>();
|
||||
const [scanRetryLibrary, setScanRetryLibrary] = useState<LibraryDto>();
|
||||
|
||||
async function refresh() {
|
||||
setLoading(true);
|
||||
@ -53,28 +54,38 @@ export function AdminPage() {
|
||||
event.preventDefault();
|
||||
setError(undefined);
|
||||
setSuccess(undefined);
|
||||
setScanRetryLibrary(undefined);
|
||||
try {
|
||||
await api.createLibrary({ name, path, enabled: true });
|
||||
const created = await api.createLibrary({ name, path, enabled: true });
|
||||
await refresh();
|
||||
setSuccess(`Bibliothèque "${name}" ajoutée.`);
|
||||
setName("Bibliotheque locale");
|
||||
setPath("/library");
|
||||
try {
|
||||
await api.scanLibrary(created.id);
|
||||
await refresh();
|
||||
setSuccess(`Bibliothèque "${created.name}" ajoutée. Scan initial demandé.`);
|
||||
} catch (scanError) {
|
||||
setScanRetryLibrary(created);
|
||||
setSuccess(
|
||||
`Bibliothèque "${created.name}" ajoutée, mais le scan initial n'a pas pu être demandé. Tu peux réessayer le scan.`
|
||||
);
|
||||
setError(scanError instanceof Error ? `Scan initial impossible : ${scanError.message}` : "Scan initial impossible.");
|
||||
}
|
||||
} catch (createError) {
|
||||
const fallback = getApiFallback<LibraryDto>(createError);
|
||||
if (fallback) setLibraries((current) => [fallback, ...current]);
|
||||
setError(fallback ? "Creation en mode secours, synchronisation a retenter." : "Creation impossible");
|
||||
setError(createError instanceof Error ? `Création impossible : ${createError.message}` : "Création impossible.");
|
||||
}
|
||||
}
|
||||
|
||||
async function scan(id: number) {
|
||||
setError(undefined);
|
||||
setSuccess(undefined);
|
||||
setScanRetryLibrary(undefined);
|
||||
try {
|
||||
await api.scanLibrary(id);
|
||||
await refresh();
|
||||
setSuccess("Scan demandé.");
|
||||
} catch (scanError) {
|
||||
const fallback = getApiFallback<JobDto>(scanError);
|
||||
if (fallback) setJobs((current) => [fallback, ...current]);
|
||||
setError(fallback ? "Scan place en file de secours, statut a verifier." : "Scan impossible");
|
||||
setError(scanError instanceof Error ? `Scan impossible : ${scanError.message}` : "Scan impossible.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,6 +97,7 @@ export function AdminPage() {
|
||||
|
||||
setError(undefined);
|
||||
setSuccess(undefined);
|
||||
setScanRetryLibrary(undefined);
|
||||
try {
|
||||
await api.deleteLibrary(library.id);
|
||||
setLibraries((current) => current.filter((item) => item.id !== library.id));
|
||||
@ -104,14 +116,22 @@ export function AdminPage() {
|
||||
</div>
|
||||
<ErrorRibbon message={error} />
|
||||
{success && <div className="success-ribbon">{success}</div>}
|
||||
{error && (
|
||||
{scanRetryLibrary ? (
|
||||
<div className="retry-row">
|
||||
<span>La bibliothèque est conservée dans la liste.</span>
|
||||
<button className="ghost-button" onClick={() => scan(scanRetryLibrary.id)}>
|
||||
<Play size={16} />
|
||||
Réessayer le scan
|
||||
</button>
|
||||
</div>
|
||||
) : error ? (
|
||||
<div className="retry-row">
|
||||
<span>Les formulaires restent disponibles.</span>
|
||||
<button className="ghost-button" onClick={() => void refresh()}>
|
||||
Reessayer
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
) : null}
|
||||
<form className="admin-form" onSubmit={createLibrary}>
|
||||
<label>
|
||||
Nom de la bibliothèque
|
||||
|
||||
Reference in New Issue
Block a user