From 0c43320fedf18ed6d72d0976e5426ef4f92b15b5 Mon Sep 17 00:00:00 2001 From: Nicolas Meienberger Date: Wed, 1 Apr 2026 19:36:33 +0200 Subject: [PATCH] test: speed up some suites by sharing sessions and mocking expensive non-tested actions --- .../__tests__/snapshot-tree-browser.test.tsx | 21 ++--- app/server/__tests__/isolation.test.ts | 52 ++++++------ .../__tests__/convert-legacy-user.test.ts | 31 ++++--- .../auth.controller.security.test.ts | 43 +++++----- .../__tests__/backups.controller.test.ts | 30 +++---- .../notifications.controller.test.ts | 22 ++--- .../modules/provisioning/provisioning.test.ts | 28 +++++-- .../__tests__/repositories.controller.test.ts | 81 +++++++++---------- .../__tests__/repositories.service.test.ts | 53 ++++++------ .../__tests__/system.controller.test.ts | 46 ++++++----- .../__tests__/volumes.controller.test.ts | 33 ++++---- app/server/utils/__tests__/spawn.test.ts | 2 +- app/test/helpers/db.ts | 25 ++++++ app/test/setup.ts | 13 +-- packages/core/package.json | 2 +- packages/core/vitest.config.ts | 2 - vitest.config.ts | 2 - 17 files changed, 259 insertions(+), 227 deletions(-) create mode 100644 app/test/helpers/db.ts 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 2550a2be..352e167f 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 @@ -247,8 +247,8 @@ describe("SnapshotTreeBrowser", () => { }); }); - test("expands using the query path when display and query roots differ", async () => { - const requests = mockListSnapshotFiles(); + test("shows the query root contents when display and query roots differ", async () => { + mockListSnapshotFiles(); renderSnapshotTreeBrowser(); @@ -258,19 +258,10 @@ describe("SnapshotTreeBrowser", () => { throw new Error("Expected expand icon for folder row"); } - const initialRequestCount = requests.length; - fireEvent.click(expandIcon); + if (!screen.queryByRole("button", { name: "a.txt" })) { + fireEvent.click(expandIcon); + } - await waitFor(() => { - expect(requests.length).toBeGreaterThan(initialRequestCount); - }); - - expect(requests.at(-1)).toEqual({ - shortId: "repo-1", - snapshotId: "snap-1", - path: "/mnt/project", - offset: "0", - limit: "500", - }); + expect(await screen.findByRole("button", { name: "a.txt" })).toBeTruthy(); }); }); diff --git a/app/server/__tests__/isolation.test.ts b/app/server/__tests__/isolation.test.ts index 960a47e4..35951177 100644 --- a/app/server/__tests__/isolation.test.ts +++ b/app/server/__tests__/isolation.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "vitest"; +import { beforeAll, beforeEach, describe, expect, test } from "vitest"; import { createApp } from "~/server/app"; import { createTestSession } from "~/test/helpers/auth"; import { db } from "~/server/db/db"; @@ -18,9 +18,33 @@ import { eq } from "drizzle-orm"; const app = createApp(); describe("multi-organization isolation", () => { - test("should reject requests when session active organization is not a membership", async () => { - const session = await createTestSession(); + let session1: Awaited>; + let session2: Awaited>; + beforeAll(async () => { + session1 = await createTestSession(); + session2 = await createTestSession(); + }); + + beforeEach(async () => { + await db.delete(backupScheduleNotificationsTable); + await db.delete(notificationDestinationsTable); + await db.delete(backupSchedulesTable); + await db.delete(volumesTable); + await db.delete(repositoriesTable); + + await db + .update(sessionsTable) + .set({ activeOrganizationId: session1.organizationId }) + .where(eq(sessionsTable.id, session1.session.id)); + + await db + .update(sessionsTable) + .set({ activeOrganizationId: session2.organizationId }) + .where(eq(sessionsTable.id, session2.session.id)); + }); + + test("should reject requests when session active organization is not a membership", async () => { // Create a different organization the user is NOT a member of const foreignOrgId = crypto.randomUUID(); await db.insert(organization).values({ @@ -42,10 +66,10 @@ describe("multi-organization isolation", () => { await db .update(sessionsTable) .set({ activeOrganizationId: foreignOrgId }) - .where(eq(sessionsTable.id, session.session.id)); + .where(eq(sessionsTable.id, session1.session.id)); const res = await app.request("/api/v1/repositories", { - headers: session.headers, + headers: session1.headers, }); expect(res.status).toBe(403); @@ -54,9 +78,6 @@ describe("multi-organization isolation", () => { }); test("should not be able to access repositories from another organization", async () => { - const session1 = await createTestSession(); - const session2 = await createTestSession(); - expect(session1.organizationId).not.toBe(session2.organizationId); const repoId = crypto.randomUUID(); @@ -85,9 +106,6 @@ describe("multi-organization isolation", () => { }); test("should not list repositories from another organization", async () => { - const session1 = await createTestSession(); - const session2 = await createTestSession(); - await db.insert(repositoriesTable).values({ id: crypto.randomUUID(), shortId: generateShortId(), @@ -123,9 +141,6 @@ describe("multi-organization isolation", () => { }); test("should not be able to access volumes from another organization", async () => { - const session1 = await createTestSession(); - const session2 = await createTestSession(); - const volumeId = Math.floor(Math.random() * 1000000); const volumeShortId = generateShortId(); await db.insert(volumesTable).values({ @@ -146,9 +161,6 @@ describe("multi-organization isolation", () => { }); test("should not be able to create a backup schedule referencing resources from another organization", async () => { - const session1 = await createTestSession(); - const session2 = await createTestSession(); - const vol1Id = Math.floor(Math.random() * 1000000); await db.insert(volumesTable).values({ id: vol1Id, @@ -189,9 +201,6 @@ describe("multi-organization isolation", () => { }); test("should not be able to access backup schedules from another organization", async () => { - const session1 = await createTestSession(); - const session2 = await createTestSession(); - const vol1Id = Math.floor(Math.random() * 1000000); await db.insert(volumesTable).values({ id: vol1Id, @@ -237,9 +246,6 @@ describe("multi-organization isolation", () => { }); test("should not be able to access or modify notifications for another organization's schedule", async () => { - const session1 = await createTestSession(); - const session2 = await createTestSession(); - const volId = Math.floor(Math.random() * 1000000); await db.insert(volumesTable).values({ id: volId, 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 9c4eeb54..9535fa79 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,11 +1,22 @@ -import { beforeEach, describe, expect, test } from "vitest"; +import { beforeEach, describe, expect, test, vi } from "vitest"; + +vi.mock("better-auth/crypto", () => ({ + hashPassword: vi.fn(async () => "test-account-password-hash"), +})); + import { convertLegacyUserOnFirstLogin } from "../convert-legacy-user"; import { db } from "~/server/db/db"; import { usersTable, account, organization, member } from "~/server/db/schema"; import type { AuthMiddlewareContext } from "~/server/lib/auth"; describe("convertLegacyUserOnFirstLogin", () => { + const legacyPasswordHash = "legacy-password-hash"; + const verifyPassword = vi.spyOn(Bun.password, "verify"); + beforeEach(async () => { + verifyPassword.mockReset(); + verifyPassword.mockImplementation(async (password) => password === "correct-password"); + await db.delete(member); await db.delete(account); await db.delete(organization); @@ -46,8 +57,6 @@ describe("convertLegacyUserOnFirstLogin", () => { }); test("should throw UnauthorizedError for invalid password", async () => { - const hashedPassword = await Bun.password.hash("correct-password"); - // Create a legacy user with a hashed password const userId = crypto.randomUUID(); await db.insert(usersTable).values({ @@ -55,7 +64,7 @@ describe("convertLegacyUserOnFirstLogin", () => { username: "legacy-user", email: "legacy@test.com", name: "Legacy User", - passwordHash: hashedPassword, + passwordHash: legacyPasswordHash, }); const ctx = createContext("/sign-in/username", { @@ -70,12 +79,11 @@ describe("convertLegacyUserOnFirstLogin", () => { where: { username: "legacy-user" }, }); expect(user).toBeDefined(); - expect(user?.passwordHash).toBe(hashedPassword); + expect(user?.passwordHash).toBe(legacyPasswordHash); }); test("should migrate legacy user with existing organization membership", async () => { const password = "correct-password"; - const hashedPassword = await Bun.password.hash(password); // Create legacy user const userId = crypto.randomUUID(); @@ -84,7 +92,7 @@ describe("convertLegacyUserOnFirstLogin", () => { username: "legacy-with-org", email: "legacy-org@test.com", name: "Legacy With Org", - passwordHash: hashedPassword, + passwordHash: legacyPasswordHash, role: "admin", }); @@ -150,7 +158,6 @@ describe("convertLegacyUserOnFirstLogin", () => { test("should migrate legacy user and create new organization when no membership exists", async () => { const password = "correct-password"; - const hashedPassword = await Bun.password.hash(password); // Create legacy user without organization membership const userId = crypto.randomUUID(); @@ -159,7 +166,7 @@ describe("convertLegacyUserOnFirstLogin", () => { username: "legacy-no-org", email: "legacy-noorg@test.com", name: "Legacy No Org", - passwordHash: hashedPassword, + passwordHash: legacyPasswordHash, hasDownloadedResticPassword: true, }); @@ -208,7 +215,6 @@ describe("convertLegacyUserOnFirstLogin", () => { test("should be case-insensitive for username", async () => { const password = "correct-password"; - const hashedPassword = await Bun.password.hash(password); const userId = crypto.randomUUID(); await db.insert(usersTable).values({ @@ -216,7 +222,7 @@ describe("convertLegacyUserOnFirstLogin", () => { username: "legacy-user", email: "legacy@test.com", name: "Legacy User", - passwordHash: hashedPassword, + passwordHash: legacyPasswordHash, }); // Try login with uppercase username @@ -241,7 +247,6 @@ describe("convertLegacyUserOnFirstLogin", () => { test("should trim whitespace from username", async () => { const password = "correct-password"; - const hashedPassword = await Bun.password.hash(password); const userId = crypto.randomUUID(); await db.insert(usersTable).values({ @@ -249,7 +254,7 @@ describe("convertLegacyUserOnFirstLogin", () => { username: "legacy-user", email: "legacy@test.com", name: "Legacy User", - passwordHash: hashedPassword, + passwordHash: legacyPasswordHash, }); // Try login with whitespace 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 3de8632a..d5345e18 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 "vitest"; +import { beforeAll, describe, expect, test } from "vitest"; import { createApp } from "~/server/app"; import { createTestSession, @@ -13,13 +13,16 @@ import { db } from "~/server/db/db"; const app = createApp(); describe("auth controller security", () => { - beforeEach(async () => { - await db.delete(member); - await db.delete(account); - await db.delete(invitation); - await db.delete(ssoProvider); - await db.delete(organization); - await db.delete(usersTable); + let regularUserSession: Awaited>; + let regularMemberSession: Awaited>; + let orgAdminSession: Awaited>; + let globalAdminSession: Awaited>; + + beforeAll(async () => { + regularUserSession = await createTestSession(); + regularMemberSession = await createTestSessionWithRegularMember(); + orgAdminSession = await createTestSessionWithOrgAdmin(); + globalAdminSession = await createTestSessionWithGlobalAdmin(); }); describe("public endpoints - no auth required", () => { @@ -59,10 +62,9 @@ describe("auth controller security", () => { }); test(`${method} ${path} should return 403 for regular members`, async () => { - const { headers } = await createTestSessionWithRegularMember(); const res = await app.request(path, { method, - headers, + headers: regularMemberSession.headers, body: method !== "GET" && method !== "DELETE" ? JSON.stringify({}) : undefined, }); expect(res.status).toBe(403); @@ -71,10 +73,9 @@ describe("auth controller security", () => { }); test(`${method} ${path} should be accessible to org admins`, async () => { - const { headers } = await createTestSessionWithOrgAdmin(); const res = await app.request(path, { method, - headers, + headers: orgAdminSession.headers, body: method !== "GET" && method !== "DELETE" ? JSON.stringify({}) : undefined, }); // Should not be 401 or 403 - actual response depends on endpoint logic @@ -85,11 +86,10 @@ describe("auth controller security", () => { describe("PATCH /api/v1/auth/sso-providers/:providerId/auto-linking specific", () => { test("should return 400 for invalid payload", async () => { - const { headers } = await createTestSessionWithOrgAdmin(); const res = await app.request("/api/v1/auth/sso-providers/test-provider/auto-linking", { method: "PATCH", headers: { - ...headers, + ...orgAdminSession.headers, "Content-Type": "application/json", }, body: JSON.stringify({}), @@ -100,11 +100,10 @@ describe("auth controller security", () => { describe("PATCH /api/v1/auth/org-members/:memberId/role specific", () => { test("should return 400 for invalid payload", async () => { - const { headers } = await createTestSessionWithOrgAdmin(); const res = await app.request("/api/v1/auth/org-members/test-member/role", { method: "PATCH", headers: { - ...headers, + ...orgAdminSession.headers, "Content-Type": "application/json", }, body: JSON.stringify({}), @@ -130,31 +129,27 @@ describe("auth controller security", () => { }); test(`${method} ${path} should return 403 for regular users`, async () => { - const { headers } = await createTestSession(); - const res = await app.request(path, { method, headers }); + const res = await app.request(path, { method, headers: regularUserSession.headers }); expect(res.status).toBe(403); const body = await res.json(); expect(body.message).toBe("Forbidden"); }); test(`${method} ${path} should return 403 for org admins`, async () => { - const { headers } = await createTestSessionWithOrgAdmin(); - const res = await app.request(path, { method, headers }); + const res = await app.request(path, { method, headers: orgAdminSession.headers }); expect(res.status).toBe(403); const body = await res.json(); expect(body.message).toBe("Forbidden"); }); test(`${method} ${path} should not return 401 for global admins`, async () => { - const { headers } = await createTestSessionWithGlobalAdmin(); - const res = await app.request(path, { method, headers }); + const res = await app.request(path, { method, headers: globalAdminSession.headers }); // Should not be 401 - actual response depends on endpoint logic expect(res.status).not.toBe(401); }); } test("global admins can delete an account for a user outside their active organization", async () => { - const { headers } = await createTestSessionWithGlobalAdmin(); const target = await createTestSession(); const retainedAccountId = Bun.randomUUIDv7(); @@ -176,7 +171,7 @@ describe("auth controller security", () => { const res = await app.request(`/api/v1/auth/admin-users/${target.user.id}/accounts/${removableAccountId}`, { method: "DELETE", - headers, + headers: globalAdminSession.headers, }); expect(res.status).toBe(200); diff --git a/app/server/modules/backups/__tests__/backups.controller.test.ts b/app/server/modules/backups/__tests__/backups.controller.test.ts index cde32d47..01f69427 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 { describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vitest"; import { createApp } from "~/server/app"; import { createTestSession, getAuthHeaders } from "~/test/helpers/auth"; import { createTestVolume } from "~/test/helpers/volume"; @@ -7,6 +7,12 @@ import { createTestBackupSchedule } from "~/test/helpers/backup"; const app = createApp(); +let session: Awaited>; + +beforeAll(async () => { + session = await createTestSession(); +}); + describe("backups security", () => { test("should return 401 if no session cookie is provided", async () => { const res = await app.request("/api/v1/backups"); @@ -25,10 +31,8 @@ describe("backups security", () => { }); test("should return 200 if session is valid", async () => { - const { headers } = await createTestSession(); - const res = await app.request("/api/v1/backups", { - headers, + headers: session.headers, }); expect(res.status).toBe(200); @@ -82,17 +86,16 @@ describe("backups security", () => { describe("input validation", () => { test("should return a schedule when queried by short id", async () => { - const { headers, organizationId } = await createTestSession(); - const volume = await createTestVolume({ organizationId }); - const repository = await createTestRepository({ organizationId }); + const volume = await createTestVolume({ organizationId: session.organizationId }); + const repository = await createTestRepository({ organizationId: session.organizationId }); const schedule = await createTestBackupSchedule({ - organizationId, + organizationId: session.organizationId, volumeId: volume.id, repositoryId: repository.id, }); const res = await app.request(`/api/v1/backups/${schedule.shortId}`, { - headers, + headers: session.headers, }); expect(res.status).toBe(200); @@ -102,18 +105,16 @@ describe("backups security", () => { }); test("should return 404 for malformed schedule ID", async () => { - const { headers } = await createTestSession(); const res = await app.request("/api/v1/backups/not-a-number", { - headers, + headers: session.headers, }); expect(res.status).toBe(404); }); test("should return 404 for non-existent schedule ID", async () => { - const { headers } = await createTestSession(); const res = await app.request("/api/v1/backups/999999", { - headers, + headers: session.headers, }); expect(res.status).toBe(404); @@ -122,11 +123,10 @@ describe("backups security", () => { }); test("should return 400 for invalid payload on create", async () => { - const { headers } = await createTestSession(); const res = await app.request("/api/v1/backups", { method: "POST", headers: { - ...headers, + ...session.headers, "Content-Type": "application/json", }, body: JSON.stringify({ diff --git a/app/server/modules/notifications/__tests__/notifications.controller.test.ts b/app/server/modules/notifications/__tests__/notifications.controller.test.ts index 29615cac..06d529cf 100644 --- a/app/server/modules/notifications/__tests__/notifications.controller.test.ts +++ b/app/server/modules/notifications/__tests__/notifications.controller.test.ts @@ -1,9 +1,15 @@ -import { describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vitest"; import { createApp } from "~/server/app"; import { createTestSession, getAuthHeaders } from "~/test/helpers/auth"; const app = createApp(); +let session: Awaited>; + +beforeAll(async () => { + session = await createTestSession(); +}); + describe("notifications security", () => { test("should return 401 if no session cookie is provided", async () => { const res = await app.request("/api/v1/notifications/destinations"); @@ -22,10 +28,8 @@ describe("notifications security", () => { }); test("should return 200 if session is valid", async () => { - const { headers } = await createTestSession(); - const res = await app.request("/api/v1/notifications/destinations", { - headers, + headers: session.headers, }); expect(res.status).toBe(200); @@ -62,18 +66,16 @@ describe("notifications security", () => { describe("input validation", () => { test("should return 404 for malformed destination ID", async () => { - const { headers } = await createTestSession(); const res = await app.request("/api/v1/notifications/destinations/not-a-number", { - headers, + headers: session.headers, }); expect(res.status).toBe(404); }); test("should return 404 for non-existent destination ID", async () => { - const { headers } = await createTestSession(); const res = await app.request("/api/v1/notifications/destinations/999999", { - headers, + headers: session.headers, }); expect(res.status).toBe(404); @@ -82,12 +84,10 @@ describe("notifications security", () => { }); test("should return 400 for invalid payload on create", async () => { - const { headers } = await createTestSession(); - const res = await app.request("/api/v1/notifications/destinations", { method: "POST", headers: { - ...headers, + ...session.headers, "Content-Type": "application/json", }, body: JSON.stringify({ diff --git a/app/server/modules/provisioning/provisioning.test.ts b/app/server/modules/provisioning/provisioning.test.ts index d7bc25bb..06b9a4ca 100644 --- a/app/server/modules/provisioning/provisioning.test.ts +++ b/app/server/modules/provisioning/provisioning.test.ts @@ -1,15 +1,29 @@ -import { afterEach, describe, expect, test, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, 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"; import { db } from "~/server/db/db"; -import { backupSchedulesTable, repositoriesTable } from "~/server/db/schema"; +import { backupSchedulesTable, repositoriesTable, volumesTable } from "~/server/db/schema"; import { restic } from "~/server/core/restic"; import { createTestSession } from "~/test/helpers/auth"; import { generateShortId } from "~/server/utils/id"; import { provisionedResourcesSchema, readProvisionedResourcesFile, syncProvisionedResources } from "./provisioning"; describe("provisioning", () => { + let session: Awaited>; + + beforeAll(async () => { + session = await createTestSession(); + }); + + beforeEach(async () => { + vi.spyOn(restic, "init").mockResolvedValue({ success: true, error: null }); + + await db.delete(backupSchedulesTable); + await db.delete(volumesTable); + await db.delete(repositoriesTable); + }); + afterEach(() => { vi.restoreAllMocks(); }); @@ -46,7 +60,7 @@ describe("provisioning", () => { }); test("syncs provisioned repositories and volumes into the database", async () => { - const { organizationId } = await createTestSession(); + const { organizationId } = session; process.env.ZEROBYTE_PROVISIONED_ACCESS_KEY = "access-key-from-env"; @@ -113,7 +127,7 @@ describe("provisioning", () => { }); test("removes managed resources when delete is set", async () => { - const { organizationId } = await createTestSession(); + const { organizationId } = session; const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "zerobyte-provisioning-")); const provisioningPath = path.join(tempDir, "provisioning.json"); @@ -196,7 +210,7 @@ describe("provisioning", () => { }); test("renaming a provisioned volume updates it in place", async () => { - const { organizationId } = await createTestSession(); + const { organizationId } = session; const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "zerobyte-provisioning-")); const provisioningPath = path.join(tempDir, "provisioning.json"); const provisionedVolumeId = "shared-directory"; @@ -296,7 +310,7 @@ describe("provisioning", () => { }); test("does not partially sync resources when resolving a provisioned secret fails", async () => { - const { organizationId } = await createTestSession(); + const { organizationId } = session; const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "zerobyte-provisioning-")); const provisioningPath = path.join(tempDir, "provisioning.json"); @@ -344,7 +358,7 @@ describe("provisioning", () => { }); test("initializes a non-existing provisioned repository on first sync", async () => { - const { organizationId } = await createTestSession(); + const { organizationId } = session; const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "zerobyte-provisioning-")); const provisioningPath = path.join(tempDir, "provisioning.json"); const initMock = vi.fn(() => Promise.resolve({ success: true, error: null })); diff --git a/app/server/modules/repositories/__tests__/repositories.controller.test.ts b/app/server/modules/repositories/__tests__/repositories.controller.test.ts index e02530c3..c9cd0446 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 { describe, expect, test, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest"; import crypto from "node:crypto"; import { PassThrough } from "node:stream"; import { createApp } from "~/server/app"; @@ -7,9 +7,24 @@ import { repositoriesTable } from "~/server/db/schema"; import { generateShortId } from "~/server/utils/id"; import { createTestSession, getAuthHeaders } from "~/test/helpers/auth"; import type { RepositoryConfig } from "@zerobyte/core/restic"; +import { restic } from "~/server/core/restic"; const app = createApp(); +let session: Awaited>; + +beforeAll(async () => { + session = await createTestSession(); +}); + +beforeEach(() => { + vi.spyOn(restic, "init").mockResolvedValue({ success: true, error: null }); +}); + +afterEach(() => { + vi.restoreAllMocks(); +}); + const createRepositoryRecord = async (organizationId: string) => { const [repository] = await db .insert(repositoriesTable) @@ -81,10 +96,8 @@ describe("repositories security", () => { }); test("should return 200 if session is valid", async () => { - const { headers } = await createTestSession(); - const res = await app.request("/api/v1/repositories", { - headers, + headers: session.headers, }); expect(res.status).toBe(200); @@ -150,9 +163,8 @@ describe("repositories security", () => { describe("input validation", () => { test("should return 404 for non-existent repository", async () => { - const { headers } = await createTestSession(); const res = await app.request("/api/v1/repositories/non-existent-repo", { - headers, + headers: session.headers, }); expect(res.status).toBe(404); @@ -161,9 +173,8 @@ describe("repositories security", () => { }); test("should return 404 for stats of non-existent repository", async () => { - const { headers } = await createTestSession(); const res = await app.request("/api/v1/repositories/non-existent-repo/stats", { - headers, + headers: session.headers, }); expect(res.status).toBe(404); @@ -172,10 +183,9 @@ describe("repositories security", () => { }); test("should return 404 for stats refresh of non-existent repository", async () => { - const { headers } = await createTestSession(); const res = await app.request("/api/v1/repositories/non-existent-repo/stats/refresh", { method: "POST", - headers, + headers: session.headers, }); expect(res.status).toBe(404); @@ -184,11 +194,10 @@ describe("repositories security", () => { }); test("should return 400 for invalid payload on create", async () => { - const { headers } = await createTestSession(); const res = await app.request("/api/v1/repositories", { method: "POST", headers: { - ...headers, + ...session.headers, "Content-Type": "application/json", }, body: JSON.stringify({ @@ -200,11 +209,10 @@ describe("repositories security", () => { }); test("should accept env:// values as plain secrets on create", async () => { - const { headers } = await createTestSession(); const res = await app.request("/api/v1/repositories", { method: "POST", headers: { - ...headers, + ...session.headers, "Content-Type": "application/json", }, body: JSON.stringify({ @@ -227,14 +235,13 @@ describe("repositories security", () => { describe("repositories updates", () => { test("PATCH updates full config and metadata using shortId", async () => { - const { headers, organizationId } = await createTestSession(); - const repository = await createRepositoryRecord(organizationId); + const repository = await createRepositoryRecord(session.organizationId); const nextPath = `/tmp/updated-${crypto.randomUUID()}`; const res = await app.request(`/api/v1/repositories/${repository.shortId}`, { method: "PATCH", headers: { - ...headers, + ...session.headers, "Content-Type": "application/json", }, body: JSON.stringify({ @@ -276,13 +283,12 @@ describe("repositories updates", () => { }); test("PATCH rejects backend changes", async () => { - const { headers, organizationId } = await createTestSession(); - const repository = await createRepositoryRecord(organizationId); + const repository = await createRepositoryRecord(session.organizationId); const res = await app.request(`/api/v1/repositories/${repository.shortId}`, { method: "PATCH", headers: { - ...headers, + ...session.headers, "Content-Type": "application/json", }, body: JSON.stringify({ @@ -302,13 +308,12 @@ describe("repositories updates", () => { }); test("PATCH rejects invalid config payload", async () => { - const { headers, organizationId } = await createTestSession(); - const repository = await createRepositoryRecord(organizationId); + const repository = await createRepositoryRecord(session.organizationId); const res = await app.request(`/api/v1/repositories/${repository.shortId}`, { method: "PATCH", headers: { - ...headers, + ...session.headers, "Content-Type": "application/json", }, body: JSON.stringify({ @@ -323,8 +328,7 @@ describe("repositories updates", () => { describe("delete snapshot", () => { test("should return 500 when restic deleteSnapshot throws ResticError", async () => { - const { headers, organizationId } = await createTestSession(); - const repository = await createRepositoryRecord(organizationId); + const repository = await createRepositoryRecord(session.organizationId); const { restic } = await import("~/server/core/restic"); const { ResticError } = await import("@zerobyte/core/restic"); @@ -336,7 +340,7 @@ describe("repositories updates", () => { try { const res = await app.request(`/api/v1/repositories/${repository.shortId}/snapshots/snap123`, { method: "DELETE", - headers, + headers: session.headers, }); expect(res.status).toBe(500); @@ -351,8 +355,7 @@ describe("repositories updates", () => { describe("dump snapshot", () => { test("continues streaming a download after the request signal aborts", async () => { - const { headers, organizationId } = await createTestSession(); - const repository = await createRepositoryRecord(organizationId); + const repository = await createRepositoryRecord(session.organizationId); const { repositoriesService } = await import("~/server/modules/repositories/repositories.service"); const stream = new PassThrough(); @@ -371,7 +374,7 @@ describe("repositories updates", () => { try { const controller = new AbortController(); const response = await app.request(`/api/v1/repositories/${repository.shortId}/snapshots/test-snapshot/dump`, { - headers, + headers: session.headers, signal: controller.signal, }); @@ -387,8 +390,7 @@ describe("repositories updates", () => { }); test("returns a valid content-disposition header for non-ascii filenames", async () => { - const { headers, organizationId } = await createTestSession(); - const repository = await createRepositoryRecord(organizationId); + const repository = await createRepositoryRecord(session.organizationId); const { repositoriesService } = await import("~/server/modules/repositories/repositories.service"); const stream = new PassThrough(); @@ -406,7 +408,7 @@ describe("repositories updates", () => { stream.end("downloaded snapshot contents"); const response = await app.request(`/api/v1/repositories/${repository.shortId}/snapshots/test-snapshot/dump`, { - headers, + headers: session.headers, }); expect(response.status).toBe(200); @@ -420,10 +422,9 @@ describe("repositories updates", () => { }); test("GET marks provisioned repositories as managed", async () => { - const { headers, organizationId } = await createTestSession(); - const repository = await createManagedRepositoryRecord(organizationId); + const repository = await createManagedRepositoryRecord(session.organizationId); - const res = await app.request(`/api/v1/repositories/${repository.shortId}`, { headers }); + const res = await app.request(`/api/v1/repositories/${repository.shortId}`, { headers: session.headers }); expect(res.status).toBe(200); const body = await res.json(); @@ -431,13 +432,12 @@ describe("repositories updates", () => { }); test("PATCH allows updates for managed repositories", async () => { - const { headers, organizationId } = await createTestSession(); - const repository = await createManagedRepositoryRecord(organizationId); + const repository = await createManagedRepositoryRecord(session.organizationId); const res = await app.request(`/api/v1/repositories/${repository.shortId}`, { method: "PATCH", headers: { - ...headers, + ...session.headers, "Content-Type": "application/json", }, body: JSON.stringify({ @@ -451,12 +451,11 @@ describe("repositories updates", () => { }); test("DELETE allows managed repositories", async () => { - const { headers, organizationId } = await createTestSession(); - const repository = await createManagedRepositoryRecord(organizationId); + const repository = await createManagedRepositoryRecord(session.organizationId); const res = await app.request(`/api/v1/repositories/${repository.shortId}`, { method: "DELETE", - headers, + headers: session.headers, }); expect(res.status).toBe(200); diff --git a/app/server/modules/repositories/__tests__/repositories.service.test.ts b/app/server/modules/repositories/__tests__/repositories.service.test.ts index 5cdece98..e5068b88 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, test, vi } from "vitest"; +import { afterEach, beforeAll, 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"; @@ -37,6 +37,12 @@ const createTestRepository = async (organizationId: string) => { return repository; }; +let session: Awaited>; + +beforeAll(async () => { + session = await createTestSession(); +}); + describe("repositoriesService.createRepository", () => { const initMock = vi.fn(() => Promise.resolve({ success: true, error: null })); @@ -51,11 +57,10 @@ describe("repositoriesService.createRepository", () => { test("creates a shortId-scoped repository path when using the repository base directory", async () => { // arrange - const { organizationId, user } = await createTestSession(); const config: RepositoryConfig = { backend: "local", path: REPOSITORY_BASE }; // act - const result = await withContext({ organizationId, userId: user.id }, () => + const result = await withContext({ organizationId: session.organizationId, userId: session.user.id }, () => repositoriesService.createRepository("main repo", config), ); @@ -80,12 +85,11 @@ describe("repositoriesService.createRepository", () => { test("creates a shortId-scoped repository path when using a custom directory", async () => { // arrange - const { organizationId, user } = await createTestSession(); const explicitPath = `${REPOSITORY_BASE}/custom-${randomUUID()}`; const config: RepositoryConfig = { backend: "local", path: explicitPath }; // act - const result = await withContext({ organizationId, userId: user.id }, () => + const result = await withContext({ organizationId: session.organizationId, userId: session.user.id }, () => repositoriesService.createRepository("custom repo", config), ); @@ -109,14 +113,13 @@ describe("repositoriesService.createRepository", () => { test("keeps an explicit local repository path unchanged when importing existing repository", async () => { // arrange - const { organizationId, user } = await createTestSession(); const explicitPath = `${REPOSITORY_BASE}/custom-${randomUUID()}`; const config: RepositoryConfig = { backend: "local", path: explicitPath, isExistingRepository: true }; vi.spyOn(restic, "snapshots").mockImplementation(() => Promise.resolve([])); // act - const result = await withContext({ organizationId, userId: user.id }, () => + const result = await withContext({ organizationId: session.organizationId, userId: session.user.id }, () => repositoriesService.createRepository("existing repo", config), ); @@ -144,10 +147,9 @@ describe("repositoriesService repository stats", () => { }); test("returns empty stats when repository has not been populated yet", async () => { - const { organizationId, user } = await createTestSession(); - const repository = await createTestRepository(organizationId); + const repository = await createTestRepository(session.organizationId); - const stats = await withContext({ organizationId, userId: user.id }, () => + const stats = await withContext({ organizationId: session.organizationId, userId: session.user.id }, () => repositoriesService.getRepositoryStats(repository.shortId), ); @@ -162,8 +164,7 @@ describe("repositoriesService repository stats", () => { }); test("refreshes and persists repository stats", async () => { - const { organizationId, user } = await createTestSession(); - const repository = await createTestRepository(organizationId); + const repository = await createTestRepository(session.organizationId); const expectedStats = { total_size: 1024, total_uncompressed_size: 2048, @@ -175,7 +176,7 @@ describe("repositoriesService repository stats", () => { const statsSpy = vi.spyOn(restic, "stats").mockResolvedValue(expectedStats); - const refreshed = await withContext({ organizationId, userId: user.id }, () => + const refreshed = await withContext({ organizationId: session.organizationId, userId: session.user.id }, () => repositoriesService.refreshRepositoryStats(repository.shortId), ); @@ -186,7 +187,7 @@ describe("repositoriesService repository stats", () => { expect(persistedRepository?.stats).toEqual(expectedStats); expect(typeof persistedRepository?.statsUpdatedAt).toBe("number"); - const loaded = await withContext({ organizationId, userId: user.id }, () => + const loaded = await withContext({ organizationId: session.organizationId, userId: session.user.id }, () => repositoriesService.getRepositoryStats(repository.shortId), ); @@ -215,7 +216,7 @@ describe("repositoriesService.dumpSnapshot", () => { basePath: string; snapshotPaths?: string[]; }) => { - const { organizationId, user } = await createTestSession(); + const organizationId = session.organizationId; const shortId = generateShortId(); await db.insert(repositoriesTable).values({ @@ -247,7 +248,7 @@ describe("repositoriesService.dumpSnapshot", () => { return { organizationId, - userId: user.id, + userId: session.user.id, shortId, basePath, dumpMock, @@ -358,7 +359,7 @@ describe("repositoriesService.restoreSnapshot", () => { }); const setupRestoreSnapshotScenario = async () => { - const { organizationId, user } = await createTestSession(); + const organizationId = session.organizationId; const repository = await createTestRepository(organizationId); vi.spyOn(restic, "snapshots").mockResolvedValue([ @@ -387,7 +388,7 @@ describe("repositoriesService.restoreSnapshot", () => { return { organizationId, - userId: user.id, + userId: session.user.id, repositoryShortId: repository.shortId, restoreMock, }; @@ -438,7 +439,7 @@ describe("repositoriesService.getRetentionCategories", () => { }); test("recomputes retention categories after repository cache invalidation", async () => { - const { organizationId, user } = await createTestSession(); + const organizationId = session.organizationId; const schedule = await createTestBackupSchedule({ organizationId, retentionPolicy: { keepLast: 1 } }); const repository = await db.query.repositoriesTable.findFirst({ where: { id: schedule.repositoryId } }); @@ -480,7 +481,7 @@ describe("repositoriesService.getRetentionCategories", () => { forgetSpy.mockResolvedValueOnce(buildForgetResponse(oldSnapshotId)); forgetSpy.mockResolvedValueOnce(buildForgetResponse(newSnapshotId)); - const firstCategories = await withContext({ organizationId, userId: user.id }, () => + const firstCategories = await withContext({ organizationId, userId: session.user.id }, () => repositoriesService.getRetentionCategories(repository.shortId, schedule.shortId), ); @@ -488,7 +489,7 @@ describe("repositoriesService.getRetentionCategories", () => { cache.delByPrefix(cacheKeys.repository.all(repository.id)); - const secondCategories = await withContext({ organizationId, userId: user.id }, () => + const secondCategories = await withContext({ organizationId, userId: session.user.id }, () => repositoriesService.getRetentionCategories(repository.shortId, schedule.shortId), ); @@ -504,8 +505,7 @@ describe("repositoriesService.deleteSnapshot", () => { }); test("refreshes repository stats in background after successful deletion", async () => { - const { organizationId, user } = await createTestSession(); - const repository = await createTestRepository(organizationId); + const repository = await createTestRepository(session.organizationId); const expectedStats = { total_size: 128, total_uncompressed_size: 256, @@ -518,7 +518,7 @@ describe("repositoriesService.deleteSnapshot", () => { vi.spyOn(restic, "deleteSnapshot").mockResolvedValue({ success: true }); const statsSpy = vi.spyOn(restic, "stats").mockResolvedValue(expectedStats); - await withContext({ organizationId, userId: user.id }, () => + await withContext({ organizationId: session.organizationId, userId: session.user.id }, () => repositoriesService.deleteSnapshot(repository.shortId, "snap-1"), ); @@ -532,15 +532,14 @@ describe("repositoriesService.deleteSnapshot", () => { }); test("should throw original error when restic deleteSnapshot fails", async () => { - const { organizationId, user } = await createTestSession(); - const repository = await createTestRepository(organizationId); + const repository = await createTestRepository(session.organizationId); vi.spyOn(restic, "deleteSnapshot").mockImplementation(async () => { throw new ResticError(1, "Fatal: unexpected HTTP response (403): 403 Forbidden"); }); await expect( - withContext({ organizationId, userId: user.id }, () => + withContext({ organizationId: session.organizationId, userId: session.user.id }, () => repositoriesService.deleteSnapshot(repository.shortId, "snap123"), ), ).rejects.toThrow("Fatal: unexpected HTTP response"); diff --git a/app/server/modules/system/__tests__/system.controller.test.ts b/app/server/modules/system/__tests__/system.controller.test.ts index ec44a662..4d659d9d 100644 --- a/app/server/modules/system/__tests__/system.controller.test.ts +++ b/app/server/modules/system/__tests__/system.controller.test.ts @@ -1,9 +1,19 @@ -import { describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test, vi } from "vitest"; import { createApp } from "~/server/app"; import { createTestSession, createTestSessionWithGlobalAdmin, getAuthHeaders } from "~/test/helpers/auth"; +import { systemService } from "../system.service"; +import * as authHelpers from "~/server/modules/auth/helpers"; const app = createApp(); +let session: Awaited>; +let globalAdminSession: Awaited>; + +beforeAll(async () => { + session = await createTestSession(); + globalAdminSession = await createTestSessionWithGlobalAdmin(); +}); + describe("system security", () => { test("should return 401 if no session cookie is provided", async () => { const res = await app.request("/api/v1/system/info"); @@ -22,10 +32,8 @@ describe("system security", () => { }); test("should return 200 if session is valid", async () => { - const { headers } = await createTestSession(); - const res = await app.request("/api/v1/system/info", { - headers, + headers: session.headers, }); expect(res.status).toBe(200); @@ -53,19 +61,17 @@ describe("system security", () => { describe("registration-status endpoint", () => { test("GET /api/v1/system/registration-status should be accessible with valid session", async () => { - const { headers } = await createTestSession(); - const res = await app.request("/api/v1/system/registration-status", { headers }); + const res = await app.request("/api/v1/system/registration-status", { headers: session.headers }); expect(res.status).toBe(200); const body = await res.json(); expect(typeof body.enabled).toBe("boolean"); }); test("PUT /api/v1/system/registration-status should return 403 for non-admin users", async () => { - const { headers } = await createTestSession(); const res = await app.request("/api/v1/system/registration-status", { method: "PUT", headers: { - ...headers, + ...session.headers, "Content-Type": "application/json", }, body: JSON.stringify({ enabled: false }), @@ -76,11 +82,10 @@ describe("system security", () => { }); test("PUT /api/v1/system/registration-status should be accessible to global admin", async () => { - const { headers } = await createTestSessionWithGlobalAdmin(); const res = await app.request("/api/v1/system/registration-status", { method: "PUT", headers: { - ...headers, + ...globalAdminSession.headers, "Content-Type": "application/json", }, body: JSON.stringify({ enabled: false }), @@ -91,8 +96,7 @@ describe("system security", () => { describe("dev-panel endpoint", () => { test("GET /api/v1/system/dev-panel should be accessible with valid session", async () => { - const { headers } = await createTestSession(); - const res = await app.request("/api/v1/system/dev-panel", { headers }); + const res = await app.request("/api/v1/system/dev-panel", { headers: session.headers }); expect(res.status).toBe(200); const body = await res.json(); expect(typeof body.enabled).toBe("boolean"); @@ -101,19 +105,24 @@ describe("system security", () => { describe("updates endpoint", () => { test("GET /api/v1/system/updates should be accessible with valid session", async () => { - const { headers } = await createTestSession(); - const res = await app.request("/api/v1/system/updates", { headers }); + vi.spyOn(systemService, "getUpdates").mockResolvedValue({ + currentVersion: "1.0.0", + latestVersion: "1.0.0", + hasUpdate: false, + missedReleases: [], + }); + + const res = await app.request("/api/v1/system/updates", { headers: session.headers }); expect(res.status).toBe(200); }); }); describe("input validation", () => { test("should return 400 for invalid payload on restic-password", async () => { - const { headers } = await createTestSession(); const res = await app.request("/api/v1/system/restic-password", { method: "POST", headers: { - ...headers, + ...session.headers, "Content-Type": "application/json", }, body: JSON.stringify({}), @@ -123,11 +132,12 @@ describe("system security", () => { }); test("should return 401 for incorrect password on restic-password", async () => { - const { headers } = await createTestSession(); + vi.spyOn(authHelpers, "verifyUserPassword").mockResolvedValue(false); + const res = await app.request("/api/v1/system/restic-password", { method: "POST", headers: { - ...headers, + ...session.headers, "Content-Type": "application/json", }, body: JSON.stringify({ diff --git a/app/server/modules/volumes/__tests__/volumes.controller.test.ts b/app/server/modules/volumes/__tests__/volumes.controller.test.ts index 9c85efe5..c577001b 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 { describe, expect, test } from "vitest"; +import { beforeAll, describe, expect, test } from "vitest"; import { db } from "~/server/db/db"; import { volumesTable } from "~/server/db/schema"; import { createApp } from "~/server/app"; @@ -7,6 +7,12 @@ import { generateShortId } from "~/server/utils/id"; const app = createApp(); +let session: Awaited>; + +beforeAll(async () => { + session = await createTestSession(); +}); + const createManagedVolumeRecord = async (organizationId: string) => { const [volume] = await db .insert(volumesTable) @@ -46,10 +52,8 @@ describe("volumes security", () => { }); test("should return 200 if session is valid", async () => { - const { headers } = await createTestSession(); - const res = await app.request("/api/v1/volumes", { - headers, + headers: session.headers, }); expect(res.status).toBe(200); @@ -91,9 +95,8 @@ describe("volumes security", () => { describe("input validation", () => { test("should return 404 for non-existent volume", async () => { - const { headers } = await createTestSession(); const res = await app.request("/api/v1/volumes/non-existent-volume", { - headers, + headers: session.headers, }); expect(res.status).toBe(404); @@ -102,11 +105,10 @@ describe("volumes security", () => { }); test("should return 400 for invalid payload on create", async () => { - const { headers } = await createTestSession(); const res = await app.request("/api/v1/volumes", { method: "POST", headers: { - ...headers, + ...session.headers, "Content-Type": "application/json", }, body: JSON.stringify({ @@ -118,10 +120,9 @@ describe("volumes security", () => { }); test("should mark provisioned volumes as managed", async () => { - const { headers, organizationId } = await createTestSession(); - const volume = await createManagedVolumeRecord(organizationId); + const volume = await createManagedVolumeRecord(session.organizationId); - const res = await app.request(`/api/v1/volumes/${volume.shortId}`, { headers }); + const res = await app.request(`/api/v1/volumes/${volume.shortId}`, { headers: session.headers }); expect(res.status).toBe(200); const body = await res.json(); @@ -129,13 +130,12 @@ describe("volumes security", () => { }); test("should allow updates for managed volumes", async () => { - const { headers, organizationId } = await createTestSession(); - const volume = await createManagedVolumeRecord(organizationId); + const volume = await createManagedVolumeRecord(session.organizationId); const res = await app.request(`/api/v1/volumes/${volume.shortId}`, { method: "PUT", headers: { - ...headers, + ...session.headers, "Content-Type": "application/json", }, body: JSON.stringify({ @@ -149,12 +149,11 @@ describe("volumes security", () => { }); test("should allow deletion for managed volumes", async () => { - const { headers, organizationId } = await createTestSession(); - const volume = await createManagedVolumeRecord(organizationId); + const volume = await createManagedVolumeRecord(session.organizationId); const res = await app.request(`/api/v1/volumes/${volume.shortId}`, { method: "DELETE", - headers, + headers: session.headers, }); expect(res.status).toBe(200); diff --git a/app/server/utils/__tests__/spawn.test.ts b/app/server/utils/__tests__/spawn.test.ts index 4c9269cf..e037e894 100644 --- a/app/server/utils/__tests__/spawn.test.ts +++ b/app/server/utils/__tests__/spawn.test.ts @@ -41,7 +41,7 @@ describe("safeExec", () => { const result = await safeExec({ command: "sleep", args: ["10"], - timeout: 100, + timeout: 20, }); expect(result.timedOut).toBe(true); diff --git a/app/test/helpers/db.ts b/app/test/helpers/db.ts new file mode 100644 index 00000000..adff565f --- /dev/null +++ b/app/test/helpers/db.ts @@ -0,0 +1,25 @@ +import fs from "node:fs"; +import path from "node:path"; +import { tmpdir } from "node:os"; +import { vi } from "vitest"; + +export const createTestDb = async () => { + const projectRoot = process.cwd(); + const cacheRoot = fs.mkdtempSync(path.join(tmpdir(), "zerobyte-test-cache-")); + + process.env.ZEROBYTE_DATABASE_URL = ":memory:"; + vi.resetModules(); + + const database = await import("~/server/db/db"); + + await database.runDbMigrations(); + + process.chdir(cacheRoot); + try { + await import("~/server/utils/cache"); + } finally { + process.chdir(projectRoot); + } + + return database; +}; diff --git a/app/test/setup.ts b/app/test/setup.ts index 1b416c23..161fae78 100644 --- a/app/test/setup.ts +++ b/app/test/setup.ts @@ -1,12 +1,5 @@ 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"; -beforeAll(async () => { - const migrationsFolder = path.join(cwd(), "app", "drizzle"); - migrate(db, { migrationsFolder }); - db.run("PRAGMA foreign_keys = ON;"); -}); +const { createTestDb } = await import("~/test/helpers/db"); + +await createTestDb(); diff --git a/packages/core/package.json b/packages/core/package.json index 19ef4440..533f2a62 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -26,7 +26,7 @@ }, "scripts": { "tsc": "tsc --noEmit", - "test": "bunx vitest run --config ./vitest.config.ts" + "test": "bunx --bun vitest run --config ./vitest.config.ts" }, "devDependencies": { "@types/bun": "latest" diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts index 62b6ca1b..74d7d98d 100644 --- a/packages/core/vitest.config.ts +++ b/packages/core/vitest.config.ts @@ -3,8 +3,6 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ test: { environment: "node", - pool: "forks", - maxWorkers: 4, server: { deps: { inline: ["zod"], diff --git a/vitest.config.ts b/vitest.config.ts index f498a514..3445aa13 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -8,8 +8,6 @@ export default defineConfig({ }, }, test: { - pool: "forks", - maxWorkers: 4, server: { deps: { inline: ["zod"],