test: ensure organization is created
This commit is contained in:
parent
207c88ef7e
commit
203df04214
10 changed files with 103 additions and 26 deletions
|
|
@ -1 +1,2 @@
|
|||
DATABASE_URL=:memory:
|
||||
ZEROBYTE_APP_SECRET=8b9acd4456dd5db0a4a3c4f4e1240b2c3ae08bb59690167197425e4a25dd9a69
|
||||
|
|
|
|||
11
AGENTS.md
11
AGENTS.md
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
35
app/test/helpers/organization.ts
Normal file
35
app/test/helpers/organization.ts
Normal 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();
|
||||
};
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
},
|
||||
}));
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue