From b7b5306619cc4420bd0ae494881ee7161391dec4 Mon Sep 17 00:00:00 2001 From: MohamedBassem Date: Sat, 2 Mar 2024 09:53:24 +0000 Subject: [PATCH] feature: Make the first user an admin automatically --- packages/web/lib/testUtils.ts | 31 ++++++---- .../web/server/api/routers/bookmarks.test.ts | 2 +- packages/web/server/api/routers/users.test.ts | 57 +++++++++++++++++++ packages/web/server/api/routers/users.ts | 7 +++ 4 files changed, 86 insertions(+), 11 deletions(-) create mode 100644 packages/web/server/api/routers/users.test.ts diff --git a/packages/web/lib/testUtils.ts b/packages/web/lib/testUtils.ts index 142ad844..bad78463 100644 --- a/packages/web/lib/testUtils.ts +++ b/packages/web/lib/testUtils.ts @@ -25,13 +25,15 @@ export async function seedUsers(db: TestDB) { .returning(); } -export function getApiCaller(db: TestDB, userId: string) { +export function getApiCaller(db: TestDB, userId?: string) { const createCaller = createCallerFactory(appRouter); return createCaller({ - user: { - id: userId, - role: "user", - }, + user: userId + ? { + id: userId, + role: "user", + } + : null, db, }); } @@ -40,20 +42,29 @@ export type APICallerType = ReturnType; export interface CustomTestContext { apiCallers: APICallerType[]; + unauthedAPICaller: APICallerType; db: TestDB; } -export async function buildTestContext(): Promise { +export async function buildTestContext( + seedDB: boolean, +): Promise { const db = getTestDB(); - const users = await seedUsers(db); + let users: Awaited> = []; + if (seedDB) { + users = await seedUsers(db); + } const callers = users.map((u) => getApiCaller(db, u.id)); return { apiCallers: callers, + unauthedAPICaller: getApiCaller(db), db, }; } -export const defaultBeforeEach = async (context: object) => { - Object.assign(context, await buildTestContext()); -}; +export function defaultBeforeEach(seedDB: boolean = true) { + return async (context: object) => { + Object.assign(context, await buildTestContext(seedDB)); + }; +} diff --git a/packages/web/server/api/routers/bookmarks.test.ts b/packages/web/server/api/routers/bookmarks.test.ts index 603a173e..626a7250 100644 --- a/packages/web/server/api/routers/bookmarks.test.ts +++ b/packages/web/server/api/routers/bookmarks.test.ts @@ -1,7 +1,7 @@ import { CustomTestContext, defaultBeforeEach } from "@/lib/testUtils"; import { expect, describe, test, beforeEach, assert } from "vitest"; -beforeEach(defaultBeforeEach); +beforeEach(defaultBeforeEach(true)); describe("Bookmark Routes", () => { test("create bookmark", async ({ apiCallers }) => { diff --git a/packages/web/server/api/routers/users.test.ts b/packages/web/server/api/routers/users.test.ts new file mode 100644 index 00000000..b188d3a0 --- /dev/null +++ b/packages/web/server/api/routers/users.test.ts @@ -0,0 +1,57 @@ +import { CustomTestContext, defaultBeforeEach } from "@/lib/testUtils"; +import { expect, describe, test, beforeEach } from "vitest"; + +beforeEach(defaultBeforeEach(false)); + +describe("User Routes", () => { + test("create user", async ({ unauthedAPICaller }) => { + const user = await unauthedAPICaller.users.create({ + name: "Test User", + email: "test123@test.com", + password: "pass1234", + confirmPassword: "pass1234", + }); + + expect(user.name).toEqual("Test User"); + expect(user.email).toEqual("test123@test.com"); + }); + + test("first user is admin", async ({ + unauthedAPICaller, + }) => { + const user1 = await unauthedAPICaller.users.create({ + name: "Test User", + email: "test123@test.com", + password: "pass1234", + confirmPassword: "pass1234", + }); + + const user2 = await unauthedAPICaller.users.create({ + name: "Test User", + email: "test124@test.com", + password: "pass1234", + confirmPassword: "pass1234", + }); + + expect(user1.role).toEqual("admin"); + expect(user2.role).toEqual("user"); + }); + + test("unique emails", async ({ unauthedAPICaller }) => { + await unauthedAPICaller.users.create({ + name: "Test User", + email: "test123@test.com", + password: "pass1234", + confirmPassword: "pass1234", + }); + + await expect(() => + unauthedAPICaller.users.create({ + name: "Test User", + email: "test123@test.com", + password: "pass1234", + confirmPassword: "pass1234", + }), + ).rejects.toThrow(/Email is already taken/); + }); +}); diff --git a/packages/web/server/api/routers/users.ts b/packages/web/server/api/routers/users.ts index 3078a42a..3d5d982d 100644 --- a/packages/web/server/api/routers/users.ts +++ b/packages/web/server/api/routers/users.ts @@ -5,6 +5,7 @@ import { z } from "zod"; import { hashPassword } from "@/server/auth"; import { TRPCError } from "@trpc/server"; import { users } from "@hoarder/db/schema"; +import { count } from "drizzle-orm"; export const usersAppRouter = router({ create: publicProcedure @@ -13,9 +14,13 @@ export const usersAppRouter = router({ z.object({ name: z.string(), email: z.string(), + role: z.enum(["user", "admin"]).nullable(), }), ) .mutation(async ({ input, ctx }) => { + const [{ count: userCount }] = await ctx.db + .select({ count: count() }) + .from(users); try { const result = await ctx.db .insert(users) @@ -23,10 +28,12 @@ export const usersAppRouter = router({ name: input.name, email: input.email, password: await hashPassword(input.password), + role: userCount == 0 ? "admin" : "user", }) .returning({ name: users.name, email: users.email, + role: users.role, }); return result[0]; } catch (e) {