fix(backup): verify scheduled export destinations before writing
This commit is contained in:
1 parent
a8f966dcfd
commit
4ce27f8ae5
12 files changed
+652
-7
No files matched your search
@@ -203,6 +203,7 @@ async function submitSchedule() {
|
||||
if (!canSave.value) return;
|
||||
saving.value = true;
|
||||
try {
|
||||
await api.recordDatabaseExportDestination(draft.value.destinationDirectory);
|
||||
saveSchedule({
|
||||
...draft.value,
|
||||
databases: allDatabases.value ? [] : [...selectedDatabases.value],
|
||||
|
||||
+20
@@ -11,6 +11,7 @@ const mocks = vi.hoisted(() => ({
|
||||
schedules: [] as DatabaseBackupSchedule[],
|
||||
ensureConnected: vi.fn(async () => {}),
|
||||
listDatabases: vi.fn(async () => [{ name: "app" }]),
|
||||
recordDatabaseExportDestination: vi.fn(async (_directory: string) => {}),
|
||||
toast: vi.fn(),
|
||||
saveSchedule: vi.fn(),
|
||||
setScheduleEnabled: vi.fn(),
|
||||
@@ -53,6 +54,7 @@ vi.mock("@/lib/backend/api", () => ({
|
||||
listDatabases: mocks.listDatabases,
|
||||
deleteDatabaseBackupFiles: vi.fn(),
|
||||
revealPathInFileManager: vi.fn(),
|
||||
recordDatabaseExportDestination: mocks.recordDatabaseExportDestination,
|
||||
}));
|
||||
|
||||
const mountedApps: App[] = [];
|
||||
@@ -127,6 +129,8 @@ afterEach(() => {
|
||||
mocks.schedules.splice(0);
|
||||
mocks.ensureConnected.mockClear();
|
||||
mocks.listDatabases.mockClear();
|
||||
mocks.recordDatabaseExportDestination.mockReset();
|
||||
mocks.recordDatabaseExportDestination.mockResolvedValue(undefined);
|
||||
mocks.toast.mockClear();
|
||||
mocks.saveSchedule.mockClear();
|
||||
});
|
||||
@@ -197,4 +201,20 @@ describe("ScheduledDatabaseBackupSettings schedule dialog", () => {
|
||||
|
||||
expect(mocks.saveSchedule).toHaveBeenCalledWith(expect.objectContaining({ databases: ["app", "archive"] }));
|
||||
});
|
||||
|
||||
it("does not save when the destination identity cannot be recorded", async () => {
|
||||
mocks.connections.push({ id: "mysql-1", name: "Local MySQL", db_type: "mysql" });
|
||||
mocks.schedules.push(schedule());
|
||||
mocks.recordDatabaseExportDestination.mockRejectedValueOnce(new Error("backup destination unavailable"));
|
||||
await mountSettings();
|
||||
|
||||
buttonWithTitle(String(i18n.global.t("databaseBackup.edit"))).click();
|
||||
await flush();
|
||||
saveScheduleButton().click();
|
||||
await flush();
|
||||
|
||||
expect(mocks.recordDatabaseExportDestination).toHaveBeenCalledWith("/backups");
|
||||
expect(mocks.saveSchedule).not.toHaveBeenCalled();
|
||||
expect(mocks.toast).toHaveBeenCalledWith("backup destination unavailable", 5000);
|
||||
});
|
||||
});
|
||||
@@ -416,6 +416,7 @@ export const releaseTableImportSource = forward("releaseTableImportSource");
|
||||
export const beginDatabaseBackupSnapshot = forward("beginDatabaseBackupSnapshot");
|
||||
export const exportDatabaseSql = forward("exportDatabaseSql");
|
||||
export const cancelDatabaseExport = forward("cancelDatabaseExport");
|
||||
export const recordDatabaseExportDestination = forward("recordDatabaseExportDestination");
|
||||
export const exportQueryResultCsv = forward("exportQueryResultCsv");
|
||||
export const exportTableDataCsv = forward("exportTableDataCsv");
|
||||
export const exportQueryResultXlsx = forward("exportQueryResultXlsx");
|
||||
|
||||
@@ -2224,6 +2224,10 @@ export async function cancelDatabaseExport(exportId: string): Promise<void> {
|
||||
await post("/api/export/database/cancel", { exportId });
|
||||
}
|
||||
|
||||
export async function recordDatabaseExportDestination(_directory: string): Promise<void> {
|
||||
throw new Error("Scheduled database backups are only available in the desktop app.");
|
||||
}
|
||||
|
||||
// --- Table Export ---
|
||||
|
||||
export async function startTableExport(request: TableExportRequest, onProgress: (progress: TableExportProgress) => void): Promise<TableExportProgress> {
|
||||
|
||||
@@ -4405,6 +4405,10 @@ export async function cancelDatabaseExport(exportId: string): Promise<void> {
|
||||
await invoke("cancel_database_export", { exportId });
|
||||
}
|
||||
|
||||
export async function recordDatabaseExportDestination(directory: string): Promise<void> {
|
||||
await invoke("record_database_export_destination", { directory });
|
||||
}
|
||||
|
||||
export async function exportQueryResultCsv(filePath: string, columns: string[], rows: readonly (readonly XlsxCellValue[])[]): Promise<void> {
|
||||
return invoke("export_query_result_csv", {
|
||||
request: {
|
||||
|
||||
Reference in new issue
Block a user