diff --git a/BE/src/auth/auth.controller.ts b/BE/src/auth/auth.controller.ts index d492b4b..2a52b48 100644 --- a/BE/src/auth/auth.controller.ts +++ b/BE/src/auth/auth.controller.ts @@ -4,6 +4,9 @@ import { AuthCredentialsDto } from "./dto/auth-credential.dto"; import { AccessTokenDto } from "./dto/auth-access-token.dto"; import { NoDuplicateLoginGuard } from "./guard/auth.user-guard"; import { CreateUserDto } from "./dto/users.dto"; +import { User } from "./users.entity"; +import { GetUser } from "./get-user.decorator"; +import { JwtAuthGuard } from "./guard/auth.jwt-guard"; @Controller("auth") export class AuthController { @@ -23,4 +26,11 @@ export class AuthController { ): Promise { return this.authService.signIn(authCredentialsDto); } + + @Post("/signout") + @UseGuards(JwtAuthGuard) + @HttpCode(204) + signOut(@GetUser() user: User): void { + this.authService.signOut(user); + } } diff --git a/BE/src/auth/auth.service.ts b/BE/src/auth/auth.service.ts index 4d83cf8..b85d7a7 100644 --- a/BE/src/auth/auth.service.ts +++ b/BE/src/auth/auth.service.ts @@ -37,4 +37,8 @@ export class AuthService { throw new NotFoundException("올바르지 않은 비밀번호입니다."); } } + + signOut(user: User): void { + // const hasRefreshToken = true; + } } diff --git a/BE/test/int/auth.signin.int-spec.ts b/BE/test/int/auth.signin.int-spec.ts index 04a2175..7ddee04 100644 --- a/BE/test/int/auth.signin.int-spec.ts +++ b/BE/test/int/auth.signin.int-spec.ts @@ -1,14 +1,12 @@ import { Test, TestingModule } from "@nestjs/testing"; import { INestApplication } from "@nestjs/common"; import * as request from "supertest"; -import { AppModule } from "../../src/app.module"; import { ValidationPipe } from "@nestjs/common"; import { AuthModule } from "src/auth/auth.module"; import { TypeOrmModule } from "@nestjs/typeorm"; import { typeORMTestConfig } from "src/configs/typeorm.test.config"; -import { typeORMConfig } from "src/configs/typeorm.config"; -describe("/auth/signin (e2e)", () => { +describe("[로그인] /auth/signin POST 통합 테스트", () => { let app: INestApplication; beforeAll(async () => { @@ -22,6 +20,7 @@ describe("/auth/signin (e2e)", () => { await app.init(); }); + afterAll(async () => { await app.close(); }); diff --git a/BE/test/int/auth.signout.int-spec.ts b/BE/test/int/auth.signout.int-spec.ts new file mode 100644 index 0000000..2836e07 --- /dev/null +++ b/BE/test/int/auth.signout.int-spec.ts @@ -0,0 +1,108 @@ +import { Test, TestingModule } from "@nestjs/testing"; +import { INestApplication } from "@nestjs/common"; +import * as request from "supertest"; +import { ValidationPipe } from "@nestjs/common"; +import { AuthModule } from "src/auth/auth.module"; +import { TypeOrmModule } from "@nestjs/typeorm"; +import { typeORMTestConfig } from "src/configs/typeorm.test.config"; +import { User } from "src/auth/users.entity"; +import { UsersRepository } from "src/auth/users.repository"; + +describe("[로그아웃] /auth/signout POST 통합 테스트", () => { + let app: INestApplication; + + beforeAll(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [TypeOrmModule.forRoot(typeORMTestConfig), AuthModule], + providers: [UsersRepository], + }).compile(); + + app = moduleFixture.createNestApplication(); + app.enableCors(); + app.useGlobalPipes(new ValidationPipe()); + + await app.init(); + + await request(app.getHttpServer()) + .post("/auth/signup") + .send({ + userId: "SignoutTestUserId", + password: "SignoutTestPassword", + email: "signouttestemail@naver.com", + nickname: "SignoutTestUser", + }) + .expect(201); + }); + + afterAll(async () => { + const usersRepository = new UsersRepository(); + + const testUser = await usersRepository.getUserByUserId("SignoutTestUserId"); + if (testUser) { + await User.remove(testUser); + } + + await app.close(); + }); + + it("올바른 토큰으로 요청 시 204 No Content 응답", async () => { + const signInResponse = await request(app.getHttpServer()) + .post("/auth/signin") + .send({ + userId: "SignoutTestUserId", + password: "SignoutTestPassword", + }) + .expect(201); + + expect(signInResponse.body).toHaveProperty("accessToken"); + + const { accessToken } = signInResponse.body; + + await request(app.getHttpServer()) + .post("/auth/signout") + .set("Authorization", `Bearer ${accessToken}`) + .expect(204); + }); + + it("유효하지 않은 액세스 토큰이 포함된 상태로 로그아웃 요청 시 401 Unauthorized 응답", async () => { + await request(app.getHttpServer()) + .post("/auth/signin") + .send({ + userId: "SignoutTestUserId", + password: "SignoutTestPassword", + }) + .expect(201); + + const invalidAccessToken = "1234"; + const postResponse = await request(app.getHttpServer()) + .post("/auth/signout") + .set("Authorization", `Bearer ${invalidAccessToken}`) + .expect(401); + + expect(postResponse.body).toEqual({ + error: "Unauthorized", + message: "유효하지 않은 토큰입니다.", + statusCode: 401, + }); + }); + + it("토큰이 존재하지 않은 상태로 로그아웃 요청 시 401 Unauthorized 응답", async () => { + await request(app.getHttpServer()) + .post("/auth/signin") + .send({ + userId: "SignoutTestUserId", + password: "SignoutTestPassword", + }) + .expect(201); + + const postResponse = await request(app.getHttpServer()) + .post("/auth/signout") + .expect(401); + + expect(postResponse.body).toEqual({ + error: "Unauthorized", + message: "비로그인 상태의 요청입니다.", + statusCode: 401, + }); + }); +});