1 parent
ee08f356e9
commit
cf57df35da
2 files changed
+90
-2
No files matched your search
@@ -144,7 +144,10 @@ async function loadDatabases(connectionId: string, preserveSelection: boolean) {
|
|||||||
const config = connectionStore.getConfig(connectionId);
|
const config = connectionStore.getConfig(connectionId);
|
||||||
const names = config?.db_type === "dameng" ? await fetchNamespaceOptionsForConnection(connectionId, config) : (await api.listDatabases(connectionId)).map((database) => database.name);
|
const names = config?.db_type === "dameng" ? await fetchNamespaceOptionsForConnection(connectionId, config) : (await api.listDatabases(connectionId)).map((database) => database.name);
|
||||||
databaseOptions.value = names;
|
databaseOptions.value = names;
|
||||||
if (!preserveSelection) {
|
if (preserveSelection) {
|
||||||
|
const selected = new Set(selectedDatabases.value);
|
||||||
|
selectedDatabases.value = names.filter((database) => selected.has(database));
|
||||||
|
} else {
|
||||||
selectedDatabases.value = [];
|
selectedDatabases.value = [];
|
||||||
allDatabases.value = true;
|
allDatabases.value = true;
|
||||||
draft.value.tableFilterMode = "all";
|
draft.value.tableFilterMode = "all";
|
||||||
|
|||||||
+86
-1
@@ -4,9 +4,11 @@ import { createApp, nextTick, type App } from "vue";
|
|||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import i18n from "@/i18n";
|
import i18n from "@/i18n";
|
||||||
import ScheduledDatabaseBackupSettings from "@/components/backup/ScheduledDatabaseBackupSettings.vue";
|
import ScheduledDatabaseBackupSettings from "@/components/backup/ScheduledDatabaseBackupSettings.vue";
|
||||||
|
import type { DatabaseBackupSchedule } from "@/lib/backup/scheduledDatabaseBackup";
|
||||||
|
|
||||||
const mocks = vi.hoisted(() => ({
|
const mocks = vi.hoisted(() => ({
|
||||||
connections: [] as Array<{ id: string; name: string; db_type: string }>,
|
connections: [] as Array<{ id: string; name: string; db_type: string }>,
|
||||||
|
schedules: [] as DatabaseBackupSchedule[],
|
||||||
ensureConnected: vi.fn(async () => {}),
|
ensureConnected: vi.fn(async () => {}),
|
||||||
listDatabases: vi.fn(async () => [{ name: "app" }]),
|
listDatabases: vi.fn(async () => [{ name: "app" }]),
|
||||||
toast: vi.fn(),
|
toast: vi.fn(),
|
||||||
@@ -29,7 +31,7 @@ vi.mock("@/stores/connectionStore", () => ({
|
|||||||
|
|
||||||
vi.mock("@/composables/useScheduledDatabaseBackups", () => ({
|
vi.mock("@/composables/useScheduledDatabaseBackups", () => ({
|
||||||
useScheduledDatabaseBackups: () => ({
|
useScheduledDatabaseBackups: () => ({
|
||||||
schedules: { __v_isRef: true, value: [] },
|
schedules: { __v_isRef: true, value: mocks.schedules },
|
||||||
runs: { __v_isRef: true, value: [] },
|
runs: { __v_isRef: true, value: [] },
|
||||||
activeScheduleIds: new Set<string>(),
|
activeScheduleIds: new Set<string>(),
|
||||||
activeRunIds: new Set<string>(),
|
activeRunIds: new Set<string>(),
|
||||||
@@ -78,13 +80,55 @@ function addScheduleButton(): HTMLButtonElement {
|
|||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function schedule(overrides: Partial<DatabaseBackupSchedule> = {}): DatabaseBackupSchedule {
|
||||||
|
return {
|
||||||
|
id: "schedule-1",
|
||||||
|
name: "Nightly backup",
|
||||||
|
enabled: true,
|
||||||
|
connectionId: "mysql-1",
|
||||||
|
databases: ["app"],
|
||||||
|
tableFilterMode: "all",
|
||||||
|
tablePatterns: [],
|
||||||
|
destinationDirectory: "/backups",
|
||||||
|
frequency: "daily",
|
||||||
|
intervalHours: 6,
|
||||||
|
timeOfDay: "02:00",
|
||||||
|
weekday: 1,
|
||||||
|
includeStructure: true,
|
||||||
|
includeData: true,
|
||||||
|
includeObjects: true,
|
||||||
|
dropTableIfExists: false,
|
||||||
|
retentionCount: 10,
|
||||||
|
createdAt: "2026-08-12T00:00:00.000Z",
|
||||||
|
updatedAt: "2026-08-12T00:00:00.000Z",
|
||||||
|
nextRunAt: "2026-08-13T02:00:00.000Z",
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buttonWithTitle(title: string): HTMLButtonElement {
|
||||||
|
const button = Array.from(document.body.querySelectorAll("button")).find((item) => item.title === title);
|
||||||
|
if (!button) throw new Error(`Button not found: ${title}`);
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveScheduleButton(): HTMLButtonElement {
|
||||||
|
const dialog = document.body.querySelector('[data-slot="dialog-content"]');
|
||||||
|
const label = String(i18n.global.t("common.save"));
|
||||||
|
const button = Array.from(dialog?.querySelectorAll("button") ?? []).find((item) => item.textContent?.trim() === label);
|
||||||
|
if (!button) throw new Error("Save schedule button not found");
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
for (const app of mountedApps.splice(0)) app.unmount();
|
for (const app of mountedApps.splice(0)) app.unmount();
|
||||||
document.body.innerHTML = "";
|
document.body.innerHTML = "";
|
||||||
mocks.connections.splice(0);
|
mocks.connections.splice(0);
|
||||||
|
mocks.schedules.splice(0);
|
||||||
mocks.ensureConnected.mockClear();
|
mocks.ensureConnected.mockClear();
|
||||||
mocks.listDatabases.mockClear();
|
mocks.listDatabases.mockClear();
|
||||||
mocks.toast.mockClear();
|
mocks.toast.mockClear();
|
||||||
|
mocks.saveSchedule.mockClear();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("ScheduledDatabaseBackupSettings schedule dialog", () => {
|
describe("ScheduledDatabaseBackupSettings schedule dialog", () => {
|
||||||
@@ -112,4 +156,45 @@ describe("ScheduledDatabaseBackupSettings schedule dialog", () => {
|
|||||||
expect(addScheduleButton().disabled).toBe(true);
|
expect(addScheduleButton().disabled).toBe(true);
|
||||||
expect(document.body.textContent).toContain(String(i18n.global.t("databaseBackup.noSupportedConnections")));
|
expect(document.body.textContent).toContain(String(i18n.global.t("databaseBackup.noSupportedConnections")));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("removes unavailable databases from an edited schedule before saving", async () => {
|
||||||
|
mocks.connections.push({ id: "mysql-1", name: "Local MySQL", db_type: "mysql" });
|
||||||
|
mocks.schedules.push(schedule({ databases: ["app", "deleted"] }));
|
||||||
|
mocks.listDatabases.mockResolvedValueOnce([{ name: "app" }]);
|
||||||
|
await mountSettings();
|
||||||
|
|
||||||
|
buttonWithTitle(String(i18n.global.t("databaseBackup.edit"))).click();
|
||||||
|
await flush();
|
||||||
|
saveScheduleButton().click();
|
||||||
|
await flush();
|
||||||
|
|
||||||
|
expect(mocks.saveSchedule).toHaveBeenCalledWith(expect.objectContaining({ databases: ["app"] }));
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not allow saving when every selected database is unavailable", async () => {
|
||||||
|
mocks.connections.push({ id: "mysql-1", name: "Local MySQL", db_type: "mysql" });
|
||||||
|
mocks.schedules.push(schedule({ databases: ["deleted"] }));
|
||||||
|
mocks.listDatabases.mockResolvedValueOnce([{ name: "app" }]);
|
||||||
|
await mountSettings();
|
||||||
|
|
||||||
|
buttonWithTitle(String(i18n.global.t("databaseBackup.edit"))).click();
|
||||||
|
await flush();
|
||||||
|
|
||||||
|
expect(saveScheduleButton().disabled).toBe(true);
|
||||||
|
expect(mocks.saveSchedule).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves configured databases when refreshing the list fails", async () => {
|
||||||
|
mocks.connections.push({ id: "mysql-1", name: "Local MySQL", db_type: "mysql" });
|
||||||
|
mocks.schedules.push(schedule({ databases: ["app", "archive"] }));
|
||||||
|
mocks.listDatabases.mockRejectedValueOnce(new Error("database list failed"));
|
||||||
|
await mountSettings();
|
||||||
|
|
||||||
|
buttonWithTitle(String(i18n.global.t("databaseBackup.edit"))).click();
|
||||||
|
await flush();
|
||||||
|
saveScheduleButton().click();
|
||||||
|
await flush();
|
||||||
|
|
||||||
|
expect(mocks.saveSchedule).toHaveBeenCalledWith(expect.objectContaining({ databases: ["app", "archive"] }));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user