test: ensure organization is created

This commit is contained in:
Nicolas Meienberger 2026-01-17 21:35:50 +01:00
parent 207c88ef7e
commit 203df04214
10 changed files with 103 additions and 26 deletions

View file

@ -1 +1,2 @@
DATABASE_URL=:memory:
ZEROBYTE_APP_SECRET=8b9acd4456dd5db0a4a3c4f4e1240b2c3ae08bb59690167197425e4a25dd9a69

View file

@ -75,14 +75,13 @@ bun run gen:api-client
### Code Quality
```bash
# Format and lint (Biome)
bunx biome check --write .
# Format and lint
# Format only
bunx biome format --write .
# Format
bunx oxfmt format --write <path>
# Lint only
bunx biome lint .
# Lint
bun run lint
```
## Architecture

View file

@ -7,6 +7,7 @@ import { generateBackupOutput } from "~/test/helpers/restic";
import { getVolumePath } from "../../volumes/helpers";
import { restic } from "~/server/utils/restic";
import path from "node:path";
import { TEST_ORG_ID } from "~/test/helpers/organization";
const backupMock = mock(() => Promise.resolve({ exitCode: 0, result: JSON.parse(generateBackupOutput()) }));
@ -36,7 +37,7 @@ describe("executeBackup - include / exclude patterns", () => {
});
// act
await backupsService.executeBackup(schedule.id);
await backupsService.executeBackup(schedule.id, TEST_ORG_ID);
// assert
expect(backupMock).toHaveBeenCalledWith(
@ -67,7 +68,7 @@ describe("executeBackup - include / exclude patterns", () => {
});
// act
await backupsService.executeBackup(schedule.id);
await backupsService.executeBackup(schedule.id, TEST_ORG_ID);
// assert
expect(backupMock).toHaveBeenCalledWith(
@ -97,7 +98,7 @@ describe("executeBackup - include / exclude patterns", () => {
});
// act
await backupsService.executeBackup(schedule.id);
await backupsService.executeBackup(schedule.id, TEST_ORG_ID);
// assert
expect(backupMock).toHaveBeenCalledWith(
@ -121,7 +122,7 @@ describe("executeBackup - include / exclude patterns", () => {
});
// act
await backupsService.executeBackup(schedule.id);
await backupsService.executeBackup(schedule.id, TEST_ORG_ID);
// assert
expect(backupMock).toHaveBeenCalledWith(

View file

@ -6,6 +6,7 @@ import { createTestRepository } from "~/test/helpers/repository";
import { generateBackupOutput } from "~/test/helpers/restic";
import { faker } from "@faker-js/faker";
import * as spawnModule from "~/server/utils/spawn";
import { TEST_ORG_ID } from "~/test/helpers/organization";
const resticBackupMock = mock(() => Promise.resolve({ exitCode: 0, summary: "", error: "" }));
@ -35,10 +36,10 @@ describe("execute backup", () => {
);
// act
await backupsService.executeBackup(schedule.id);
await backupsService.executeBackup(schedule.id, TEST_ORG_ID);
// assert
const updatedSchedule = await backupsService.getSchedule(schedule.id);
const updatedSchedule = await backupsService.getSchedule(schedule.id, TEST_ORG_ID);
expect(updatedSchedule.nextBackupAt).not.toBeNull();
const nextBackupAt = new Date(updatedSchedule.nextBackupAt ?? 0);
@ -59,7 +60,7 @@ describe("execute backup", () => {
});
// act
await backupsService.executeBackup(schedule.id);
await backupsService.executeBackup(schedule.id, TEST_ORG_ID);
// assert
expect(resticBackupMock).not.toHaveBeenCalled();
@ -80,7 +81,7 @@ describe("execute backup", () => {
);
// act
await backupsService.executeBackup(schedule.id, true);
await backupsService.executeBackup(schedule.id, TEST_ORG_ID, true);
// assert
expect(resticBackupMock).toHaveBeenCalled();
@ -101,9 +102,9 @@ describe("execute backup", () => {
});
// act
void backupsService.executeBackup(schedule.id);
void backupsService.executeBackup(schedule.id, TEST_ORG_ID);
await new Promise((resolve) => setTimeout(resolve, 10));
await backupsService.executeBackup(schedule.id);
await backupsService.executeBackup(schedule.id, TEST_ORG_ID);
// assert
expect(resticBackupMock).toHaveBeenCalledTimes(1);
@ -123,10 +124,10 @@ describe("execute backup", () => {
);
// act
await backupsService.executeBackup(schedule.id);
await backupsService.executeBackup(schedule.id, TEST_ORG_ID);
// assert
const updatedSchedule = await backupsService.getSchedule(schedule.id);
const updatedSchedule = await backupsService.getSchedule(schedule.id, TEST_ORG_ID);
expect(updatedSchedule.lastBackupStatus).toBe("warning");
});
@ -144,10 +145,10 @@ describe("execute backup", () => {
);
// act
await backupsService.executeBackup(schedule.id);
await backupsService.executeBackup(schedule.id, TEST_ORG_ID);
// assert
const updatedSchedule = await backupsService.getSchedule(schedule.id);
const updatedSchedule = await backupsService.getSchedule(schedule.id, TEST_ORG_ID);
expect(updatedSchedule.lastBackupStatus).toBe("error");
});
});
@ -168,7 +169,7 @@ describe("getSchedulesToExecute", () => {
});
// act
const schedulesToExecute = await backupsService.getSchedulesToExecute();
const schedulesToExecute = await backupsService.getSchedulesToExecute(TEST_ORG_ID);
// assert
expect(schedulesToExecute).toContain(schedule.id);

View file

@ -1,7 +1,8 @@
import { db } from "~/server/db/db";
import { sessionsTable, usersTable, account } from "~/server/db/schema";
import { sessionsTable, usersTable, account, organization, member } from "~/server/db/schema";
import { hashPassword } from "better-auth/crypto";
import { createHmac } from "node:crypto";
import { eq } from "drizzle-orm";
export async function createTestSession() {
const [existingUser] = await db.select().from(usersTable);
@ -21,6 +22,34 @@ export async function createTestSession() {
const sessionId = token;
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
const [existingOrg] = await db.select().from(organization);
let orgId: string;
if (!existingOrg) {
orgId = crypto.randomUUID();
await db.insert(organization).values({
id: orgId,
name: "Test Org",
slug: "test-org",
createdAt: new Date(),
});
} else {
orgId = existingOrg.id;
}
const [existingMember] = await db.select().from(member).where(eq(member.userId, user.id));
if (!existingMember) {
await db.insert(member).values({
id: crypto.randomUUID(),
userId: user.id,
organizationId: orgId,
role: "owner",
createdAt: new Date(),
});
}
await db.insert(sessionsTable).values({
id: sessionId,
userId: user.id,
@ -28,12 +57,10 @@ export async function createTestSession() {
token: token,
createdAt: new Date(),
updatedAt: new Date(),
activeOrganizationId: orgId,
});
// Better Auth signs the token using HMAC-SHA256 with the secret
// The secret is "test-secret" because we mocked cryptoUtils.deriveSecret
const signature = createHmac("sha256", "test-secret").update(token).digest("base64");
const signedToken = `${token}.${signature}`;
await db
@ -49,5 +76,5 @@ export async function createTestSession() {
})
.onConflictDoNothing();
return { token: encodeURIComponent(signedToken), user };
return { token: encodeURIComponent(signedToken), user, organizationId: orgId };
}

View file

@ -1,14 +1,18 @@
import { db } from "~/server/db/db";
import { faker } from "@faker-js/faker";
import { backupSchedulesTable, type BackupScheduleInsert } from "~/server/db/schema";
import { ensureTestOrganization, TEST_ORG_ID } from "./organization";
export const createTestBackupSchedule = async (overrides: Partial<BackupScheduleInsert> = {}) => {
await ensureTestOrganization();
const backup: BackupScheduleInsert = {
name: faker.system.fileName(),
cronExpression: "0 0 * * *",
repositoryId: "repo_123",
volumeId: 1,
shortId: faker.string.uuid(),
organizationId: TEST_ORG_ID,
...overrides,
};

View file

@ -0,0 +1,35 @@
import { db } from "~/server/db/db";
import { organization, type OrganizationMetadata } from "~/server/db/schema";
import { faker } from "@faker-js/faker";
export const TEST_ORG_ID = "test-org-00000001";
export const createTestOrganization = async (overrides: Partial<typeof organization.$inferInsert> = {}) => {
const metadata: OrganizationMetadata = {
resticPassword: "test-encrypted-restic-password",
};
const org: typeof organization.$inferInsert = {
id: TEST_ORG_ID,
name: "Test Organization",
slug: `test-org-${faker.string.alphanumeric(6)}`,
createdAt: new Date(),
metadata,
...overrides,
};
const existing = await db.query.organization.findFirst({
where: (o, { eq }) => eq(o.id, org.id ?? TEST_ORG_ID),
});
if (existing) {
return existing;
}
const data = await db.insert(organization).values(org).returning();
return data[0];
};
export const ensureTestOrganization = async () => {
return createTestOrganization();
};

View file

@ -1,8 +1,11 @@
import { db } from "~/server/db/db";
import { faker } from "@faker-js/faker";
import { repositoriesTable, type RepositoryInsert } from "~/server/db/schema";
import { ensureTestOrganization, TEST_ORG_ID } from "./organization";
export const createTestRepository = async (overrides: Partial<RepositoryInsert> = {}) => {
await ensureTestOrganization();
const repository: RepositoryInsert = {
id: faker.string.alphanumeric(6),
name: faker.string.alphanumeric(10),
@ -12,6 +15,7 @@ export const createTestRepository = async (overrides: Partial<RepositoryInsert>
backend: "local",
},
type: "local",
organizationId: TEST_ORG_ID,
...overrides,
};

View file

@ -1,8 +1,11 @@
import { db } from "~/server/db/db";
import { faker } from "@faker-js/faker";
import { volumesTable, type VolumeInsert } from "~/server/db/schema";
import { ensureTestOrganization, TEST_ORG_ID } from "./organization";
export const createTestVolume = async (overrides: Partial<VolumeInsert> = {}) => {
await ensureTestOrganization();
const volume: VolumeInsert = {
name: faker.system.fileName(),
config: {
@ -13,6 +16,7 @@ export const createTestVolume = async (overrides: Partial<VolumeInsert> = {}) =>
autoRemount: true,
shortId: faker.string.alphanumeric(6),
type: "directory",
organizationId: TEST_ORG_ID,
...overrides,
};

View file

@ -22,6 +22,7 @@ void mock.module("~/server/utils/crypto", () => ({
deriveSecret: async () => "test-secret",
sealSecret: async (v: string) => v,
resolveSecret: async (v: string) => v,
generateResticPassword: () => "test-restic-password",
},
}));