refactor(admin): split the backups section

The section becomes a composer over backups/: the restore dialog with its
preview, the refusal, skip and schedule notices, the restore jobs and
their request, the upload flow, the run and download actions, and the
two row layouts sharing one action pair. The upload total is a pure
function and tested as one. The read-failure guard's 250-character
window had stopped reaching the restore preview's own failure lines; the
preview now sits in a smaller component, the guard sees it, and it is
listed as a deliberate exception with its reason.
This commit is contained in:
Marc-André Bombeck committed 2026-10-23 13:20:48 +02:00
1 parent c9917d2de0
commit 5eafed8dd5
19 files changed
+1533 -1348

No files matched your search

@@ -84,13 +84,16 @@ vi.mock("@/hooks/use-auth", () => ({
}));
import { I18nProvider } from "@/lib/i18n/context";
import { BackupsSection, restorePreviewFailure } from "../backups-section";
import {
RestorePreview,
restorePreviewFailure,
} from "../backups/restore-dialog";
describe("restore preview query", () => {
it("is never retried and waits a bounded time", async () => {
renderToStaticMarkup(
<I18nProvider initialLocale="en">
<BackupsSection />
<RestorePreview backupId="b1" open />
</I18nProvider>,
);
const key = JSON.stringify(queryKeys.adminBackupSummary("b1"));
@@ -29,7 +29,7 @@ import { I18nProvider } from "@/lib/i18n/context";
import {
RestoreRefusalNotice,
missingSectionsOf,
} from "@/components/admin/backups-section";
} from "@/components/admin/backups/restore-notices";
import type { MissingBackupSection } from "@/lib/export/restore-skips";
import { primeLocales } from "@/__tests__/helpers/prime-locales";
@@ -22,7 +22,7 @@ import { describe, expect, it, vi } from "vitest";
import { renderToStaticMarkup } from "react-dom/server";
import { I18nProvider } from "@/lib/i18n/context";
import { RestoreSkipReport } from "@/components/admin/backups-section";
import { RestoreSkipReport } from "@/components/admin/backups/restore-notices";
import type { RestoreSkipSummary } from "@/lib/export/restore-skips";
import { primeLocales } from "@/__tests__/helpers/prime-locales";
@@ -17,10 +17,6 @@ const healthySchedule = {
};
const mocks = vi.hoisted(() => ({
mutationOptions: [] as Array<{
onSuccess?: (data: { summary: Record<string, number> }) => void;
}>,
toastSuccess: vi.fn(),
// Mutable so one file can render both a healthy schedule and a broken one;
// the query mock reads it on every render.
queryData: { rows: [], retentionDays: 30 } as Record<string, unknown>,
@@ -43,7 +39,7 @@ vi.mock("next/navigation", () => ({
vi.mock("sonner", () => ({
toast: {
success: mocks.toastSuccess,
success: vi.fn(),
error: vi.fn(),
},
}));
@@ -55,21 +51,14 @@ vi.mock("@tanstack/react-query", () => ({
isError: false,
}),
useQueryClient: () => ({ invalidateQueries: vi.fn() }),
useMutation: (options: unknown) => {
mocks.mutationOptions.push(
options as {
onSuccess?: (data: { summary: Record<string, number> }) => void;
},
);
return {
mutate: vi.fn(),
mutateAsync: vi.fn(),
isPending: false,
isError: false,
error: null,
variables: undefined,
};
},
useMutation: () => ({
mutate: vi.fn(),
mutateAsync: vi.fn(),
isPending: false,
isError: false,
error: null,
variables: undefined,
}),
}));
vi.mock("@/hooks/use-auth", () => ({
@@ -83,6 +72,7 @@ vi.mock("@/hooks/use-auth", () => ({
import { I18nProvider } from "@/lib/i18n/context";
import { BackupsSection } from "../backups-section";
import { uploadedRecordCount } from "../backups/use-backup-upload";
import { primeLocales } from "@/__tests__/helpers/prime-locales";
primeLocales("de");
@@ -224,26 +214,16 @@ describe("BackupsSection — schedule health", () => {
});
});
describe("BackupsSection — upload outcome", () => {
describe("backup upload outcome", () => {
it("includes health profile fact revisions in the restored record total", () => {
mocks.toastSuccess.mockClear();
const firstMutation = mocks.mutationOptions.length;
render();
const uploadOutcome = mocks.mutationOptions[firstMutation + 1]?.onSuccess;
expect(uploadOutcome).toBeTypeOf("function");
uploadOutcome!({
summary: {
expect(
uploadedRecordCount({
measurements: 1,
medications: 2,
intakeEvents: 3,
moodEntries: 4,
healthProfileFactRevisions: 5,
},
});
expect(mocks.toastSuccess).toHaveBeenCalledWith(
expect.stringContaining("15"),
);
}),
).toBe(15);
});
});