diff --git a/backend/src/api/user.ts b/backend/src/api/user.ts index d4a332a2..bb367235 100644 --- a/backend/src/api/user.ts +++ b/backend/src/api/user.ts @@ -1,11 +1,12 @@ import { SelectUser, userTable } from '@/drizzle/schema'; import { zValidator } from '@hono/zod-validator'; -import { and, asc, eq, like } from 'drizzle-orm'; +import { and, asc, eq, inArray, like } from 'drizzle-orm'; import { drizzle } from 'drizzle-orm/d1'; import { Hono } from 'hono'; import { createUserBody, deleteUserParams, + deleteUsersBody, getUserParams, getUserResponse, getUsersQueryParams, @@ -151,6 +152,39 @@ app.post( }, ); +app.delete( + '/', + zValidator('json', deleteUsersBody, (result, ctx) => { + if (!result.success) { + return ctx.json( + { + message: 'Request Body Validation Error', + error: result.error, + }, + 400, + ); + } + }), + async (ctx) => { + const authed = await isLoggedIn(ctx); + if (!authed) { + return ctx.json( + { + message: 'Unauthorized', + }, + 401, + ); + } + + const { userIdList } = ctx.req.valid('json'); + + const db = drizzle(ctx.env.DB); + await db.delete(userTable).where(inArray(userTable.id, userIdList)); + + return ctx.body(null, 204); + }, +); + app.get( '/:userId', zValidator('param', getUserParams, (result, ctx) => { diff --git a/backend/test/api/books.test.ts b/backend/test/api/books.test.ts index 84d672aa..f65fae42 100644 --- a/backend/test/api/books.test.ts +++ b/backend/test/api/books.test.ts @@ -320,34 +320,37 @@ describe('DELETE /books', () => { bookFactory.resetSequenceNumber(); }); - loggedInTest('should delete books', async ({ currentUser, sessionToken }) => { - const before = await db.select({ count: count() }).from(bookTable); + loggedInTest( + 'should delete books successfully', + async ({ currentUser, sessionToken }) => { + const before = await db.select({ count: count() }).from(bookTable); - const bookIdList = [1, 2]; + const bookIdList = [1, 2]; - const response = await app.request( - '/books', - { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - Cookie: [ - `__Secure-user_id=${currentUser.id}`, - `__Secure-session_token=${sessionToken}`, - ].join('; '), + const response = await app.request( + '/books', + { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + Cookie: [ + `__Secure-user_id=${currentUser.id}`, + `__Secure-session_token=${sessionToken}`, + ].join('; '), + }, + body: JSON.stringify({ bookIdList: bookIdList }), }, - body: JSON.stringify({ bookIdList: bookIdList }), - }, - env, - ); + env, + ); - // ステータスコード - expect(response.status).toBe(204); + // ステータスコード + expect(response.status).toBe(204); - // データベースから書籍が削除されていることを確認する - const after = await db.select({ count: count() }).from(bookTable); - expect(after[0].count).toBe(before[0].count - bookIdList.length); - }); + // データベースから書籍が削除されていることを確認する + const after = await db.select({ count: count() }).from(bookTable); + expect(after[0].count).toBe(before[0].count - bookIdList.length); + }, + ); it('should return 400 when bookIdList is not array', async () => { const response = await app.request( diff --git a/backend/test/api/users.test.ts b/backend/test/api/users.test.ts index c8035932..6060e50c 100644 --- a/backend/test/api/users.test.ts +++ b/backend/test/api/users.test.ts @@ -84,7 +84,7 @@ describe('GET /users', () => { describe('POST /users', () => { const db = drizzle(env.DB); - afterAll(() => { + afterAll(async () => { userFactory.resetSequenceNumber(); }); @@ -327,3 +327,81 @@ describe('POST /users', () => { }, ); }); + +describe('DELETE /users', () => { + const db = drizzle(env.DB); + + beforeAll(async () => { + const users = userFactory.buildList(5); + await db.insert(userTable).values(users); + }); + + afterAll(async () => { + await db.delete(userTable); + userFactory.resetSequenceNumber(); + }); + + loggedInTest( + 'should delete users successfully', + async ({ currentUser, sessionToken }) => { + const before = await db.select({ count: count() }).from(userTable); + + const userIdList = [1, 2]; + + const response = await app.request( + '/users', + { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + Cookie: [ + `__Secure-user_id=${currentUser.id}`, + `__Secure-session_token=${sessionToken}`, + ].join('; '), + }, + body: JSON.stringify({ userIdList: userIdList }), + }, + env, + ); + + // ステータスコード + expect(response.status).toBe(204); + + // データベースからユーザが削除されていることを確認する + const after = await db.select({ count: count() }).from(userTable); + expect(after[0].count).toBe(before[0].count - userIdList.length); + }, + ); + + it('should return 400 when userIdList is not array', async () => { + const response = await app.request( + '/users', + { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ userIdList: 1 }), + }, + env, + ); + + expect(response.status).toBe(400); + }); + + it('should return 401 when not logged in', async () => { + const response = await app.request( + '/users', + { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ userIdList: [1] }), + }, + env, + ); + + expect(response.status).toBe(401); + }); +}); diff --git a/backend/test/context/login.ts b/backend/test/context/login.ts index 0e902c07..60485217 100644 --- a/backend/test/context/login.ts +++ b/backend/test/context/login.ts @@ -17,10 +17,11 @@ export const loggedInTest = test.extend({ const db = drizzle(env.DB); const sessionToken = crypto.randomUUID(); + const { id, ...rest } = user; const insertUser = await db .insert(userTable) .values({ - ...user, + ...rest, sessionToken, }) .returning();