fix(schedule): disable if saving with manual-only mode
Some checks failed
Release Workflow / determine-release-type (push) Has been cancelled
Release Workflow / checks (push) Has been cancelled
Release Workflow / e2e-tests (push) Has been cancelled
Release Workflow / build-images (push) Has been cancelled
Release Workflow / publish-release (push) Has been cancelled

This commit is contained in:
Nicolas Meienberger 2026-04-04 21:07:38 +02:00
parent 74d20d5be3
commit f11e3b7f82
2 changed files with 148 additions and 1 deletions

View file

@ -1,6 +1,6 @@
import { afterEach, expect, test, vi } from "vitest"; import { afterEach, expect, test, vi } from "vitest";
import { HttpResponse, http, server } from "~/test/msw/server"; import { HttpResponse, http, server } from "~/test/msw/server";
import { cleanup, render, screen, userEvent } from "~/test/test-utils"; import { cleanup, fireEvent, render, screen, userEvent } from "~/test/test-utils";
const navigateMock = vi.fn(async () => {}); const navigateMock = vi.fn(async () => {});
@ -27,6 +27,7 @@ test("submits the computed cron expression when saving a daily schedule", async
return HttpResponse.json({ return HttpResponse.json({
shortId: "backup-1", shortId: "backup-1",
name: "Backup 1", name: "Backup 1",
enabled: true,
repository: { shortId: "repo-1", name: "Repo 1", type: "local" }, repository: { shortId: "repo-1", name: "Repo 1", type: "local" },
volume: { volume: {
id: "volume-1", id: "volume-1",
@ -82,6 +83,151 @@ test("submits the computed cron expression when saving a daily schedule", async
await expect(submittedBody).resolves.toMatchObject({ await expect(submittedBody).resolves.toMatchObject({
frequency: "daily", frequency: "daily",
enabled: true,
cronExpression: "00 02 * * *",
});
});
test("disables the schedule when switching an enabled custom cron schedule to manual only", async () => {
const submittedBody = new Promise<Record<string, unknown>>((resolve) => {
server.use(
http.get("/api/v1/backups/:shortId", () => {
return HttpResponse.json({
shortId: "backup-1",
name: "Backup 1",
enabled: true,
repository: { shortId: "repo-1", name: "Repo 1", type: "local" },
volume: {
id: "volume-1",
shortId: "vol-1",
name: "Volume 1",
config: { backend: "directory", path: "/mnt" },
},
cronExpression: "*/13 * * * *",
retentionPolicy: null,
includePaths: ["/project"],
includePatterns: [],
excludePatterns: [],
excludeIfPresent: [],
oneFileSystem: false,
customResticParams: [],
});
}),
http.get("/api/v1/repositories", () => {
return HttpResponse.json([{ shortId: "repo-1", name: "Repo 1", type: "local" }]);
}),
http.get("/api/v1/volumes/:shortId/files", () => {
return HttpResponse.json({
files: [{ name: "project", path: "/project", type: "directory" }],
path: "/",
offset: 0,
limit: 100,
total: 1,
hasMore: false,
});
}),
http.patch("/api/v1/backups/:shortId", async ({ request }) => {
const body = (await request.json()) as Record<string, unknown>;
resolve(body);
return HttpResponse.json({
shortId: "backup-1",
volume: {
id: "volume-1",
shortId: "vol-1",
name: "Volume 1",
config: { backend: "directory", path: "/mnt" },
},
repository: { shortId: "repo-1", name: "Repo 1", type: "local" },
...body,
});
}),
);
});
render(<EditBackupPage backupId="backup-1" />, { withSuspense: true });
const nativeFrequencySelect = (await screen.findAllByRole("combobox"))
.at(1)
?.parentElement?.querySelector('select[aria-hidden="true"]');
if (!(nativeFrequencySelect instanceof HTMLSelectElement)) {
throw new Error("Expected hidden native select for frequency field");
}
fireEvent.change(nativeFrequencySelect, { target: { value: "manual" } });
await userEvent.click(screen.getByRole("button", { name: "Update schedule" }));
await expect(submittedBody).resolves.toMatchObject({
frequency: "manual",
enabled: false,
cronExpression: "",
});
});
test("preserves a disabled schedule when saving a non-manual frequency", async () => {
const submittedBody = new Promise<Record<string, unknown>>((resolve) => {
server.use(
http.get("/api/v1/backups/:shortId", () => {
return HttpResponse.json({
shortId: "backup-1",
name: "Backup 1",
enabled: false,
repository: { shortId: "repo-1", name: "Repo 1", type: "local" },
volume: {
id: "volume-1",
shortId: "vol-1",
name: "Volume 1",
config: { backend: "directory", path: "/mnt" },
},
cronExpression: "0 2 * * *",
retentionPolicy: null,
includePaths: ["/project"],
includePatterns: [],
excludePatterns: [],
excludeIfPresent: [],
oneFileSystem: false,
customResticParams: [],
});
}),
http.get("/api/v1/repositories", () => {
return HttpResponse.json([{ shortId: "repo-1", name: "Repo 1", type: "local" }]);
}),
http.get("/api/v1/volumes/:shortId/files", () => {
return HttpResponse.json({
files: [{ name: "project", path: "/project", type: "directory" }],
path: "/",
offset: 0,
limit: 100,
total: 1,
hasMore: false,
});
}),
http.patch("/api/v1/backups/:shortId", async ({ request }) => {
const body = (await request.json()) as Record<string, unknown>;
resolve(body);
return HttpResponse.json({
shortId: "backup-1",
volume: {
id: "volume-1",
shortId: "vol-1",
name: "Volume 1",
config: { backend: "directory", path: "/mnt" },
},
repository: { shortId: "repo-1", name: "Repo 1", type: "local" },
...body,
});
}),
);
});
render(<EditBackupPage backupId="backup-1" />, { withSuspense: true });
await userEvent.click(await screen.findByRole("button", { name: "Update schedule" }));
await expect(submittedBody).resolves.toMatchObject({
frequency: "daily",
enabled: false,
cronExpression: "00 02 * * *", cronExpression: "00 02 * * *",
}); });
}); });

View file

@ -51,6 +51,7 @@ export function EditBackupPage({ backupId }: { backupId: string }) {
path: { shortId: schedule.shortId }, path: { shortId: schedule.shortId },
body: { body: {
...formValues, ...formValues,
enabled: formValues.frequency === "manual" ? false : schedule.enabled,
cronExpression, cronExpression,
retentionPolicy: Object.keys(retentionPolicy).length > 0 ? retentionPolicy : undefined, retentionPolicy: Object.keys(retentionPolicy).length > 0 ? retentionPolicy : undefined,
}, },