From 36e8c0de75244bb62ab380e7cc8d936114fe5890 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Wed, 1 Apr 2026 19:36:31 +0200 Subject: [PATCH] chore: migrate to vitest --- AGENTS.md | 2 +- .../components/__test__/file-tree.test.tsx | 2 +- .../components/__test__/restore-form.test.tsx | 13 +++-- .../__tests__/snapshot-tree-browser.test.tsx | 2 +- .../__tests__/use-server-events.test.tsx | 12 ++-- app/client/lib/__tests__/datetime.test.ts | 6 +- app/client/lib/volume-path.test.ts | 2 +- .../auth/routes/__tests__/login.test.tsx | 17 ++++-- .../__tests__/snapshot-file-browser.test.tsx | 29 +++++++--- .../routes/__tests__/backup-details.test.tsx | 53 +++++++++++------- app/server/__tests__/isolation.test.ts | 2 +- app/server/cli/commands/change-email.test.ts | 2 +- .../core/__tests__/repository-mutex.test.ts | 2 +- app/server/core/__tests__/scheduler.test.ts | 14 ++--- app/server/lib/__tests__/username.test.ts | 2 +- .../__tests__/create-default-org.test.ts | 2 +- .../__tests__/convert-legacy-user.test.ts | 2 +- .../auth.account-and-membership.test.ts | 2 +- .../auth.cleanup-user-organizations.test.ts | 2 +- .../auth.controller.security.test.ts | 2 +- .../utils/__tests__/backend-utils.test.ts | 33 ++++++++--- .../__tests__/backups.controller.test.ts | 2 +- .../__tests__/backups.execution.test.ts | 28 +++++----- .../__tests__/backups.patterns.test.ts | 2 +- .../backups/__tests__/backups.queries.test.ts | 2 +- .../backups/__tests__/backups.service.test.ts | 14 ++--- .../__tests__/events.controller.test.ts | 2 +- .../__tests__/notification-builders.test.ts | 2 +- .../notification-config-secrets.test.ts | 2 +- .../notifications.controller.test.ts | 2 +- .../modules/provisioning/provisioning.test.ts | 8 +-- .../__tests__/repositories.controller.test.ts | 8 +-- .../__tests__/repositories.service.test.ts | 48 ++++++++-------- .../modules/sso/__tests__/sso.errors.test.ts | 2 +- .../sso/__tests__/sso.integration.test.ts | 2 +- .../sso/__tests__/sso.registration.test.ts | 5 +- .../sso/__tests__/sso.security.test.ts | 5 +- .../modules/sso/__tests__/sso.service.test.ts | 2 +- .../__tests__/require-invitation.test.ts | 12 ++-- .../trust-provider-for-linking.test.ts | 2 +- .../__tests__/validate-callback-urls.test.ts | 2 +- .../__tests__/validate-provider-id.test.ts | 2 +- .../__tests__/system.controller.test.ts | 2 +- .../__tests__/volumes.controller.test.ts | 2 +- .../volumes/__tests__/volumes.service.test.ts | 2 +- app/server/utils/__tests__/spawn.test.ts | 2 +- app/server/utils/common-ancestor.test.ts | 2 +- app/test/setup-client.ts | 9 +-- app/test/setup-shared.ts | 32 +++++++++++ app/test/setup.ts | 26 +-------- app/utils/__tests__/path.test.ts | 2 +- bun.lock | 47 +++++++++++++++- package.json | 7 ++- packages/core/package.json | 2 +- .../restic/commands/__tests__/backup.test.ts | 8 +-- .../restic/commands/__tests__/copy.test.ts | 8 +-- .../__tests__/delete-snapshots.test.ts | 8 +-- .../restic/commands/__tests__/dump.test.ts | 8 +-- .../src/restic/commands/__tests__/ls.test.ts | 8 +-- .../restic/commands/__tests__/restore.test.ts | 6 +- .../commands/__tests__/tag-snapshots.test.ts | 8 +-- .../helpers/__tests__/build-env.test.ts | 2 +- .../helpers/__tests__/build-repo-url.test.ts | 2 +- .../__tests__/validate-custom-params.test.ts | 2 +- packages/core/src/utils/__tests__/fs.test.ts | 2 +- packages/core/test/setup.ts | 4 +- packages/core/vitest.config.ts | 16 ++++++ tsconfig.json | 2 +- vitest.config.ts | 55 +++++++++++++++++++ 69 files changed, 402 insertions(+), 229 deletions(-) create mode 100644 app/test/setup-shared.ts create mode 100644 packages/core/vitest.config.ts create mode 100644 vitest.config.ts diff --git a/AGENTS.md b/AGENTS.md index 2d1f8237..3fb61040 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,7 +24,7 @@ bun run tsc bun run test # Run a specific test file -bunx dotenv-cli -e .env.test -- bun test --preload ./app/test/setup.ts path/to/test.ts +bunx dotenv-cli -e .env.test -- bunx --bun vitest run --project server path/to/test.ts ``` ### Building diff --git a/app/client/components/__test__/file-tree.test.tsx b/app/client/components/__test__/file-tree.test.tsx index c01f71ca..d53e9180 100644 --- a/app/client/components/__test__/file-tree.test.tsx +++ b/app/client/components/__test__/file-tree.test.tsx @@ -1,5 +1,5 @@ /** biome-ignore-all lint/style/noNonNullAssertion: Testing file - non-null assertions are acceptable here */ -import { afterEach, expect, test, describe } from "bun:test"; +import { afterEach, describe, expect, test } from "vitest"; import { cleanup, render, screen, fireEvent, within } from "@testing-library/react"; import { useState } from "react"; import { FileTree, type FileEntry } from "../file-tree"; diff --git a/app/client/components/__test__/restore-form.test.tsx b/app/client/components/__test__/restore-form.test.tsx index 463162ef..aa651ab3 100644 --- a/app/client/components/__test__/restore-form.test.tsx +++ b/app/client/components/__test__/restore-form.test.tsx @@ -1,11 +1,16 @@ -import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test"; +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { HttpResponse, http, server } from "~/test/msw/server"; import { cleanup, render, screen, userEvent, waitFor, within } from "~/test/test-utils"; import { fromAny } from "@total-typescript/shoehorn"; -await mock.module("@tanstack/react-router", () => ({ - useNavigate: () => mock(() => {}), -})); +vi.mock("@tanstack/react-router", async (importOriginal) => { + const actual = await importOriginal(); + + return { + ...actual, + useNavigate: (() => vi.fn(async () => {})) as typeof actual.useNavigate, + }; +}); import { RestoreForm } from "../restore-form"; diff --git a/app/client/components/file-browsers/__tests__/snapshot-tree-browser.test.tsx b/app/client/components/file-browsers/__tests__/snapshot-tree-browser.test.tsx index 2cde8a90..2550a2be 100644 --- a/app/client/components/file-browsers/__tests__/snapshot-tree-browser.test.tsx +++ b/app/client/components/file-browsers/__tests__/snapshot-tree-browser.test.tsx @@ -1,5 +1,5 @@ import type { ComponentProps } from "react"; -import { afterEach, describe, expect, test } from "bun:test"; +import { afterEach, describe, expect, test } from "vitest"; import { HttpResponse, http, server } from "~/test/msw/server"; import { cleanup, fireEvent, render, screen, userEvent, waitFor, within } from "~/test/test-utils"; diff --git a/app/client/hooks/__tests__/use-server-events.test.tsx b/app/client/hooks/__tests__/use-server-events.test.tsx index 40ca9759..2e106f3d 100644 --- a/app/client/hooks/__tests__/use-server-events.test.tsx +++ b/app/client/hooks/__tests__/use-server-events.test.tsx @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test"; +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { useEffect, useState } from "react"; import { cleanup, createTestQueryClient, render, screen } from "~/test/test-utils"; import { useServerEvents } from "../use-server-events"; @@ -7,7 +7,7 @@ class MockEventSource { static instances: MockEventSource[] = []; public onerror: ((event: Event) => void) | null = null; - public close = mock(() => {}); + public close = vi.fn(() => {}); private listeners = new Map void>>(); constructor(public url: string) { @@ -71,8 +71,8 @@ describe("useServerEvents", () => { beforeEach(() => { MockEventSource.reset(); globalThis.EventSource = MockEventSource as unknown as typeof EventSource; - console.info = mock(() => {}); - console.error = mock(() => {}); + console.info = vi.fn(() => {}); + console.error = vi.fn(() => {}); }); afterEach(() => { @@ -85,8 +85,8 @@ describe("useServerEvents", () => { test("shares one EventSource across consumers and invalidates queries once on backup completion", async () => { const queryClient = createTestQueryClient(); - const invalidateQueries = mock(async () => undefined); - const refetchQueries = mock(async () => undefined); + const invalidateQueries = vi.fn(async () => undefined); + const refetchQueries = vi.fn(async () => undefined); queryClient.invalidateQueries = invalidateQueries as typeof queryClient.invalidateQueries; queryClient.refetchQueries = refetchQueries as typeof queryClient.refetchQueries; diff --git a/app/client/lib/__tests__/datetime.test.ts b/app/client/lib/__tests__/datetime.test.ts index 464ef65e..10580407 100644 --- a/app/client/lib/__tests__/datetime.test.ts +++ b/app/client/lib/__tests__/datetime.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"; +import { afterEach, describe, expect, test, vi } from "vitest"; import { DEFAULT_TIME_FORMAT, formatDate, @@ -12,7 +12,7 @@ import { } from "../datetime"; afterEach(() => { - mock.restore(); + vi.restoreAllMocks(); }); const sampleDate = new Date("2026-01-10T14:30:00.000Z"); @@ -56,7 +56,7 @@ describe("datetime formatters", () => { }); test("formats relative times without approximation prefixes", () => { - const nowSpy = spyOn(Date, "now").mockReturnValue(new Date("2026-01-10T14:35:00.000Z").getTime()); + const nowSpy = vi.spyOn(Date, "now").mockReturnValue(new Date("2026-01-10T14:35:00.000Z").getTime()); expect(formatTimeAgo(sampleDate)).toBe("5 minutes ago"); diff --git a/app/client/lib/volume-path.test.ts b/app/client/lib/volume-path.test.ts index a0369ca8..5843dc00 100644 --- a/app/client/lib/volume-path.test.ts +++ b/app/client/lib/volume-path.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { describe, expect, test } from "vitest"; import { getVolumeMountPath } from "./volume-path"; import { fromAny } from "@total-typescript/shoehorn"; diff --git a/app/client/modules/auth/routes/__tests__/login.test.tsx b/app/client/modules/auth/routes/__tests__/login.test.tsx index eff96959..a50f4fe3 100644 --- a/app/client/modules/auth/routes/__tests__/login.test.tsx +++ b/app/client/modules/auth/routes/__tests__/login.test.tsx @@ -1,12 +1,17 @@ -import { afterEach, describe, expect, mock, test } from "bun:test"; +import { afterEach, describe, expect, test, vi } from "vitest"; import { cleanup, render, screen } from "~/test/test-utils"; -await mock.module("@tanstack/react-router", () => ({ - useNavigate: () => mock(() => {}), -})); +vi.mock("@tanstack/react-router", async (importOriginal) => { + const actual = await importOriginal(); -await mock.module("~/client/modules/sso/components/sso-login-section", () => ({ - SsoLoginSection: () => null, + return { + ...actual, + useNavigate: (() => vi.fn(async () => {})) as typeof actual.useNavigate, + }; +}); + +vi.mock("~/client/modules/sso/components/sso-login-section", () => ({ + SsoLoginSection: () => <>, })); import { LoginPage } from "../login"; diff --git a/app/client/modules/backups/components/__tests__/snapshot-file-browser.test.tsx b/app/client/modules/backups/components/__tests__/snapshot-file-browser.test.tsx index e3491854..81b855bc 100644 --- a/app/client/modules/backups/components/__tests__/snapshot-file-browser.test.tsx +++ b/app/client/modules/backups/components/__tests__/snapshot-file-browser.test.tsx @@ -1,18 +1,29 @@ import type { ReactNode } from "react"; -import { afterEach, describe, expect, mock, test } from "bun:test"; +import { afterEach, describe, expect, test, vi } from "vitest"; import { HttpResponse, http, server } from "~/test/msw/server"; import { cleanup, render, screen, waitFor } from "~/test/test-utils"; import { fromAny } from "@total-typescript/shoehorn"; -await mock.module("@tanstack/react-router", () => ({ - Link: ({ children }: { children?: ReactNode }) => {children}, -})); +vi.mock("@tanstack/react-router", async (importOriginal) => { + const actual = await importOriginal(); -await mock.module("~/client/lib/datetime", () => ({ - useTimeFormat: () => ({ - formatDateTime: () => "2026-03-26 00:00", - }), -})); + return { + ...actual, + Link: (({ children }: { children?: ReactNode }) => {children}) as typeof actual.Link, + }; +}); + +vi.mock("~/client/lib/datetime", async (importOriginal) => { + const actual = await importOriginal(); + + return { + ...actual, + useTimeFormat: () => ({ + ...actual.useTimeFormat(), + formatDateTime: () => "2026-03-26 00:00", + }), + }; +}); import { SnapshotFileBrowser } from "../snapshot-file-browser"; diff --git a/app/client/modules/backups/routes/__tests__/backup-details.test.tsx b/app/client/modules/backups/routes/__tests__/backup-details.test.tsx index 16a555d8..1fdfd625 100644 --- a/app/client/modules/backups/routes/__tests__/backup-details.test.tsx +++ b/app/client/modules/backups/routes/__tests__/backup-details.test.tsx @@ -1,40 +1,51 @@ import type { ReactNode } from "react"; -import { afterEach, describe, expect, mock, test } from "bun:test"; +import { afterEach, describe, expect, test, vi } from "vitest"; import { fromAny } from "@total-typescript/shoehorn"; import { HttpResponse, http, server } from "~/test/msw/server"; import { cleanup, render, screen } from "~/test/test-utils"; -await mock.module("@tanstack/react-router", () => ({ - Link: ({ children }: { children?: ReactNode }) => {children}, - useNavigate: () => mock(() => {}), - useSearch: () => ({}), +vi.mock("@tanstack/react-router", async (importOriginal) => { + const actual = await importOriginal(); + + return { + ...actual, + Link: (({ children }: { children?: ReactNode }) => {children}) as typeof actual.Link, + useNavigate: (() => vi.fn(async () => {})) as typeof actual.useNavigate, + useSearch: (() => ({})) as typeof actual.useSearch, + }; +}); + +vi.mock("~/client/components/backup-summary-card", () => ({ + BackupSummaryCard: () => <>, })); -await mock.module("~/client/components/backup-summary-card", () => ({ - BackupSummaryCard: () => null, +vi.mock("~/client/modules/backups/components/schedule-summary", () => ({ + ScheduleSummary: () => <>, })); -await mock.module("~/client/modules/backups/components/schedule-summary", () => ({ - ScheduleSummary: () => null, +vi.mock("~/client/modules/backups/components/snapshot-timeline", () => ({ + SnapshotTimeline: () => <>, })); -await mock.module("~/client/modules/backups/components/snapshot-timeline", () => ({ - SnapshotTimeline: () => null, +vi.mock("~/client/modules/backups/components/schedule-notifications-config", () => ({ + ScheduleNotificationsConfig: () => <>, })); -await mock.module("~/client/modules/backups/components/schedule-notifications-config", () => ({ - ScheduleNotificationsConfig: () => null, +vi.mock("~/client/modules/backups/components/schedule-mirrors-config", () => ({ + ScheduleMirrorsConfig: () => <>, })); -await mock.module("~/client/modules/backups/components/schedule-mirrors-config", () => ({ - ScheduleMirrorsConfig: () => null, -})); +vi.mock("~/client/lib/datetime", async (importOriginal) => { + const actual = await importOriginal(); -await mock.module("~/client/lib/datetime", () => ({ - useTimeFormat: () => ({ - formatDateTime: () => "2026-03-26 00:00", - }), -})); + return { + ...actual, + useTimeFormat: () => ({ + ...actual.useTimeFormat(), + formatDateTime: () => "2026-03-26 00:00", + }), + }; +}); import { ScheduleDetailsPage } from "../backup-details"; diff --git a/app/server/__tests__/isolation.test.ts b/app/server/__tests__/isolation.test.ts index 020c7853..960a47e4 100644 --- a/app/server/__tests__/isolation.test.ts +++ b/app/server/__tests__/isolation.test.ts @@ -1,4 +1,4 @@ -import { test, describe, expect } from "bun:test"; +import { describe, expect, test } from "vitest"; import { createApp } from "~/server/app"; import { createTestSession } from "~/test/helpers/auth"; import { db } from "~/server/db/db"; diff --git a/app/server/cli/commands/change-email.test.ts b/app/server/cli/commands/change-email.test.ts index 93fd172c..de2e66c9 100644 --- a/app/server/cli/commands/change-email.test.ts +++ b/app/server/cli/commands/change-email.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, test } from "bun:test"; +import { beforeEach, describe, expect, test } from "vitest"; import { eq } from "drizzle-orm"; import { db } from "~/server/db/db"; import { account, sessionsTable, usersTable } from "~/server/db/schema"; diff --git a/app/server/core/__tests__/repository-mutex.test.ts b/app/server/core/__tests__/repository-mutex.test.ts index 4e968bdb..811ff6fd 100644 --- a/app/server/core/__tests__/repository-mutex.test.ts +++ b/app/server/core/__tests__/repository-mutex.test.ts @@ -1,4 +1,4 @@ -import { test, describe, expect } from "bun:test"; +import { describe, expect, test } from "vitest"; import { repoMutex } from "../repository-mutex"; describe("RepositoryMutex", () => { diff --git a/app/server/core/__tests__/scheduler.test.ts b/app/server/core/__tests__/scheduler.test.ts index f503a022..343a5f5d 100644 --- a/app/server/core/__tests__/scheduler.test.ts +++ b/app/server/core/__tests__/scheduler.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, mock, spyOn, test, vi } from "bun:test"; +import { afterEach, describe, expect, test, vi } from "vitest"; import { logger } from "@zerobyte/core/node"; import { Job, Scheduler } from "../scheduler"; @@ -9,17 +9,17 @@ const flushMicrotasks = async () => { const mockTimeZone = (timeZone: string) => { // oxlint-disable-next-line typescript/unbound-method const resolvedOptions = Intl.DateTimeFormat.prototype.resolvedOptions; - return spyOn(Intl.DateTimeFormat.prototype, "resolvedOptions").mockImplementation( - function (this: Intl.DateTimeFormat) { + return vi + .spyOn(Intl.DateTimeFormat.prototype, "resolvedOptions") + .mockImplementation(function (this: Intl.DateTimeFormat) { return { ...resolvedOptions.call(this), timeZone }; - }, - ); + }); }; describe("Scheduler", () => { afterEach(async () => { await Scheduler.clear(); - mock.restore(); + vi.restoreAllMocks(); vi.useRealTimers(); }); @@ -28,7 +28,7 @@ describe("Scheduler", () => { let runCount = 0; let releaseRun: (() => void) | undefined; - const warn = spyOn(logger, "warn"); + const warn = vi.spyOn(logger, "warn"); class TestJob extends Job { async run() { diff --git a/app/server/lib/__tests__/username.test.ts b/app/server/lib/__tests__/username.test.ts index bffacd7f..c983a333 100644 --- a/app/server/lib/__tests__/username.test.ts +++ b/app/server/lib/__tests__/username.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { describe, expect, test } from "vitest"; import { isValidUsername, normalizeUsername } from "~/lib/username"; describe("username helpers", () => { diff --git a/app/server/lib/auth/helpers/__tests__/create-default-org.test.ts b/app/server/lib/auth/helpers/__tests__/create-default-org.test.ts index 4c6a0dbc..536c65fe 100644 --- a/app/server/lib/auth/helpers/__tests__/create-default-org.test.ts +++ b/app/server/lib/auth/helpers/__tests__/create-default-org.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, test } from "bun:test"; +import { beforeEach, describe, expect, test } from "vitest"; import { eq } from "drizzle-orm"; import { db, sqlite } from "~/server/db/db"; import { account, invitation, member, organization, usersTable } from "~/server/db/schema"; diff --git a/app/server/lib/auth/middlewares/__tests__/convert-legacy-user.test.ts b/app/server/lib/auth/middlewares/__tests__/convert-legacy-user.test.ts index 158f72b7..9c4eeb54 100644 --- a/app/server/lib/auth/middlewares/__tests__/convert-legacy-user.test.ts +++ b/app/server/lib/auth/middlewares/__tests__/convert-legacy-user.test.ts @@ -1,4 +1,4 @@ -import { test, describe, beforeEach, expect } from "bun:test"; +import { beforeEach, describe, expect, test } from "vitest"; import { convertLegacyUserOnFirstLogin } from "../convert-legacy-user"; import { db } from "~/server/db/db"; import { usersTable, account, organization, member } from "~/server/db/schema"; diff --git a/app/server/modules/auth/__tests__/auth.account-and-membership.test.ts b/app/server/modules/auth/__tests__/auth.account-and-membership.test.ts index f26b367b..541f1979 100644 --- a/app/server/modules/auth/__tests__/auth.account-and-membership.test.ts +++ b/app/server/modules/auth/__tests__/auth.account-and-membership.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, test } from "bun:test"; +import { beforeEach, describe, expect, test } from "vitest"; import { db, sqlite } from "~/server/db/db"; import { account, member, organization, sessionsTable, usersTable } from "~/server/db/schema"; import { authService } from "../auth.service"; diff --git a/app/server/modules/auth/__tests__/auth.cleanup-user-organizations.test.ts b/app/server/modules/auth/__tests__/auth.cleanup-user-organizations.test.ts index 754945b6..6c8f58ad 100644 --- a/app/server/modules/auth/__tests__/auth.cleanup-user-organizations.test.ts +++ b/app/server/modules/auth/__tests__/auth.cleanup-user-organizations.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, test } from "bun:test"; +import { beforeEach, describe, expect, test } from "vitest"; import { db, sqlite } from "~/server/db/db"; import { member, organization, sessionsTable, usersTable } from "~/server/db/schema"; import { authService } from "../auth.service"; diff --git a/app/server/modules/auth/__tests__/auth.controller.security.test.ts b/app/server/modules/auth/__tests__/auth.controller.security.test.ts index a4482d01..3de8632a 100644 --- a/app/server/modules/auth/__tests__/auth.controller.security.test.ts +++ b/app/server/modules/auth/__tests__/auth.controller.security.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, test } from "bun:test"; +import { beforeEach, describe, expect, test } from "vitest"; import { createApp } from "~/server/app"; import { createTestSession, diff --git a/app/server/modules/backends/utils/__tests__/backend-utils.test.ts b/app/server/modules/backends/utils/__tests__/backend-utils.test.ts index d8da94d8..afee747c 100644 --- a/app/server/modules/backends/utils/__tests__/backend-utils.test.ts +++ b/app/server/modules/backends/utils/__tests__/backend-utils.test.ts @@ -1,15 +1,34 @@ +import { afterEach, describe, expect, test, vi } from "vitest"; + +vi.mock("node:fs/promises", async (importOriginal) => { + const actual = await importOriginal(); + + return { + ...actual, + access: vi.fn(actual.access), + }; +}); + +vi.mock("../../../../utils/mountinfo", async (importOriginal) => { + const actual = await importOriginal(); + + return { + ...actual, + getMountForPath: vi.fn(actual.getMountForPath), + }; +}); + import * as fs from "node:fs/promises"; -import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"; import * as mountinfo from "../../../../utils/mountinfo"; import { assertMounted } from "../backend-utils"; afterEach(() => { - mock.restore(); + vi.restoreAllMocks(); }); describe("assertMountedFilesystem", () => { test("throws when the path is not accessible", async () => { - spyOn(fs, "access").mockRejectedValueOnce(new Error("missing")); + vi.mocked(fs.access).mockRejectedValueOnce(new Error("missing")); await expect(assertMounted("/tmp/volume", (fstype) => fstype.startsWith("nfs"))).rejects.toThrow( "Volume is not mounted", @@ -17,8 +36,8 @@ describe("assertMountedFilesystem", () => { }); test("throws when the mount filesystem does not match", async () => { - spyOn(fs, "access").mockResolvedValueOnce(undefined); - spyOn(mountinfo, "getMountForPath").mockResolvedValueOnce({ + vi.mocked(fs.access).mockResolvedValueOnce(undefined); + vi.mocked(mountinfo.getMountForPath).mockResolvedValueOnce({ mountPoint: "/tmp/volume", fstype: "cifs", }); @@ -29,8 +48,8 @@ describe("assertMountedFilesystem", () => { }); test("accepts a matching mounted filesystem", async () => { - spyOn(fs, "access").mockResolvedValueOnce(undefined); - spyOn(mountinfo, "getMountForPath").mockResolvedValueOnce({ + vi.mocked(fs.access).mockResolvedValueOnce(undefined); + vi.mocked(mountinfo.getMountForPath).mockResolvedValueOnce({ mountPoint: "/tmp/volume", fstype: "nfs4", }); diff --git a/app/server/modules/backups/__tests__/backups.controller.test.ts b/app/server/modules/backups/__tests__/backups.controller.test.ts index 17a3755c..cde32d47 100644 --- a/app/server/modules/backups/__tests__/backups.controller.test.ts +++ b/app/server/modules/backups/__tests__/backups.controller.test.ts @@ -1,4 +1,4 @@ -import { test, describe, expect } from "bun:test"; +import { describe, expect, test } from "vitest"; import { createApp } from "~/server/app"; import { createTestSession, getAuthHeaders } from "~/test/helpers/auth"; import { createTestVolume } from "~/test/helpers/volume"; diff --git a/app/server/modules/backups/__tests__/backups.execution.test.ts b/app/server/modules/backups/__tests__/backups.execution.test.ts index f13c4703..0064d45d 100644 --- a/app/server/modules/backups/__tests__/backups.execution.test.ts +++ b/app/server/modules/backups/__tests__/backups.execution.test.ts @@ -1,5 +1,5 @@ import waitForExpect from "wait-for-expect"; -import { test, describe, mock, expect, afterEach, spyOn } from "bun:test"; +import { afterEach, describe, expect, test, vi } from "vitest"; import { backupsService } from "../backups.service"; import { backupsExecutionService } from "../backups.execution"; import { createTestVolume } from "~/test/helpers/volume"; @@ -19,12 +19,12 @@ import { repositoriesService } from "~/server/modules/repositories/repositories. import { repoMutex } from "~/server/core/repository-mutex"; const setup = () => { - const resticBackupMock = mock((_: SafeSpawnParams) => + const resticBackupMock = vi.fn((_: SafeSpawnParams) => Promise.resolve({ exitCode: 0, summary: generateBackupOutput(), error: "" }), ); - const resticForgetMock = mock(() => Promise.resolve({ success: true, data: null })); - const resticCopyMock = mock(() => Promise.resolve({ success: true, output: "" })); - const refreshStatsMock = mock(() => + const resticForgetMock = vi.fn(() => Promise.resolve({ success: true, data: null })); + const resticCopyMock = vi.fn(() => Promise.resolve({ success: true, output: "" })); + const refreshStatsMock = vi.fn(() => Promise.resolve({ total_size: 0, total_uncompressed_size: 0, @@ -35,11 +35,11 @@ const setup = () => { }), ); - spyOn(spawnModule, "safeSpawn").mockImplementation(resticBackupMock); - spyOn(restic, "forget").mockImplementation(resticForgetMock); - spyOn(restic, "copy").mockImplementation(resticCopyMock); - spyOn(repositoriesService, "refreshRepositoryStats").mockImplementation(refreshStatsMock); - spyOn(context, "getOrganizationId").mockReturnValue(TEST_ORG_ID); + vi.spyOn(spawnModule, "safeSpawn").mockImplementation(resticBackupMock); + vi.spyOn(restic, "forget").mockImplementation(resticForgetMock); + vi.spyOn(restic, "copy").mockImplementation(resticCopyMock); + vi.spyOn(repositoriesService, "refreshRepositoryStats").mockImplementation(refreshStatsMock); + vi.spyOn(context, "getOrganizationId").mockReturnValue(TEST_ORG_ID); return { resticBackupMock, @@ -50,7 +50,7 @@ const setup = () => { }; afterEach(() => { - mock.restore(); + vi.restoreAllMocks(); }); describe("backup execution - validation failures", () => { @@ -92,7 +92,7 @@ describe("backup execution - validation failures", () => { ...hydratedSchedule, volume: null, }; - spyOn(scheduleQueries, "findById").mockResolvedValueOnce(fromAny(scheduleWithoutVolume)); + vi.spyOn(scheduleQueries, "findById").mockResolvedValueOnce(fromAny(scheduleWithoutVolume)); // act const result = await backupsExecutionService.validateBackupExecution(schedule.id); @@ -122,7 +122,7 @@ describe("backup execution - validation failures", () => { ...hydratedSchedule, repository: null, }; - spyOn(scheduleQueries, "findById").mockResolvedValueOnce(fromAny(scheduleWithoutRepository)); + vi.spyOn(scheduleQueries, "findById").mockResolvedValueOnce(fromAny(scheduleWithoutRepository)); // act const result = await backupsExecutionService.validateBackupExecution(schedule.id); @@ -261,7 +261,7 @@ describe("stop backup", () => { repositoryId: repository.id, }); - spyOn(repoMutex, "acquireShared").mockImplementation((_repositoryId, _operation, signal) => { + vi.spyOn(repoMutex, "acquireShared").mockImplementation((_repositoryId, _operation, signal) => { return new Promise((_, reject) => { if (signal?.aborted) { reject(signal.reason instanceof Error ? signal.reason : new Error("Operation aborted")); diff --git a/app/server/modules/backups/__tests__/backups.patterns.test.ts b/app/server/modules/backups/__tests__/backups.patterns.test.ts index 2c7f886d..b0c471dd 100644 --- a/app/server/modules/backups/__tests__/backups.patterns.test.ts +++ b/app/server/modules/backups/__tests__/backups.patterns.test.ts @@ -1,4 +1,4 @@ -import { test, describe, expect } from "bun:test"; +import { describe, expect, test } from "vitest"; import path from "node:path"; import { fromAny } from "@total-typescript/shoehorn"; import { createBackupOptions, processPattern } from "../backup.helpers"; diff --git a/app/server/modules/backups/__tests__/backups.queries.test.ts b/app/server/modules/backups/__tests__/backups.queries.test.ts index 4744aee9..573fdd42 100644 --- a/app/server/modules/backups/__tests__/backups.queries.test.ts +++ b/app/server/modules/backups/__tests__/backups.queries.test.ts @@ -1,4 +1,4 @@ -import { test, describe, expect } from "bun:test"; +import { describe, expect, test } from "vitest"; import { scheduleQueries } from "../backups.queries"; import { createTestBackupSchedule } from "~/test/helpers/backup"; import { createTestVolume } from "~/test/helpers/volume"; diff --git a/app/server/modules/backups/__tests__/backups.service.test.ts b/app/server/modules/backups/__tests__/backups.service.test.ts index eaa16a05..d69137c1 100644 --- a/app/server/modules/backups/__tests__/backups.service.test.ts +++ b/app/server/modules/backups/__tests__/backups.service.test.ts @@ -1,5 +1,5 @@ import waitForExpect from "wait-for-expect"; -import { test, describe, mock, expect, afterEach, spyOn } from "bun:test"; +import { afterEach, describe, expect, test, vi } from "vitest"; import { eq } from "drizzle-orm"; import { backupsService } from "../backups.service"; import { createTestVolume } from "~/test/helpers/volume"; @@ -16,8 +16,8 @@ import { backupsExecutionService } from "../backups.execution"; import { repositoriesService } from "~/server/modules/repositories/repositories.service"; const setup = () => { - const resticBackupMock = mock(() => Promise.resolve({ exitCode: 0, summary: "", error: "" })); - const refreshStatsMock = mock(() => + const resticBackupMock = vi.fn(() => Promise.resolve({ exitCode: 0, summary: "", error: "" })); + const refreshStatsMock = vi.fn(() => Promise.resolve({ total_size: 0, total_uncompressed_size: 0, @@ -27,9 +27,9 @@ const setup = () => { snapshots_count: 0, }), ); - spyOn(spawnModule, "safeSpawn").mockImplementation(resticBackupMock); - spyOn(repositoriesService, "refreshRepositoryStats").mockImplementation(refreshStatsMock); - spyOn(context, "getOrganizationId").mockReturnValue(TEST_ORG_ID); + vi.spyOn(spawnModule, "safeSpawn").mockImplementation(resticBackupMock); + vi.spyOn(repositoriesService, "refreshRepositoryStats").mockImplementation(refreshStatsMock); + vi.spyOn(context, "getOrganizationId").mockReturnValue(TEST_ORG_ID); return { resticBackupMock, @@ -38,7 +38,7 @@ const setup = () => { }; afterEach(() => { - mock.restore(); + vi.restoreAllMocks(); }); describe("execute backup", () => { diff --git a/app/server/modules/events/__tests__/events.controller.test.ts b/app/server/modules/events/__tests__/events.controller.test.ts index d2af6a8e..5a98f8fa 100644 --- a/app/server/modules/events/__tests__/events.controller.test.ts +++ b/app/server/modules/events/__tests__/events.controller.test.ts @@ -1,4 +1,4 @@ -import { test, describe, expect } from "bun:test"; +import { describe, expect, test } from "vitest"; import { createApp } from "~/server/app"; import { serverEvents } from "~/server/core/events"; import { createTestSession, getAuthHeaders } from "~/test/helpers/auth"; diff --git a/app/server/modules/notifications/__tests__/notification-builders.test.ts b/app/server/modules/notifications/__tests__/notification-builders.test.ts index 532065df..64062919 100644 --- a/app/server/modules/notifications/__tests__/notification-builders.test.ts +++ b/app/server/modules/notifications/__tests__/notification-builders.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { describe, expect, test } from "vitest"; import { buildCustomShoutrrrUrl } from "../builders/custom"; import { buildDiscordShoutrrrUrl } from "../builders/discord"; import { buildEmailShoutrrrUrl } from "../builders/email"; diff --git a/app/server/modules/notifications/__tests__/notification-config-secrets.test.ts b/app/server/modules/notifications/__tests__/notification-config-secrets.test.ts index a56f2836..b7fa81dd 100644 --- a/app/server/modules/notifications/__tests__/notification-config-secrets.test.ts +++ b/app/server/modules/notifications/__tests__/notification-config-secrets.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { describe, expect, test } from "vitest"; import { mapNotificationConfigSecrets } from "../notification-config-secrets"; describe("mapNotificationConfigSecrets", () => { diff --git a/app/server/modules/notifications/__tests__/notifications.controller.test.ts b/app/server/modules/notifications/__tests__/notifications.controller.test.ts index a6e6bfc1..29615cac 100644 --- a/app/server/modules/notifications/__tests__/notifications.controller.test.ts +++ b/app/server/modules/notifications/__tests__/notifications.controller.test.ts @@ -1,4 +1,4 @@ -import { test, describe, expect } from "bun:test"; +import { describe, expect, test } from "vitest"; import { createApp } from "~/server/app"; import { createTestSession, getAuthHeaders } from "~/test/helpers/auth"; diff --git a/app/server/modules/provisioning/provisioning.test.ts b/app/server/modules/provisioning/provisioning.test.ts index 97fa9c99..d7bc25bb 100644 --- a/app/server/modules/provisioning/provisioning.test.ts +++ b/app/server/modules/provisioning/provisioning.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"; +import { afterEach, describe, expect, test, vi } from "vitest"; import * as fs from "node:fs/promises"; import * as os from "node:os"; import * as path from "node:path"; @@ -11,7 +11,7 @@ import { provisionedResourcesSchema, readProvisionedResourcesFile, syncProvision describe("provisioning", () => { afterEach(() => { - mock.restore(); + vi.restoreAllMocks(); }); test("rejects duplicate ids for the same organization", () => { @@ -347,9 +347,9 @@ describe("provisioning", () => { const { organizationId } = await createTestSession(); const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "zerobyte-provisioning-")); const provisioningPath = path.join(tempDir, "provisioning.json"); - const initMock = mock(() => Promise.resolve({ success: true, error: null })); + const initMock = vi.fn(() => Promise.resolve({ success: true, error: null })); - spyOn(restic, "init").mockImplementation(initMock); + vi.spyOn(restic, "init").mockImplementation(initMock); await fs.writeFile( provisioningPath, diff --git a/app/server/modules/repositories/__tests__/repositories.controller.test.ts b/app/server/modules/repositories/__tests__/repositories.controller.test.ts index 7b255ec3..e02530c3 100644 --- a/app/server/modules/repositories/__tests__/repositories.controller.test.ts +++ b/app/server/modules/repositories/__tests__/repositories.controller.test.ts @@ -1,4 +1,4 @@ -import { test, describe, expect, spyOn } from "bun:test"; +import { describe, expect, test, vi } from "vitest"; import crypto from "node:crypto"; import { PassThrough } from "node:stream"; import { createApp } from "~/server/app"; @@ -329,7 +329,7 @@ describe("repositories updates", () => { const { restic } = await import("~/server/core/restic"); const { ResticError } = await import("@zerobyte/core/restic"); - const deleteSnapshotSpy = spyOn(restic, "deleteSnapshot").mockImplementation(async () => { + const deleteSnapshotSpy = vi.spyOn(restic, "deleteSnapshot").mockImplementation(async () => { throw new ResticError(1, "Fatal: unexpected HTTP response (403): 403 Forbidden"); }); @@ -358,7 +358,7 @@ describe("repositories updates", () => { const stream = new PassThrough(); const expectedContent = "downloaded snapshot contents"; - const dumpSnapshotSpy = spyOn(repositoriesService, "dumpSnapshot").mockResolvedValue({ + const dumpSnapshotSpy = vi.spyOn(repositoriesService, "dumpSnapshot").mockResolvedValue({ stream, completion: Promise.resolve(), abort: () => { @@ -392,7 +392,7 @@ describe("repositories updates", () => { const { repositoriesService } = await import("~/server/modules/repositories/repositories.service"); const stream = new PassThrough(); - const dumpSnapshotSpy = spyOn(repositoriesService, "dumpSnapshot").mockResolvedValue({ + const dumpSnapshotSpy = vi.spyOn(repositoriesService, "dumpSnapshot").mockResolvedValue({ stream, completion: Promise.resolve(), abort: () => { diff --git a/app/server/modules/repositories/__tests__/repositories.service.test.ts b/app/server/modules/repositories/__tests__/repositories.service.test.ts index aa910e8d..5cdece98 100644 --- a/app/server/modules/repositories/__tests__/repositories.service.test.ts +++ b/app/server/modules/repositories/__tests__/repositories.service.test.ts @@ -3,7 +3,7 @@ import * as os from "node:os"; import nodePath from "node:path"; import { randomUUID } from "node:crypto"; import { Readable } from "node:stream"; -import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test"; +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import type { RepositoryConfig } from "@zerobyte/core/restic"; import { REPOSITORY_BASE } from "~/server/core/constants"; import { serverEvents } from "~/server/core/events"; @@ -38,15 +38,15 @@ const createTestRepository = async (organizationId: string) => { }; describe("repositoriesService.createRepository", () => { - const initMock = mock(() => Promise.resolve({ success: true, error: null })); + const initMock = vi.fn(() => Promise.resolve({ success: true, error: null })); beforeEach(() => { initMock.mockClear(); - spyOn(restic, "init").mockImplementation(initMock); + vi.spyOn(restic, "init").mockImplementation(initMock); }); afterEach(() => { - mock.restore(); + vi.restoreAllMocks(); }); test("creates a shortId-scoped repository path when using the repository base directory", async () => { @@ -113,7 +113,7 @@ describe("repositoriesService.createRepository", () => { const explicitPath = `${REPOSITORY_BASE}/custom-${randomUUID()}`; const config: RepositoryConfig = { backend: "local", path: explicitPath, isExistingRepository: true }; - spyOn(restic, "snapshots").mockImplementation(() => Promise.resolve([])); + vi.spyOn(restic, "snapshots").mockImplementation(() => Promise.resolve([])); // act const result = await withContext({ organizationId, userId: user.id }, () => @@ -140,7 +140,7 @@ describe("repositoriesService.createRepository", () => { describe("repositoriesService repository stats", () => { afterEach(() => { - mock.restore(); + vi.restoreAllMocks(); }); test("returns empty stats when repository has not been populated yet", async () => { @@ -173,7 +173,7 @@ describe("repositoriesService repository stats", () => { snapshots_count: 3, }; - const statsSpy = spyOn(restic, "stats").mockResolvedValue(expectedStats); + const statsSpy = vi.spyOn(restic, "stats").mockResolvedValue(expectedStats); const refreshed = await withContext({ organizationId, userId: user.id }, () => repositoriesService.refreshRepositoryStats(repository.shortId), @@ -197,7 +197,7 @@ describe("repositoriesService repository stats", () => { describe("repositoriesService.dumpSnapshot", () => { afterEach(() => { - mock.restore(); + vi.restoreAllMocks(); }); const createDumpResult = () => ({ @@ -232,7 +232,7 @@ describe("repositoriesService.dumpSnapshot", () => { organizationId, }); - spyOn(restic, "snapshots").mockResolvedValue([ + vi.spyOn(restic, "snapshots").mockResolvedValue([ { id: snapshotId, short_id: snapshotId, @@ -242,8 +242,8 @@ describe("repositoriesService.dumpSnapshot", () => { }, ]); - const dumpMock = mock(() => Promise.resolve(createDumpResult())); - spyOn(restic, "dump").mockImplementation(dumpMock); + const dumpMock = vi.fn(() => Promise.resolve(createDumpResult())); + vi.spyOn(restic, "dump").mockImplementation(dumpMock); return { organizationId, @@ -259,7 +259,7 @@ describe("repositoriesService.dumpSnapshot", () => { snapshotId: "snapshot-123", basePath: "/var/lib/zerobyte/volumes/vol123/_data", }); - const emitSpy = spyOn(serverEvents, "emit"); + const emitSpy = vi.spyOn(serverEvents, "emit"); await withContext({ organizationId, userId }, () => repositoriesService.dumpSnapshot(shortId, "snapshot-123", `${basePath}/documents`, "dir"), @@ -330,7 +330,7 @@ describe("repositoriesService.dumpSnapshot", () => { basePath: "/var/lib/zerobyte/volumes/vol123/_data", }); - expect( + await expect( withContext({ organizationId, userId }, () => repositoriesService.dumpSnapshot(shortId, "snapshot-no-kind", `${basePath}/documents/report.txt`), ), @@ -354,14 +354,14 @@ describe("repositoriesService.dumpSnapshot", () => { describe("repositoriesService.restoreSnapshot", () => { afterEach(() => { - mock.restore(); + vi.restoreAllMocks(); }); const setupRestoreSnapshotScenario = async () => { const { organizationId, user } = await createTestSession(); const repository = await createTestRepository(organizationId); - spyOn(restic, "snapshots").mockResolvedValue([ + vi.spyOn(restic, "snapshots").mockResolvedValue([ { id: "snapshot-restore", short_id: "snapshot-restore", @@ -371,7 +371,7 @@ describe("repositoriesService.restoreSnapshot", () => { }, ]); - const restoreMock = mock(() => + const restoreMock = vi.fn(() => Promise.resolve({ message_type: "summary" as const, seconds_elapsed: 1, @@ -383,7 +383,7 @@ describe("repositoriesService.restoreSnapshot", () => { bytes_restored: 1, }), ); - spyOn(restic, "restore").mockImplementation(restoreMock); + vi.spyOn(restic, "restore").mockImplementation(restoreMock); return { organizationId, @@ -434,7 +434,7 @@ describe("repositoriesService.restoreSnapshot", () => { describe("repositoriesService.getRetentionCategories", () => { afterEach(() => { - mock.restore(); + vi.restoreAllMocks(); }); test("recomputes retention categories after repository cache invalidation", async () => { @@ -476,7 +476,7 @@ describe("repositoriesService.getRetentionCategories", () => { ], }); - const forgetSpy = spyOn(restic, "forget"); + const forgetSpy = vi.spyOn(restic, "forget"); forgetSpy.mockResolvedValueOnce(buildForgetResponse(oldSnapshotId)); forgetSpy.mockResolvedValueOnce(buildForgetResponse(newSnapshotId)); @@ -500,7 +500,7 @@ describe("repositoriesService.getRetentionCategories", () => { describe("repositoriesService.deleteSnapshot", () => { afterEach(() => { - mock.restore(); + vi.restoreAllMocks(); }); test("refreshes repository stats in background after successful deletion", async () => { @@ -515,8 +515,8 @@ describe("repositoriesService.deleteSnapshot", () => { snapshots_count: 1, }; - spyOn(restic, "deleteSnapshot").mockResolvedValue({ success: true }); - const statsSpy = spyOn(restic, "stats").mockResolvedValue(expectedStats); + vi.spyOn(restic, "deleteSnapshot").mockResolvedValue({ success: true }); + const statsSpy = vi.spyOn(restic, "stats").mockResolvedValue(expectedStats); await withContext({ organizationId, userId: user.id }, () => repositoriesService.deleteSnapshot(repository.shortId, "snap-1"), @@ -535,11 +535,11 @@ describe("repositoriesService.deleteSnapshot", () => { const { organizationId, user } = await createTestSession(); const repository = await createTestRepository(organizationId); - spyOn(restic, "deleteSnapshot").mockImplementation(async () => { + vi.spyOn(restic, "deleteSnapshot").mockImplementation(async () => { throw new ResticError(1, "Fatal: unexpected HTTP response (403): 403 Forbidden"); }); - expect( + await expect( withContext({ organizationId, userId: user.id }, () => repositoriesService.deleteSnapshot(repository.shortId, "snap123"), ), diff --git a/app/server/modules/sso/__tests__/sso.errors.test.ts b/app/server/modules/sso/__tests__/sso.errors.test.ts index 4f3d2223..5f755a7d 100644 --- a/app/server/modules/sso/__tests__/sso.errors.test.ts +++ b/app/server/modules/sso/__tests__/sso.errors.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { describe, expect, test } from "vitest"; import { mapAuthErrorToCode } from "../sso.errors"; describe("mapAuthErrorToCode", () => { diff --git a/app/server/modules/sso/__tests__/sso.integration.test.ts b/app/server/modules/sso/__tests__/sso.integration.test.ts index a471a97c..c617d4f3 100644 --- a/app/server/modules/sso/__tests__/sso.integration.test.ts +++ b/app/server/modules/sso/__tests__/sso.integration.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, test } from "bun:test"; +import { beforeEach, describe, expect, test } from "vitest"; import type { GenericEndpointContext } from "better-auth"; import { eq } from "drizzle-orm"; import { db } from "~/server/db/db"; diff --git a/app/server/modules/sso/__tests__/sso.registration.test.ts b/app/server/modules/sso/__tests__/sso.registration.test.ts index 8dab023f..fe35f4f5 100644 --- a/app/server/modules/sso/__tests__/sso.registration.test.ts +++ b/app/server/modules/sso/__tests__/sso.registration.test.ts @@ -1,12 +1,11 @@ -import { beforeEach, describe, expect, test } from "bun:test"; +import { beforeEach, describe, expect, test } from "vitest"; import { createApp } from "~/server/app"; -import { config } from "~/server/core/config"; import { db } from "~/server/db/db"; import { account, invitation, member, organization, ssoProvider, usersTable, verification } from "~/server/db/schema"; import { createTestSession, createTestSessionWithOrgAdmin } from "~/test/helpers/auth"; const app = createApp(); -const ssoRegisterUrl = new URL("/api/auth/sso/register", config.baseUrl).toString(); +const ssoRegisterUrl = new URL("/api/auth/sso/register", "http://localhost:3000").toString(); function buildRegisterBody(organizationId: string, suffix: string) { return { diff --git a/app/server/modules/sso/__tests__/sso.security.test.ts b/app/server/modules/sso/__tests__/sso.security.test.ts index ad93a157..095a1c4b 100644 --- a/app/server/modules/sso/__tests__/sso.security.test.ts +++ b/app/server/modules/sso/__tests__/sso.security.test.ts @@ -1,11 +1,10 @@ -import { beforeEach, describe, expect, test } from "bun:test"; +import { beforeEach, describe, expect, test } from "vitest"; import { createApp } from "~/server/app"; -import { config } from "~/server/core/config"; import { account, invitation, member, organization, ssoProvider, usersTable } from "~/server/db/schema"; import { db } from "~/server/db/db"; const app = createApp(); -const ssoSignInUrl = new URL("/api/auth/sign-in/sso", config.baseUrl).toString(); +const ssoSignInUrl = new URL("/api/auth/sign-in/sso", "http://localhost:3000").toString(); function postSsoSignIn(body: Record) { return app.request(ssoSignInUrl, { diff --git a/app/server/modules/sso/__tests__/sso.service.test.ts b/app/server/modules/sso/__tests__/sso.service.test.ts index 126df6d3..652bd94c 100644 --- a/app/server/modules/sso/__tests__/sso.service.test.ts +++ b/app/server/modules/sso/__tests__/sso.service.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, test } from "bun:test"; +import { beforeEach, describe, expect, test } from "vitest"; import { db, sqlite } from "~/server/db/db"; import { account, invitation, member, organization, sessionsTable, ssoProvider, usersTable } from "~/server/db/schema"; import { ssoService } from "../sso.service"; diff --git a/app/server/modules/sso/middlewares/__tests__/require-invitation.test.ts b/app/server/modules/sso/middlewares/__tests__/require-invitation.test.ts index d2c563a6..ecf79c83 100644 --- a/app/server/modules/sso/middlewares/__tests__/require-invitation.test.ts +++ b/app/server/modules/sso/middlewares/__tests__/require-invitation.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, test } from "bun:test"; +import { beforeEach, describe, expect, test } from "vitest"; import type { GenericEndpointContext } from "better-auth"; import { db } from "~/server/db/db"; import { account, invitation, member, organization, ssoProvider, usersTable } from "~/server/db/schema"; @@ -80,7 +80,7 @@ describe("requireSsoInvitation", () => { await createSsoProvider("oidc-acme"); const ctx = createMockContext("/sign-up/email"); - expect(requireSsoInvitation("user@example.com", ctx)).rejects.toThrow("Missing providerId"); + await expect(requireSsoInvitation("user@example.com", ctx)).rejects.toThrow("Missing providerId"); }); test("detects whether current request is an SSO callback", async () => { @@ -97,7 +97,7 @@ describe("requireSsoInvitation", () => { await createSsoProvider("oidc-acme"); const ctx = createMockSsoCallbackContext("oidc-acme"); - expect(requireSsoInvitation("user@example.com", ctx)).rejects.toThrow("must be invited"); + await expect(requireSsoInvitation("user@example.com", ctx)).rejects.toThrow("must be invited"); }); test("blocks SSO callback when invitation is expired", async () => { @@ -115,7 +115,7 @@ describe("requireSsoInvitation", () => { }); const ctx = createMockSsoCallbackContext("oidc-acme"); - expect(requireSsoInvitation("user@example.com", ctx)).rejects.toThrow("must be invited"); + await expect(requireSsoInvitation("user@example.com", ctx)).rejects.toThrow("must be invited"); }); test("allows SSO callback when a matching pending invitation exists", async () => { @@ -133,11 +133,11 @@ describe("requireSsoInvitation", () => { }); const ctx = createMockSsoCallbackContext("oidc-acme"); - expect(requireSsoInvitation(" USER@EXAMPLE.COM ", ctx)).resolves.toBeUndefined(); + await expect(requireSsoInvitation(" USER@EXAMPLE.COM ", ctx)).resolves.toBeUndefined(); }); test("throws when provider is not registered", async () => { const ctx = createMockSsoCallbackContext("missing-provider"); - expect(requireSsoInvitation("user@example.com", ctx)).rejects.toThrow("SSO provider not found"); + await expect(requireSsoInvitation("user@example.com", ctx)).rejects.toThrow("SSO provider not found"); }); }); diff --git a/app/server/modules/sso/middlewares/__tests__/trust-provider-for-linking.test.ts b/app/server/modules/sso/middlewares/__tests__/trust-provider-for-linking.test.ts index ae9e7d34..7912fc16 100644 --- a/app/server/modules/sso/middlewares/__tests__/trust-provider-for-linking.test.ts +++ b/app/server/modules/sso/middlewares/__tests__/trust-provider-for-linking.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, test } from "bun:test"; +import { beforeEach, describe, expect, test } from "vitest"; import { eq } from "drizzle-orm"; import { db } from "~/server/db/db"; import { account, member, organization, ssoProvider, usersTable } from "~/server/db/schema"; diff --git a/app/server/modules/sso/middlewares/__tests__/validate-callback-urls.test.ts b/app/server/modules/sso/middlewares/__tests__/validate-callback-urls.test.ts index 15b1dba4..b8c12795 100644 --- a/app/server/modules/sso/middlewares/__tests__/validate-callback-urls.test.ts +++ b/app/server/modules/sso/middlewares/__tests__/validate-callback-urls.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { describe, expect, test } from "vitest"; import type { AuthMiddlewareContext } from "~/server/lib/auth"; import { validateSsoCallbackUrls } from "../validate-callback-urls"; diff --git a/app/server/modules/sso/middlewares/__tests__/validate-provider-id.test.ts b/app/server/modules/sso/middlewares/__tests__/validate-provider-id.test.ts index 2ecd55b1..1977fa51 100644 --- a/app/server/modules/sso/middlewares/__tests__/validate-provider-id.test.ts +++ b/app/server/modules/sso/middlewares/__tests__/validate-provider-id.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { describe, expect, test } from "vitest"; import type { AuthMiddlewareContext } from "~/server/lib/auth"; import { validateSsoProviderId } from "../validate-provider-id"; diff --git a/app/server/modules/system/__tests__/system.controller.test.ts b/app/server/modules/system/__tests__/system.controller.test.ts index a1ac2e26..ec44a662 100644 --- a/app/server/modules/system/__tests__/system.controller.test.ts +++ b/app/server/modules/system/__tests__/system.controller.test.ts @@ -1,4 +1,4 @@ -import { test, describe, expect } from "bun:test"; +import { describe, expect, test } from "vitest"; import { createApp } from "~/server/app"; import { createTestSession, createTestSessionWithGlobalAdmin, getAuthHeaders } from "~/test/helpers/auth"; diff --git a/app/server/modules/volumes/__tests__/volumes.controller.test.ts b/app/server/modules/volumes/__tests__/volumes.controller.test.ts index 822cfd7e..9c85efe5 100644 --- a/app/server/modules/volumes/__tests__/volumes.controller.test.ts +++ b/app/server/modules/volumes/__tests__/volumes.controller.test.ts @@ -1,4 +1,4 @@ -import { test, describe, expect } from "bun:test"; +import { describe, expect, test } from "vitest"; import { db } from "~/server/db/db"; import { volumesTable } from "~/server/db/schema"; import { createApp } from "~/server/app"; diff --git a/app/server/modules/volumes/__tests__/volumes.service.test.ts b/app/server/modules/volumes/__tests__/volumes.service.test.ts index ac0b4966..0fa2f6dc 100644 --- a/app/server/modules/volumes/__tests__/volumes.service.test.ts +++ b/app/server/modules/volumes/__tests__/volumes.service.test.ts @@ -1,4 +1,4 @@ -import { test, describe, expect } from "bun:test"; +import { describe, expect, test } from "vitest"; import { volumeService } from "../volume.service"; import { db } from "~/server/db/db"; import { volumesTable } from "~/server/db/schema"; diff --git a/app/server/utils/__tests__/spawn.test.ts b/app/server/utils/__tests__/spawn.test.ts index 8066a442..4c9269cf 100644 --- a/app/server/utils/__tests__/spawn.test.ts +++ b/app/server/utils/__tests__/spawn.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { describe, expect, test } from "vitest"; import { safeExec, safeSpawn } from "@zerobyte/core/node"; describe("safeExec", () => { diff --git a/app/server/utils/common-ancestor.test.ts b/app/server/utils/common-ancestor.test.ts index 5790e5f6..468fb28a 100644 --- a/app/server/utils/common-ancestor.test.ts +++ b/app/server/utils/common-ancestor.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { describe, expect, test } from "vitest"; import { findCommonAncestor } from "@zerobyte/core/utils"; describe("findCommonAncestor", () => { diff --git a/app/test/setup-client.ts b/app/test/setup-client.ts index c32d4e4b..23f17ca9 100644 --- a/app/test/setup-client.ts +++ b/app/test/setup-client.ts @@ -1,10 +1,9 @@ -import "./setup.ts"; -import { GlobalRegistrator } from "@happy-dom/global-registrator"; -import { afterAll, afterEach, beforeAll, mock } from "bun:test"; +import "./setup-shared"; +import { afterAll, afterEach, beforeAll, vi } from "vitest"; import { client } from "~/client/api-client/client.gen"; import { server } from "~/test/msw/server"; -void mock.module("~/client/hooks/use-root-loader-data", () => ({ +vi.mock(import("~/client/hooks/use-root-loader-data"), () => ({ useRootLoaderData: () => ({ theme: "dark", locale: "en-US", @@ -15,8 +14,6 @@ void mock.module("~/client/hooks/use-root-loader-data", () => ({ }), })); -GlobalRegistrator.register({ url: "http://localhost:3000" }); - client.setConfig({ baseUrl: "http://localhost:3000", credentials: "include", diff --git a/app/test/setup-shared.ts b/app/test/setup-shared.ts new file mode 100644 index 00000000..d9ec50de --- /dev/null +++ b/app/test/setup-shared.ts @@ -0,0 +1,32 @@ +import { vi } from "vitest"; + +process.env.BASE_URL = "http://localhost:3000"; +process.env.TRUSTED_ORIGINS = "http://localhost:3000"; + +vi.mock(import("@zerobyte/core/node"), async () => { + const utils = await vi.importActual("@zerobyte/core/node"); + + return { + ...utils, + logger: { + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + }, + }; +}); + +vi.mock(import("~/server/utils/crypto"), async () => { + const cryptoModule = await vi.importActual("~/server/utils/crypto"); + + return { + cryptoUtils: { + ...cryptoModule.cryptoUtils, + deriveSecret: async () => "test-secret", + sealSecret: async (v: string) => v, + resolveSecret: async (v: string) => v, + generateResticPassword: () => "test-restic-password", + }, + }; +}); diff --git a/app/test/setup.ts b/app/test/setup.ts index 0ae49a38..1b416c23 100644 --- a/app/test/setup.ts +++ b/app/test/setup.ts @@ -1,32 +1,10 @@ -import { beforeAll, mock } from "bun:test"; +import "./setup-shared"; import { migrate } from "drizzle-orm/bun-sqlite/migrator"; import path from "node:path"; import { cwd } from "node:process"; +import { beforeAll } from "vitest"; import { db } from "~/server/db/db"; -import * as utils from "@zerobyte/core/node"; - -void mock.module("@zerobyte/core/node", () => { - return { - ...utils, - logger: { - debug: () => {}, - info: () => {}, - warn: () => {}, - error: () => {}, - }, - }; -}); - -void mock.module("~/server/utils/crypto", () => ({ - cryptoUtils: { - deriveSecret: async () => "test-secret", - sealSecret: async (v: string) => v, - resolveSecret: async (v: string) => v, - generateResticPassword: () => "test-restic-password", - }, -})); - beforeAll(async () => { const migrationsFolder = path.join(cwd(), "app", "drizzle"); migrate(db, { migrationsFolder }); diff --git a/app/utils/__tests__/path.test.ts b/app/utils/__tests__/path.test.ts index d90c767e..7a8a2778 100644 --- a/app/utils/__tests__/path.test.ts +++ b/app/utils/__tests__/path.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { describe, expect, test } from "vitest"; import { isPathWithin, normalizeAbsolutePath } from "@zerobyte/core/utils"; describe("normalizeAbsolutePath", () => { diff --git a/bun.lock b/bun.lock index 4cc7eb9e..aa3ebedf 100644 --- a/bun.lock +++ b/bun.lock @@ -110,6 +110,7 @@ "typescript": "^5.9.3", "vite": "^8.0.1", "vite-bundle-analyzer": "^1.2.3", + "vitest": "^4.1.1", "wait-for-expect": "^4.0.0", }, }, @@ -955,6 +956,8 @@ "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], + "@types/content-disposition": ["@types/content-disposition@0.5.9", "", {}, "sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ=="], "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], @@ -977,6 +980,8 @@ "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], @@ -1021,6 +1026,20 @@ "@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="], + "@vitest/expect": ["@vitest/expect@4.1.1", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.1", "@vitest/utils": "4.1.1", "chai": "^6.2.2", "tinyrainbow": "^3.0.3" } }, "sha512-xAV0fqBTk44Rn6SjJReEQkHP3RrqbJo6JQ4zZ7/uVOiJZRarBtblzrOfFIZeYUrukp2YD6snZG6IBqhOoHTm+A=="], + + "@vitest/mocker": ["@vitest/mocker@4.1.1", "", { "dependencies": { "@vitest/spy": "4.1.1", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-h3BOylsfsCLPeceuCPAAJ+BvNwSENgJa4hXoXu4im0bs9Lyp4URc4JYK4pWLZ4pG/UQn7AT92K6IByi6rE6g3A=="], + + "@vitest/pretty-format": ["@vitest/pretty-format@4.1.1", "", { "dependencies": { "tinyrainbow": "^3.0.3" } }, "sha512-GM+TEQN5WhOygr1lp7skeVjdLPqqWMHsfzXrcHAqZJi/lIVh63H0kaRCY8MDhNWikx19zBUK8ceaLB7X5AH9NQ=="], + + "@vitest/runner": ["@vitest/runner@4.1.1", "", { "dependencies": { "@vitest/utils": "4.1.1", "pathe": "^2.0.3" } }, "sha512-f7+FPy75vN91QGWsITueq0gedwUZy1fLtHOCMeQpjs8jTekAHeKP80zfDEnhrleviLHzVSDXIWuCIOFn3D3f8A=="], + + "@vitest/snapshot": ["@vitest/snapshot@4.1.1", "", { "dependencies": { "@vitest/pretty-format": "4.1.1", "@vitest/utils": "4.1.1", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-kMVSgcegWV2FibXEx9p9WIKgje58lcTbXgnJixfcg15iK8nzCXhmalL0ZLtTWLW9PH1+1NEDShiFFedB3tEgWg=="], + + "@vitest/spy": ["@vitest/spy@4.1.1", "", {}, "sha512-6Ti/KT5OVaiupdIZEuZN7l3CZcR0cxnxt70Z0//3CtwgObwA6jZhmVBA3yrXSVN3gmwjgd7oDNLlsXz526gpRA=="], + + "@vitest/utils": ["@vitest/utils@4.1.1", "", { "dependencies": { "@vitest/pretty-format": "4.1.1", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.0.3" } }, "sha512-cNxAlaB3sHoCdL6pj6yyUXv9Gry1NHNg0kFTXdvSIZXLHsqKH7chiWOkwJ5s5+d/oMwcoG9T0bKU38JZWKusrQ=="], + "@xmldom/is-dom-node": ["@xmldom/is-dom-node@1.0.1", "", {}, "sha512-CJDxIgE5I0FH+ttq/Fxy6nRpxP70+e2O048EPe85J2use3XKdatVM7dDVvFNjQudd9B49NPoZ+8PG49zj4Er8Q=="], "@xmldom/xmldom": ["@xmldom/xmldom@0.8.11", "", {}, "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw=="], @@ -1055,6 +1074,8 @@ "asn1": ["asn1@0.2.6", "", { "dependencies": { "safer-buffer": "~2.1.0" } }, "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ=="], + "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="], "aws-ssl-profiles": ["aws-ssl-profiles@1.1.2", "", {}, "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g=="], @@ -1101,6 +1122,8 @@ "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="], + "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], @@ -1263,6 +1286,8 @@ "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], + "es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="], + "es-toolkit": ["es-toolkit@1.45.1", "", {}, "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw=="], "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], @@ -1277,12 +1302,16 @@ "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="], "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], + "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], @@ -1661,6 +1690,8 @@ "nypm": ["nypm@0.6.5", "", { "dependencies": { "citty": "^0.2.0", "pathe": "^2.0.3", "tinyexec": "^1.0.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ=="], + "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], + "ofetch": ["ofetch@2.0.0-alpha.3", "", {}, "sha512-zpYTCs2byOuft65vI3z43Dd6iSdFbOZZLb9/d21aCpx2rGastVU9dOCv0lu4ykc1Ur1anAYjDi3SUvR0vq50JA=="], "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], @@ -1825,6 +1856,8 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="], + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "slugify": ["slugify@1.6.8", "", {}, "sha512-HVk9X1E0gz3mSpoi60h/saazLKXKaZThMLU3u/aNwoYn8/xQyX2MGxL0ui2eaokkD7tF+Zo+cKTHUbe1mmmGzA=="], @@ -1845,9 +1878,11 @@ "srvx": ["srvx@0.10.1", "", { "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-A//xtfak4eESMWWydSRFUVvCTQbSwivnGCEf8YGPe2eHU0+Z6znfUTCPF0a7oV3sObSOcrXHlL6Bs9vVctfXdg=="], + "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="], + "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], - "std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + "std-env": ["std-env@4.0.0", "", {}, "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ=="], "strict-event-emitter": ["strict-event-emitter@0.5.1", "", {}, "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ=="], @@ -1883,12 +1918,16 @@ "tiny-warning": ["tiny-warning@1.0.3", "", {}, "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA=="], + "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], + "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], "tinypool": ["tinypool@2.1.0", "", {}, "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw=="], + "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="], + "tldts": ["tldts@6.1.86", "", { "dependencies": { "tldts-core": "^6.1.86" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ=="], "tldts-core": ["tldts-core@6.1.86", "", {}, "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA=="], @@ -1967,6 +2006,8 @@ "vitefu": ["vitefu@1.1.2", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-zpKATdUbzbsycPFBN71nS2uzBUQiVnFoOrr2rvqv34S1lcAgMKKkjWleLGeiJlZ8lwCXvtWaRn7R3ZC16SYRuw=="], + "vitest": ["vitest@4.1.1", "", { "dependencies": { "@vitest/expect": "4.1.1", "@vitest/mocker": "4.1.1", "@vitest/pretty-format": "4.1.1", "@vitest/runner": "4.1.1", "@vitest/snapshot": "4.1.1", "@vitest/spy": "4.1.1", "@vitest/utils": "4.1.1", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.1", "@vitest/browser-preview": "4.1.1", "@vitest/browser-webdriverio": "4.1.1", "@vitest/ui": "4.1.1", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-yF+o4POL41rpAzj5KVILUxm1GCjKnELvaqmU9TLLUbMfDzuN0UpUR9uaDs+mCtjPe+uYPksXDRLQGGPvj1cTmA=="], + "wait-for-expect": ["wait-for-expect@4.0.0", "", {}, "sha512-mcH2HYUUHhdFGHVJkgwkBxRihZO4VSuPyh6xhYHz7LEnYkcaLbTAEEsTpYiFw4UY45XdTZYYIaquuMucw9wWMw=="], "web-haptics": ["web-haptics@0.0.6", "", { "peerDependencies": { "react": ">=18", "react-dom": ">=18", "svelte": ">=4", "vue": ">=3" }, "optionalPeers": ["react", "react-dom", "svelte", "vue"] }, "sha512-eCzcf1LDi20+Fr0x9V3OkX92k0gxEQXaHajmhXHitsnk6SxPeshv8TBtBRqxyst8HI1uf2FyFVE7QS3jo1gkrw=="], @@ -1985,6 +2026,8 @@ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], + "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], "ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="], @@ -2087,6 +2130,8 @@ "@prisma/dev/hono": ["hono@4.11.4", "", {}, "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA=="], + "@prisma/dev/std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="], + "@prisma/engines/@prisma/get-platform": ["@prisma/get-platform@7.4.2", "", { "dependencies": { "@prisma/debug": "7.4.2" } }, "sha512-UTnChXRwiauzl/8wT4hhe7Xmixja9WE28oCnGpBtRejaHhvekx5kudr3R4Y9mLSA0kqGnAMeyTiKwDVMjaEVsw=="], "@prisma/fetch-engine/@prisma/get-platform": ["@prisma/get-platform@7.4.2", "", { "dependencies": { "@prisma/debug": "7.4.2" } }, "sha512-UTnChXRwiauzl/8wT4hhe7Xmixja9WE28oCnGpBtRejaHhvekx5kudr3R4Y9mLSA0kqGnAMeyTiKwDVMjaEVsw=="], diff --git a/package.json b/package.json index f3a913f2..6fef82b0 100644 --- a/package.json +++ b/package.json @@ -22,9 +22,9 @@ "gen:api-client": "dotenv -e .env.local -- openapi-ts", "gen:migrations": "dotenv -e .env.local -- drizzle-kit generate", "studio": "drizzle-kit studio", - "test:server": "dotenv -e .env.test -- bun test app/server --preload ./app/test/setup.ts", - "test:client": "dotenv -e .env.test -- bun test app/client --preload ./app/test/setup-client.ts", - "test": "bun run test:server && bun run test:client && turbo run test", + "test:server": "dotenv -e .env.test -- bunx --bun vitest run --project server", + "test:client": "dotenv -e .env.test -- bunx --bun vitest run --project client", + "test": "dotenv -e .env.test -- bunx --bun vitest run && turbo run test", "test:e2e": "NODE_ENV=test dotenv -e .env.local -- playwright test", "test:e2e:ui": "NODE_ENV=test dotenv -e .env.local -- playwright test --ui", "test:codegen": "playwright codegen localhost:4096" @@ -135,6 +135,7 @@ "typescript": "^5.9.3", "vite": "^8.0.1", "vite-bundle-analyzer": "^1.2.3", + "vitest": "^4.1.1", "wait-for-expect": "^4.0.0" }, "overrides": { diff --git a/packages/core/package.json b/packages/core/package.json index 61319536..19ef4440 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -26,7 +26,7 @@ }, "scripts": { "tsc": "tsc --noEmit", - "test": "bun test --preload ./test/setup.ts" + "test": "bunx vitest run --config ./vitest.config.ts" }, "devDependencies": { "@types/bun": "latest" diff --git a/packages/core/src/restic/commands/__tests__/backup.test.ts b/packages/core/src/restic/commands/__tests__/backup.test.ts index 9203b0a5..554cee32 100644 --- a/packages/core/src/restic/commands/__tests__/backup.test.ts +++ b/packages/core/src/restic/commands/__tests__/backup.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"; +import { afterEach, describe, expect, test, vi } from "vitest"; import * as cleanupModule from "../../helpers/cleanup-temporary-keys"; import * as spawnModule from "../../../utils/spawn"; import { ResticError } from "../../error"; @@ -61,8 +61,8 @@ type SetupOptions = { const setup = ({ spawnResult = {}, onSpawnCall }: SetupOptions = {}) => { let capturedArgs: string[] = []; - const cleanupSpy = spyOn(cleanupModule, "cleanupTemporaryKeys").mockImplementation(() => Promise.resolve()); - spyOn(spawnModule, "safeSpawn").mockImplementation((params) => { + const cleanupSpy = vi.spyOn(cleanupModule, "cleanupTemporaryKeys").mockImplementation(() => Promise.resolve()); + vi.spyOn(spawnModule, "safeSpawn").mockImplementation((params) => { capturedArgs = params.args; return Promise.resolve(onSpawnCall?.(params)).then(() => ({ exitCode: 0, @@ -89,7 +89,7 @@ const setup = ({ spawnResult = {}, onSpawnCall }: SetupOptions = {}) => { }; afterEach(() => { - mock.restore(); + vi.restoreAllMocks(); }); describe("backup command", () => { diff --git a/packages/core/src/restic/commands/__tests__/copy.test.ts b/packages/core/src/restic/commands/__tests__/copy.test.ts index dbc15fdb..e4ec40f5 100644 --- a/packages/core/src/restic/commands/__tests__/copy.test.ts +++ b/packages/core/src/restic/commands/__tests__/copy.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"; +import { afterEach, describe, expect, test, vi } from "vitest"; import * as cleanupModule from "../../helpers/cleanup-temporary-keys"; import * as nodeModule from "../../../node"; import { copy } from "../copy"; @@ -29,8 +29,8 @@ const destConfig = { const setup = () => { let capturedArgs: string[] = []; - spyOn(cleanupModule, "cleanupTemporaryKeys").mockImplementation(() => Promise.resolve()); - spyOn(nodeModule, "safeExec").mockImplementation(async ({ args }) => { + vi.spyOn(cleanupModule, "cleanupTemporaryKeys").mockImplementation(() => Promise.resolve()); + vi.spyOn(nodeModule, "safeExec").mockImplementation(async ({ args }) => { capturedArgs = args ?? []; return { exitCode: 0, stdout: "copied", stderr: "", timedOut: false }; }); @@ -41,7 +41,7 @@ const setup = () => { }; afterEach(() => { - mock.restore(); + vi.restoreAllMocks(); }); describe("copy command", () => { diff --git a/packages/core/src/restic/commands/__tests__/delete-snapshots.test.ts b/packages/core/src/restic/commands/__tests__/delete-snapshots.test.ts index ad136890..460dca03 100644 --- a/packages/core/src/restic/commands/__tests__/delete-snapshots.test.ts +++ b/packages/core/src/restic/commands/__tests__/delete-snapshots.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"; +import { afterEach, describe, expect, test, vi } from "vitest"; import * as cleanupModule from "../../helpers/cleanup-temporary-keys"; import * as nodeModule from "../../../node"; import { deleteSnapshots } from "../delete-snapshots"; @@ -22,8 +22,8 @@ const config = { const setup = () => { let capturedArgs: string[] = []; - spyOn(cleanupModule, "cleanupTemporaryKeys").mockImplementation(() => Promise.resolve()); - spyOn(nodeModule, "safeExec").mockImplementation(async ({ args }) => { + vi.spyOn(cleanupModule, "cleanupTemporaryKeys").mockImplementation(() => Promise.resolve()); + vi.spyOn(nodeModule, "safeExec").mockImplementation(async ({ args }) => { capturedArgs = args ?? []; return { exitCode: 0, stdout: "", stderr: "", timedOut: false }; }); @@ -34,7 +34,7 @@ const setup = () => { }; afterEach(() => { - mock.restore(); + vi.restoreAllMocks(); }); describe("deleteSnapshots command", () => { diff --git a/packages/core/src/restic/commands/__tests__/dump.test.ts b/packages/core/src/restic/commands/__tests__/dump.test.ts index 0b21ba5f..8d1f2b54 100644 --- a/packages/core/src/restic/commands/__tests__/dump.test.ts +++ b/packages/core/src/restic/commands/__tests__/dump.test.ts @@ -1,6 +1,6 @@ import { PassThrough } from "node:stream"; import { spawn } from "node:child_process"; -import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"; +import { afterEach, describe, expect, test, vi } from "vitest"; import * as cleanupModule from "../../helpers/cleanup-temporary-keys"; import * as nodeModule from "../../../node"; import { dump } from "../dump"; @@ -24,8 +24,8 @@ const config = { const setup = () => { let capturedArgs: string[] = []; - spyOn(cleanupModule, "cleanupTemporaryKeys").mockImplementation(() => Promise.resolve()); - spyOn(nodeModule, "safeSpawn").mockImplementation((params) => { + vi.spyOn(cleanupModule, "cleanupTemporaryKeys").mockImplementation(() => Promise.resolve()); + vi.spyOn(nodeModule, "safeSpawn").mockImplementation((params) => { capturedArgs = params.args; const child = { stdout: new PassThrough() } as unknown as ReturnType; params.onSpawn?.(child); @@ -38,7 +38,7 @@ const setup = () => { }; afterEach(() => { - mock.restore(); + vi.restoreAllMocks(); }); describe("dump command", () => { diff --git a/packages/core/src/restic/commands/__tests__/ls.test.ts b/packages/core/src/restic/commands/__tests__/ls.test.ts index 861ff3cb..ea68c563 100644 --- a/packages/core/src/restic/commands/__tests__/ls.test.ts +++ b/packages/core/src/restic/commands/__tests__/ls.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"; +import { afterEach, describe, expect, test, vi } from "vitest"; import * as cleanupModule from "../../helpers/cleanup-temporary-keys"; import * as spawnModule from "../../../utils/spawn"; import { ls } from "../ls"; @@ -34,8 +34,8 @@ const snapshotLine = JSON.stringify({ const setup = () => { let capturedArgs: string[] = []; - spyOn(cleanupModule, "cleanupTemporaryKeys").mockImplementation(() => Promise.resolve()); - spyOn(spawnModule, "safeSpawn").mockImplementation((params: SafeSpawnParams) => { + vi.spyOn(cleanupModule, "cleanupTemporaryKeys").mockImplementation(() => Promise.resolve()); + vi.spyOn(spawnModule, "safeSpawn").mockImplementation((params: SafeSpawnParams) => { capturedArgs = params.args; params.onStdout?.(snapshotLine); return Promise.resolve({ exitCode: 0, summary: snapshotLine, error: "" }); @@ -47,7 +47,7 @@ const setup = () => { }; afterEach(() => { - mock.restore(); + vi.restoreAllMocks(); }); describe("ls command", () => { diff --git a/packages/core/src/restic/commands/__tests__/restore.test.ts b/packages/core/src/restic/commands/__tests__/restore.test.ts index 1a22c1d4..07d85712 100644 --- a/packages/core/src/restic/commands/__tests__/restore.test.ts +++ b/packages/core/src/restic/commands/__tests__/restore.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"; +import { afterEach, describe, expect, test, vi } from "vitest"; import * as spawnModule from "../../../utils/spawn"; import { restore } from "../restore"; import type { ResticDeps } from "../../types"; @@ -33,7 +33,7 @@ const config = { const setup = () => { let capturedArgs: string[] = []; - spyOn(spawnModule, "safeSpawn").mockImplementation((params: SafeSpawnParams) => { + vi.spyOn(spawnModule, "safeSpawn").mockImplementation((params: SafeSpawnParams) => { capturedArgs = params.args; return Promise.resolve({ exitCode: 0, summary: successfulRestoreSummary, error: "" }); }); @@ -64,7 +64,7 @@ const setup = () => { }; afterEach(() => { - mock.restore(); + vi.restoreAllMocks(); }); describe("restore command", () => { diff --git a/packages/core/src/restic/commands/__tests__/tag-snapshots.test.ts b/packages/core/src/restic/commands/__tests__/tag-snapshots.test.ts index 77246c1a..8bb63d6b 100644 --- a/packages/core/src/restic/commands/__tests__/tag-snapshots.test.ts +++ b/packages/core/src/restic/commands/__tests__/tag-snapshots.test.ts @@ -1,4 +1,4 @@ -import { afterEach, describe, expect, mock, spyOn, test } from "bun:test"; +import { afterEach, describe, expect, test, vi } from "vitest"; import * as cleanupModule from "../../helpers/cleanup-temporary-keys"; import * as nodeModule from "../../../node"; import { tagSnapshots } from "../tag-snapshots"; @@ -22,8 +22,8 @@ const config = { const setup = () => { let capturedArgs: string[] = []; - spyOn(cleanupModule, "cleanupTemporaryKeys").mockImplementation(() => Promise.resolve()); - spyOn(nodeModule, "safeExec").mockImplementation(async ({ args }) => { + vi.spyOn(cleanupModule, "cleanupTemporaryKeys").mockImplementation(() => Promise.resolve()); + vi.spyOn(nodeModule, "safeExec").mockImplementation(async ({ args }) => { capturedArgs = args ?? []; return { exitCode: 0, stdout: "", stderr: "", timedOut: false }; }); @@ -34,7 +34,7 @@ const setup = () => { }; afterEach(() => { - mock.restore(); + vi.restoreAllMocks(); }); describe("tagSnapshots command", () => { diff --git a/packages/core/src/restic/helpers/__tests__/build-env.test.ts b/packages/core/src/restic/helpers/__tests__/build-env.test.ts index e54afcfd..6428af73 100644 --- a/packages/core/src/restic/helpers/__tests__/build-env.test.ts +++ b/packages/core/src/restic/helpers/__tests__/build-env.test.ts @@ -1,5 +1,5 @@ import fs from "node:fs/promises"; -import { afterEach, describe, expect, test } from "bun:test"; +import { afterEach, describe, expect, test } from "vitest"; import { buildEnv } from "../build-env"; import type { ResticDeps } from "../../types"; diff --git a/packages/core/src/restic/helpers/__tests__/build-repo-url.test.ts b/packages/core/src/restic/helpers/__tests__/build-repo-url.test.ts index a5aa8c50..84193b75 100644 --- a/packages/core/src/restic/helpers/__tests__/build-repo-url.test.ts +++ b/packages/core/src/restic/helpers/__tests__/build-repo-url.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { describe, expect, test } from "vitest"; import { buildRepoUrl } from "../build-repo-url"; describe("buildRepoUrl", () => { diff --git a/packages/core/src/restic/helpers/__tests__/validate-custom-params.test.ts b/packages/core/src/restic/helpers/__tests__/validate-custom-params.test.ts index 1599f07b..410a7c36 100644 --- a/packages/core/src/restic/helpers/__tests__/validate-custom-params.test.ts +++ b/packages/core/src/restic/helpers/__tests__/validate-custom-params.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { describe, expect, test } from "vitest"; import { validateCustomResticParams } from "../validate-custom-params"; describe("validateCustomResticParams", () => { diff --git a/packages/core/src/utils/__tests__/fs.test.ts b/packages/core/src/utils/__tests__/fs.test.ts index 34ecf7b2..1aaed3d4 100644 --- a/packages/core/src/utils/__tests__/fs.test.ts +++ b/packages/core/src/utils/__tests__/fs.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; -import { afterEach, describe, expect, test } from "bun:test"; +import { afterEach, describe, expect, test } from "vitest"; import { FILE_MODES, writeFileWithMode } from "../fs"; const tempDirectories = new Set(); diff --git a/packages/core/test/setup.ts b/packages/core/test/setup.ts index 99dd0829..5e136be4 100644 --- a/packages/core/test/setup.ts +++ b/packages/core/test/setup.ts @@ -1,6 +1,6 @@ -import { mock } from "bun:test"; +import { vi } from "vitest"; -void mock.module("../src/utils/logger.ts", () => ({ +vi.mock(import("../src/utils/logger.ts"), () => ({ logger: { debug: () => {}, info: () => {}, diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts new file mode 100644 index 00000000..62b6ca1b --- /dev/null +++ b/packages/core/vitest.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + pool: "forks", + maxWorkers: 4, + server: { + deps: { + inline: ["zod"], + }, + }, + include: ["src/**/*.test.ts", "src/**/*.spec.ts"], + setupFiles: ["./test/setup.ts"], + }, +}); diff --git a/tsconfig.json b/tsconfig.json index 6956e49b..a4506dd3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "include": ["app/**/*"], "compilerOptions": { "lib": ["DOM", "DOM.Iterable", "ES2022"], - "types": ["node", "vite/client"], + "types": ["bun", "node", "vite/client"], "target": "ES2022", "module": "ES2022", "moduleResolution": "bundler", diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 00000000..f498a514 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,55 @@ +import path from "node:path"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + resolve: { + alias: { + "~": path.resolve(import.meta.dirname, "app"), + }, + }, + test: { + pool: "forks", + maxWorkers: 4, + server: { + deps: { + inline: ["zod"], + }, + }, + projects: [ + { + extends: true, + test: { + name: "server", + environment: "node", + include: ["app/**/*.test.ts", "app/**/*.test.tsx", "app/**/*.spec.ts", "app/**/*.spec.tsx"], + exclude: [ + "app/client/**/*.test.ts", + "app/client/**/*.test.tsx", + "app/client/**/*.spec.ts", + "app/client/**/*.spec.tsx", + ], + setupFiles: ["./app/test/setup.ts"], + }, + }, + { + extends: true, + test: { + name: "client", + environment: "happy-dom", + environmentOptions: { + happyDOM: { + url: "http://localhost:3000", + }, + }, + include: [ + "app/client/**/*.test.ts", + "app/client/**/*.test.tsx", + "app/client/**/*.spec.ts", + "app/client/**/*.spec.tsx", + ], + setupFiles: ["./app/test/setup-client.ts"], + }, + }, + ], + }, +});