126 lines
4.7 KiB
TypeScript
126 lines
4.7 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "vitest";
|
|
import { databaseBackupFilePath, databaseBackupRunsToPrune, databaseBackupScheduleIsDue, nextDatabaseBackupRunAt, normalizeDatabaseBackupSchedule, resolveScheduledDatabaseBackupTargets, supportsScheduledDatabaseBackup, type DatabaseBackupRun, type DatabaseBackupSchedule } from "../../apps/desktop/src/lib/backup/scheduledDatabaseBackup.ts";
|
|
|
|
function schedule(overrides: Partial<DatabaseBackupSchedule> = {}): DatabaseBackupSchedule {
|
|
return {
|
|
id: "schedule-1",
|
|
name: "Nightly backup",
|
|
enabled: true,
|
|
connectionId: "connection-1",
|
|
databases: [],
|
|
destinationDirectory: "C:\\backups",
|
|
frequency: "daily",
|
|
intervalHours: 6,
|
|
timeOfDay: "02:00",
|
|
weekday: 1,
|
|
includeStructure: true,
|
|
includeData: true,
|
|
includeObjects: true,
|
|
dropTableIfExists: false,
|
|
retentionCount: 2,
|
|
createdAt: "2026-07-16T00:00:00.000Z",
|
|
updatedAt: "2026-07-16T00:00:00.000Z",
|
|
nextRunAt: "2026-07-17T00:00:00.000Z",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function run(id: string, startedAt: string): DatabaseBackupRun {
|
|
return {
|
|
id,
|
|
scheduleId: "schedule-1",
|
|
scheduleName: "Nightly backup",
|
|
connectionId: "connection-1",
|
|
connectionName: "Postgres",
|
|
trigger: "scheduled",
|
|
status: "success",
|
|
startedAt,
|
|
completedAt: startedAt,
|
|
files: [],
|
|
};
|
|
}
|
|
|
|
test("daily backup advances to the next configured local time", () => {
|
|
const after = new Date(2026, 6, 16, 3, 30, 0);
|
|
const next = nextDatabaseBackupRunAt(schedule(), after);
|
|
|
|
assert.equal(next.getFullYear(), 2026);
|
|
assert.equal(next.getMonth(), 6);
|
|
assert.equal(next.getDate(), 17);
|
|
assert.equal(next.getHours(), 2);
|
|
assert.equal(next.getMinutes(), 0);
|
|
});
|
|
|
|
test("weekly backup selects the next matching weekday", () => {
|
|
const after = new Date(2026, 6, 16, 12, 0, 0);
|
|
const next = nextDatabaseBackupRunAt(schedule({ frequency: "weekly", weekday: 1, timeOfDay: "08:15" }), after);
|
|
|
|
assert.equal(next.getDay(), 1);
|
|
assert.equal(next.getHours(), 8);
|
|
assert.equal(next.getMinutes(), 15);
|
|
assert.ok(next.getTime() > after.getTime());
|
|
});
|
|
|
|
test("normalization deduplicates databases and bounds schedule values", () => {
|
|
const normalized = normalizeDatabaseBackupSchedule({
|
|
...schedule(),
|
|
databases: ["app", "app", " analytics "],
|
|
intervalHours: 999,
|
|
retentionCount: 0,
|
|
timeOfDay: "invalid",
|
|
});
|
|
|
|
assert.ok(normalized);
|
|
assert.deepEqual(normalized.databases, ["app", "analytics"]);
|
|
assert.equal(normalized.intervalHours, 168);
|
|
assert.equal(normalized.retentionCount, 1);
|
|
assert.equal(normalized.timeOfDay, "02:00");
|
|
});
|
|
|
|
test("due check respects enabled state and next run", () => {
|
|
const now = new Date("2026-07-16T12:00:00.000Z");
|
|
assert.equal(databaseBackupScheduleIsDue(schedule({ nextRunAt: "2026-07-16T11:59:00.000Z" }), now), true);
|
|
assert.equal(databaseBackupScheduleIsDue(schedule({ enabled: false, nextRunAt: "2026-07-16T11:59:00.000Z" }), now), false);
|
|
});
|
|
|
|
test("backup file names are unique and safe for schema-aware exports", () => {
|
|
const path = databaseBackupFilePath("C:\\backups", "Nightly: prod", "app/private", new Date(2026, 6, 16, 2, 3, 4), "12345678-abcd");
|
|
|
|
assert.equal(path, "C:\\backups\\dbx-backup__Nightly_ prod__20260716-020304__app_private__12345678.sql");
|
|
});
|
|
|
|
test("retention pruning keeps the newest successful runs", () => {
|
|
const runs = [run("new", "2026-07-16T03:00:00.000Z"), run("middle", "2026-07-16T02:00:00.000Z"), run("old", "2026-07-16T01:00:00.000Z")];
|
|
|
|
assert.deepEqual(
|
|
databaseBackupRunsToPrune(runs, "schedule-1", 2).map((item) => item.id),
|
|
["old"],
|
|
);
|
|
});
|
|
|
|
test("all-database backups use the complete database list", () => {
|
|
assert.deepEqual(resolveScheduledDatabaseBackupTargets([], ["visible", "hidden"]), ["visible", "hidden"]);
|
|
});
|
|
|
|
test("explicit backup databases fail when any configured target is missing", () => {
|
|
assert.throws(() => resolveScheduledDatabaseBackupTargets(["app", "renamed"], ["app"]), /renamed/);
|
|
});
|
|
|
|
test("scheduled backups are limited to databases with consistent snapshot support", () => {
|
|
assert.equal(supportsScheduledDatabaseBackup("postgres"), true);
|
|
assert.equal(supportsScheduledDatabaseBackup("mysql"), true);
|
|
assert.equal(supportsScheduledDatabaseBackup("sqlite"), false);
|
|
assert.equal(supportsScheduledDatabaseBackup("sqlserver"), false);
|
|
});
|
|
|
|
test("retention never selects failed backup runs", () => {
|
|
const failed = { ...run("failed", "2026-07-16T04:00:00.000Z"), status: "failed" as const };
|
|
const successful = [run("new", "2026-07-16T03:00:00.000Z"), run("old", "2026-07-16T01:00:00.000Z")];
|
|
|
|
assert.deepEqual(
|
|
databaseBackupRunsToPrune([failed, ...successful], "schedule-1", 1).map((item) => item.id),
|
|
["old"],
|
|
);
|
|
});
|