Files
zerobyte/app/server/modules/lifecycle/__tests__/startup.test.ts
2026-05-30 16:54:49 +02:00

83 lines
3.6 KiB
TypeScript

import { afterEach, beforeEach, expect, test, vi } from "vitest";
import { Scheduler } from "~/server/core/scheduler";
import { serverEvents } from "~/server/core/events";
import { config } from "~/server/core/config";
import { db } from "~/server/db/db";
import { backupsService } from "~/server/modules/backups/backups.service";
import { repositoriesService } from "~/server/modules/repositories/repositories.service";
import { notificationsService } from "~/server/modules/notifications/notifications.service";
import { volumeService } from "~/server/modules/volumes/volume.service";
import * as provisioningModule from "~/server/modules/provisioning/provisioning";
import { taskStore } from "~/server/modules/tasks/tasks.store";
import { createTestBackupSchedule } from "~/test/helpers/backup";
import { createTestRepository } from "~/test/helpers/repository";
import { createTestVolume } from "~/test/helpers/volume";
import { TEST_ORG_ID } from "~/test/helpers/organization";
const loadStartupModule = async () => {
const moduleUrl = new URL("../startup.ts", import.meta.url);
moduleUrl.searchParams.set("test", crypto.randomUUID());
return import(moduleUrl.href);
};
let originalEnableLocalAgent: boolean;
beforeEach(() => {
originalEnableLocalAgent = config.flags.enableLocalAgent;
config.flags.enableLocalAgent = true;
vi.spyOn(Scheduler, "start").mockResolvedValue();
vi.spyOn(Scheduler, "clear").mockResolvedValue();
vi.spyOn(Scheduler, "build").mockImplementation(() => ({ schedule: vi.fn() }));
vi.spyOn(provisioningModule, "syncProvisionedResources").mockResolvedValue();
vi.spyOn(backupsService, "cleanupOrphanedSchedules").mockResolvedValue({ deletedSchedules: 0 });
vi.spyOn(volumeService, "updateVolume").mockResolvedValue(undefined as never);
vi.spyOn(repositoriesService, "updateRepository").mockResolvedValue(undefined as never);
vi.spyOn(notificationsService, "updateDestination").mockResolvedValue(undefined as never);
});
afterEach(() => {
config.flags.enableLocalAgent = originalEnableLocalAgent;
vi.restoreAllMocks();
});
test("marks active tasks stale and keeps stuck backup schedule recovery silent", async () => {
const emitSpy = vi.spyOn(serverEvents, "emit");
const notificationSpy = vi.spyOn(notificationsService, "sendBackupNotification").mockResolvedValue();
const volume = await createTestVolume();
const repository = await createTestRepository();
const schedule = await createTestBackupSchedule({
volumeId: volume.id,
repositoryId: repository.id,
lastBackupStatus: "in_progress",
});
const task = taskStore.create({
id: "task-startup-active",
organizationId: TEST_ORG_ID,
resourceType: "backup_schedule",
resourceId: String(schedule.id),
targetAgentId: "local",
input: {
kind: "backup",
scheduleId: schedule.id,
scheduleShortId: schedule.shortId,
manual: false,
},
});
taskStore.markRunning(task.id);
const { startup } = await loadStartupModule();
await startup();
const updatedTask = await db.query.tasksTable.findFirst({ where: { id: task.id } });
const updatedSchedule = await db.query.backupSchedulesTable.findFirst({ where: { id: schedule.id } });
expect(updatedTask?.status).toBe("stale");
expect(updatedTask?.error).toBe("Zerobyte was restarted before this task completed");
expect(updatedTask?.finishedAt).toEqual(expect.any(Number));
expect(updatedSchedule?.lastBackupStatus).toBe("warning");
expect(updatedSchedule?.lastBackupError).toBe("Zerobyte was restarted during the last scheduled backup");
expect(emitSpy).not.toHaveBeenCalledWith("backup:completed", expect.anything());
expect(notificationSpy).not.toHaveBeenCalled();
});