diff --git a/BE/package-lock.json b/BE/package-lock.json index da3e556..766ab73 100644 --- a/BE/package-lock.json +++ b/BE/package-lock.json @@ -59,6 +59,7 @@ "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", + "typeorm-transactional-tests": "^2.0.0", "typescript": "^5.1.3" } }, @@ -9385,6 +9386,21 @@ } } }, + "node_modules/typeorm-transactional-tests": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/typeorm-transactional-tests/-/typeorm-transactional-tests-2.0.0.tgz", + "integrity": "sha512-xfeEvWjYjaVU0TM4q0CV0jPznBPCZoAubqLWvtKc+RxrvyCfjexWI23mq+QiUdOHQ8f5UTJktuYh9nyTYbvSWQ==", + "dev": true, + "engines": { + "node": ">=6.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + }, + "peerDependencies": { + "typeorm": "^0.3.6" + } + }, "node_modules/typeorm/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", diff --git a/BE/package.json b/BE/package.json index 647d01d..29a71bb 100644 --- a/BE/package.json +++ b/BE/package.json @@ -72,6 +72,7 @@ "ts-loader": "^9.4.3", "ts-node": "^10.9.1", "tsconfig-paths": "^4.2.0", + "typeorm-transactional-tests": "^2.0.0", "typescript": "^5.1.3" }, "jest": { diff --git a/BE/src/auth/auth.controller.ts b/BE/src/auth/auth.controller.ts index 77dcbed..6365a45 100644 --- a/BE/src/auth/auth.controller.ts +++ b/BE/src/auth/auth.controller.ts @@ -73,7 +73,7 @@ export class AuthController { @UseGuards(ExpiredOrNotGuard) @HttpCode(204) async signOut(@GetUser() user: User): Promise { - await this.authService.signOut(user); + await this.authService.signOut(user.userId); } @Post("/reissue") diff --git a/BE/src/auth/auth.service.ts b/BE/src/auth/auth.service.ts index 1583ed6..ec95597 100644 --- a/BE/src/auth/auth.service.ts +++ b/BE/src/auth/auth.service.ts @@ -10,6 +10,7 @@ import { Redis } from "ioredis"; import { InjectRedis } from "@liaoliaots/nestjs-redis"; import { Request } from "express"; import { providerEnum } from "src/utils/enum"; +import * as jwt from "jsonwebtoken"; @Injectable() export class AuthService { @@ -45,8 +46,8 @@ export class AuthService { return new LoginResponseDto(accessToken, nickname); } - async signOut(user: User): Promise { - await this.redisClient.del(user.userId); + async signOut(userId: string): Promise { + await this.redisClient.del(userId); } async reissueAccessToken(request: Request): Promise { @@ -121,4 +122,17 @@ export class AuthService { return accessToken; } + + extractJwtToken(accessToken: string) { + const jwtPayload = jwt.verify( + accessToken, + process.env.JWT_SECRET, + ) as jwt.JwtPayload; + + return jwtPayload; + } + + async getRefreshTokenFromRedis(key: string): Promise { + return this.redisClient.get(key); + } } diff --git a/BE/src/diaries/dto/diaries.sentiment.dto.ts b/BE/src/diaries/dto/diaries.sentiment.dto.ts index 37aceeb..46eb10c 100644 --- a/BE/src/diaries/dto/diaries.sentiment.dto.ts +++ b/BE/src/diaries/dto/diaries.sentiment.dto.ts @@ -5,4 +5,16 @@ export class SentimentDto { neutralRatio: number; negativeRatio: number; sentiment: sentimentStatus; + + constructor( + positiveRatio: number, + neutralRatio: number, + negativeRatio: number, + sentiment: sentimentStatus, + ) { + this.positiveRatio = positiveRatio; + this.neutralRatio = neutralRatio; + this.negativeRatio = negativeRatio; + this.sentiment = sentiment; + } } diff --git a/BE/src/purchase/purchase.repository.ts b/BE/src/purchase/purchase.repository.ts index 4749de1..55baf81 100644 --- a/BE/src/purchase/purchase.repository.ts +++ b/BE/src/purchase/purchase.repository.ts @@ -1,5 +1,4 @@ import { Purchase } from "./purchase.entity"; -import { PurchaseDesignDto } from "./dto/purchase.design.dto"; import { User } from "src/auth/users.entity"; import { designEnum, domainEnum } from "src/utils/enum"; @@ -9,7 +8,7 @@ export class PurchaseRepository { domain: domainEnum, design: designEnum, ): Promise { - const purchase = await Purchase.create(); + const purchase = Purchase.create(); purchase.domain = domain; purchase.design = design; @@ -18,7 +17,7 @@ export class PurchaseRepository { await purchase.save(); } - async getDesignPurchaseList(user: User) { - return Purchase.find({ where: { user: { userId: user.userId } } }); + async getDesignPurchaseList(userId: string): Promise { + return Purchase.find({ where: { user: { userId } } }); } } diff --git a/BE/src/purchase/purchase.service.ts b/BE/src/purchase/purchase.service.ts index 6898fda..71ad334 100644 --- a/BE/src/purchase/purchase.service.ts +++ b/BE/src/purchase/purchase.service.ts @@ -70,8 +70,9 @@ export class PurchaseService { } async getDesignPurchaseList(user: User): Promise { - const purchaseList = - await this.purchaseRepository.getDesignPurchaseList(user); + const purchaseList = await this.purchaseRepository.getDesignPurchaseList( + user.userId, + ); const groundPurchase = []; purchaseList.forEach((purchase) => { diff --git a/BE/src/shapes/shapes.repository.ts b/BE/src/shapes/shapes.repository.ts index 1b1dd94..5021515 100644 --- a/BE/src/shapes/shapes.repository.ts +++ b/BE/src/shapes/shapes.repository.ts @@ -32,14 +32,9 @@ export class ShapesRepository { return found; } - async getShapesByUser(user: User): Promise { - const shapeList: Shape[] = await Shape.find({ - where: { user: { userId: user.userId } }, + async getShapesByUser(userId: string): Promise { + return Shape.find({ + where: { user: { userId } }, }); - if (!shapeList) { - throw new NotFoundException("존재하지 않는 사용자입니다."); - } - - return shapeList; } } diff --git a/BE/src/shapes/shapes.service.ts b/BE/src/shapes/shapes.service.ts index 63a32ef..4840907 100644 --- a/BE/src/shapes/shapes.service.ts +++ b/BE/src/shapes/shapes.service.ts @@ -35,7 +35,7 @@ export class ShapesService { if (user.userId !== "commonUser") { shapeList = defaultShapeList.concat( - await this.shapesRepository.getShapesByUser(user), + await this.shapesRepository.getShapesByUser(user.userId), ); } else { shapeList = defaultShapeList; diff --git a/BE/src/tags/tags.repository.ts b/BE/src/tags/tags.repository.ts index 1f84a51..92a5ec1 100644 --- a/BE/src/tags/tags.repository.ts +++ b/BE/src/tags/tags.repository.ts @@ -3,7 +3,7 @@ import { NotFoundException } from "@nestjs/common"; export class TagsRepository { async createTag(name: string): Promise { - const tag = await Tag.create({ name }); + const tag = Tag.create({ name }); await tag.save(); return tag; diff --git a/BE/test/int/auth.service.int-spec.ts b/BE/test/int/auth.service.int-spec.ts index 94f55f5..f871f86 100644 --- a/BE/test/int/auth.service.int-spec.ts +++ b/BE/test/int/auth.service.int-spec.ts @@ -7,18 +7,24 @@ import { AuthModule } from "src/auth/auth.module"; import { AuthService } from "src/auth/auth.service"; import { UsersRepository } from "src/auth/users.repository"; import { JwtService } from "@nestjs/jwt"; -import { clearUserDb } from "src/utils/clearDb"; import { CreateUserDto } from "src/auth/dto/users.dto"; import { AuthCredentialsDto } from "src/auth/dto/auth-credential.dto"; import { Request } from "express"; import { LoginResponseDto } from "src/auth/dto/login-response.dto"; import { NotFoundException } from "@nestjs/common"; -import { providerEnum } from "src/utils/enum"; +import { DataSource } from "typeorm"; +import { TransactionalTestContext } from "typeorm-transactional-tests"; +import { premiumStatus, providerEnum } from "src/utils/enum"; +import * as bcrypt from "bcryptjs"; describe("AuthService 통합 테스트", () => { let authService: AuthService; - let usersRepository: UsersRepository; - let jwtService: JwtService; + let dataSource: DataSource; + let transactionalContext: TransactionalTestContext; + const request = { + ip: "111.111.111.111", + headers: { authorization: "" }, + } as Request; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ @@ -35,17 +41,39 @@ describe("AuthService 통합 테스트", () => { ], providers: [AuthService, UsersRepository, JwtService], }).compile(); - authService = await moduleFixture.get(AuthService); - usersRepository = await moduleFixture.get(UsersRepository); - jwtService = await moduleFixture.get(JwtService); + authService = moduleFixture.get(AuthService); + dataSource = moduleFixture.get(DataSource); + }); - await clearUserDb(moduleFixture, usersRepository); + beforeEach(async () => { + transactionalContext = new TransactionalTestContext(dataSource); + await transactionalContext.start(); }); afterEach(async () => { - await jest.clearAllMocks(); + await transactionalContext.finish(); + await jest.restoreAllMocks(); }); + async function performLoginTest( + result: LoginResponseDto, + expectedUserId: string, + expectedNickname: string, + expectedRequestIp: string = "111.111.111.111", + ) { + const { userId } = authService.extractJwtToken(result.accessToken); + const refreshToken = await authService.getRefreshTokenFromRedis(userId); + const { requestIp, accessToken } = + authService.extractJwtToken(refreshToken); + + expect(result).toBeInstanceOf(LoginResponseDto); + expect(result.nickname).toBe(expectedNickname); + + expect(userId).toBe(expectedUserId); + expect(requestIp).toBe(expectedRequestIp); + expect(accessToken).toEqual(result.accessToken); + } + describe("signUp 메서드", () => { it("메서드 정상 요청", async () => { const createUserDto = new CreateUserDto(); @@ -57,21 +85,31 @@ describe("AuthService 통합 테스트", () => { const result = await authService.signUp(createUserDto); expect(result).toBeInstanceOf(User); - expect(result.userId).toBe("ValidUser123"); + expect(result).toMatchObject({ + userId: "ValidUser123", + email: "valid.email@test.com", + provider: providerEnum.BYEOLSOOP, + credit: 0, + premium: premiumStatus.FALSE, + }); + + const isPasswordMatch = await bcrypt.compare( + createUserDto.password, + result.password, + ); + expect(isPasswordMatch).toEqual(true); }); }); - describe("signIn 메서드", () => { + describe("기존 유저 signIn 메서드", () => { it("메서드 정상 요청", async () => { const authCredentialsDto: AuthCredentialsDto = { - userId: "commonUser", - password: process.env.COMMON_USER_PASS, + userId: "oldUser", + password: "oldUser", }; - const request = { ip: "111.111.111.111" } as Request; const result = await authService.signIn(authCredentialsDto, request); - - expect(result).toBeInstanceOf(LoginResponseDto); + performLoginTest(result, authCredentialsDto.userId, "기존유저"); }); it("존재하지 않는 아이디로 요청 시 실패", async () => { @@ -79,12 +117,12 @@ describe("AuthService 통합 테스트", () => { userId: "notFoundUser", password: "notFoundUser", }; - const request = { ip: "111.111.111.111" } as Request; try { await authService.signIn(authCredentialsDto, request); } catch (error) { expect(error).toBeInstanceOf(NotFoundException); + expect(error.message).toBe("존재하지 않는 아이디입니다."); } }); @@ -93,61 +131,77 @@ describe("AuthService 통합 테스트", () => { userId: "commonUser", password: "commonUser", }; - const request = { ip: "111.111.111.111" } as Request; try { await authService.signIn(authCredentialsDto, request); } catch (error) { expect(error).toBeInstanceOf(NotFoundException); + expect(error.message).toBe("올바르지 않은 비밀번호입니다."); } }); }); - describe("signOut 메서드", () => { + describe("기존유저 signOut 메서드", () => { it("메서드 정상 요청", async () => { const authCredentialsDto: AuthCredentialsDto = { - userId: "commonUser", - password: process.env.COMMON_USER_PASS, + userId: "oldUser", + password: "oldUser", }; - const request = { ip: "111.111.111.111" } as Request; - - const user = await User.findOne({ where: { userId: "commonUser" } }); await authService.signIn(authCredentialsDto, request); - await authService.signOut(user); + { + const refreshToken = await authService.getRefreshTokenFromRedis( + authCredentialsDto.userId, + ); + expect(refreshToken).not.toBeNull(); + } + + await authService.signOut(authCredentialsDto.userId); + { + const refreshToken = await authService.getRefreshTokenFromRedis( + authCredentialsDto.userId, + ); + expect(refreshToken).toBeNull(); + } }); }); - describe("reissueAccessToken 메서드", () => { + describe("기존유저 reissueAccessToken 메서드", () => { it("메서드 정상 요청", async () => { const authCredentialsDto: AuthCredentialsDto = { - userId: "commonUser", - password: process.env.COMMON_USER_PASS, + userId: "oldUser", + password: "oldUser", }; - const user = await User.findOne({ where: { userId: "commonUser" } }); - const request = { ip: "111.111.111.111" } as Request; - await authService.signIn(authCredentialsDto, request); - const result = await authService.reissueAccessToken(user, request); + const { accessToken } = await authService.signIn( + authCredentialsDto, + request, + ); + + request.headers.authorization = `Bearer ${accessToken}`; - expect(result).toBeInstanceOf(LoginResponseDto); + const result = await authService.reissueAccessToken(request); + performLoginTest(result, authCredentialsDto.userId, "기존유저"); }); }); describe("naverSignIn 메서드", () => { it("메서드 정상 요청", async () => { - const user = new User(); - user.email = "test@naver.com"; - user.userId = "test*naver"; - user.nickname = "test"; - user.password = "test"; - user.provider = providerEnum.NAVER; - - const request = { ip: "111.111.111.111" } as Request; + const userId = "naverUser"; + const user = await User.findOne({ where: { userId } }); const result = await authService.naverSignIn(user, request); + performLoginTest(result, userId, "네이버유저"); + }); + }); + + describe("kakaoSignIn 메서드", () => { + it("메서드 정상 요청", async () => { + const userId = "kakaoUser"; + const user = await User.findOne({ where: { userId } }); - expect(result).toBeInstanceOf(LoginResponseDto); + const result = await authService.naverSignIn(user, request); + performLoginTest(result, userId, "카카오유저"); }); }); }); diff --git a/BE/test/int/diaries.repository.int-spec.ts b/BE/test/int/diaries.repository.int-spec.ts index e237b60..91226d7 100644 --- a/BE/test/int/diaries.repository.int-spec.ts +++ b/BE/test/int/diaries.repository.int-spec.ts @@ -1,10 +1,8 @@ -import { RedisModule } from "@liaoliaots/nestjs-redis"; import { Test, TestingModule } from "@nestjs/testing"; import { TypeOrmModule } from "@nestjs/typeorm"; import { User } from "src/auth/users.entity"; import { typeORMTestConfig } from "src/configs/typeorm.test.config"; import { Diary } from "src/diaries/diaries.entity"; -import { DiariesModule } from "src/diaries/diaries.module"; import { DiariesRepository } from "src/diaries/diaries.repository"; import { CreateDiaryDto, UpdateDiaryDto } from "src/diaries/dto/diaries.dto"; import { ReadDiaryDto } from "src/diaries/dto/diaries.read.dto"; @@ -13,132 +11,187 @@ import { Shape } from "src/shapes/shapes.entity"; import { Tag } from "src/tags/tags.entity"; import { sentimentStatus } from "src/utils/enum"; import { NotFoundException } from "@nestjs/common"; +import { DataSource } from "typeorm"; +import { TransactionalTestContext } from "typeorm-transactional-tests"; describe("DiariesRepository 통합 테스트", () => { let diariesRepository: DiariesRepository; - let user: User; let createdDiary: Diary; + let dataSource: DataSource; + let transactionalContext: TransactionalTestContext; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [ - TypeOrmModule.forRoot(typeORMTestConfig), - DiariesModule, - RedisModule.forRoot({ - readyLog: true, - config: { - host: "223.130.129.145", - port: 6379, - }, - }), - ], + imports: [TypeOrmModule.forRoot(typeORMTestConfig)], providers: [DiariesRepository], }).compile(); - diariesRepository = - await moduleFixture.get(DiariesRepository); + diariesRepository = moduleFixture.get(DiariesRepository); + dataSource = moduleFixture.get(DataSource); }); beforeEach(async () => { - user = await User.findOne({ where: { userId: "commonUser" } }); - const shape = await Shape.findOne({ - where: { user: { userId: "commonUser" } }, - }); - const tags = [new Tag(), new Tag()]; - - const createDiaryDto = new CreateDiaryDto(); - createDiaryDto.title = "Test Title"; - createDiaryDto.content = "Test Content"; - createDiaryDto.point = "1,1,1"; - (createDiaryDto.date as any) = "2023-11-29"; - createDiaryDto.tags = ["tag1", "tag2"]; - createDiaryDto.shapeUuid = shape.uuid; - - const sentimentDto = new SentimentDto(); - sentimentDto.positiveRatio = 0; - sentimentDto.negativeRatio = 0; - sentimentDto.neutralRatio = 100; - sentimentDto.sentiment = sentimentStatus.NEUTRAL; - - createdDiary = await diariesRepository.createDiary( - createDiaryDto, - createDiaryDto.content, - tags, - user, - shape, - sentimentDto, - ); + transactionalContext = new TransactionalTestContext(dataSource); + await transactionalContext.start(); + }); + + afterEach(async () => { + await transactionalContext.finish(); }); describe("createDiary 통합 테스트", () => { it("메서드 정상 요청", async () => { + const newUser = await User.findOne({ where: { userId: "newUser" } }); + const shape = await Shape.findOne({ where: { id: 2 } }); + const tags = [await Tag.findOne({ where: { name: "Naver" } })]; + + const createDiaryDto = new CreateDiaryDto(); + createDiaryDto.title = "Test Title"; + createDiaryDto.content = "Test Content"; + createDiaryDto.point = "1,1,1"; + createDiaryDto.date = new Date("2023-11-29"); + createDiaryDto.tags = ["Naver"]; + createDiaryDto.shapeUuid = shape.uuid; + + const sentimentDto = new SentimentDto(0, 100, 0, sentimentStatus.NEUTRAL); + + createdDiary = await diariesRepository.createDiary( + createDiaryDto, + createDiaryDto.content, + tags, + newUser, + shape, + sentimentDto, + ); + expect(createdDiary).toBeInstanceOf(Diary); - expect(createdDiary.title).toBe("Test Title"); + expect(createdDiary).toMatchObject({ + title: "Test Title", + positiveRatio: 0, + negativeRatio: 0, + neutralRatio: 100, + sentiment: "neutral", + date: new Date("2023-11-29"), + tags: [{ id: 13, name: "Naver" }], + }); + expect(createdDiary.shape.id).toBe(2); + expect(createdDiary.user.id).toBe(3); }); }); describe("readDiary 통합 테스트", () => { it("메서드 정상 요청", async () => { const readDiaryDto = new ReadDiaryDto(); - readDiaryDto.uuid = createdDiary.uuid; + readDiaryDto.uuid = "0c388311-61c4-41ad-8d4d-b7b1ce075ef2"; const result = await diariesRepository.readDiary(readDiaryDto); expect(result).toBeInstanceOf(Diary); - expect(result.title).toBe("Test Title"); + expect(result).toMatchObject({ + title: "부정 1", + uuid: "0c388311-61c4-41ad-8d4d-b7b1ce075ef2", + positiveRatio: 0.00949954, + negativeRatio: 99.978, + neutralRatio: 0.0124967, + sentiment: "negative", + date: new Date("2023-03-02T15:00:00.000Z"), + tags: [ + { id: 1, name: "Old" }, + { id: 5, name: "9월" }, + { id: 8, name: "중립" }, + ], + }); }); }); describe("readDiariesByUser 통합 테스트", () => { it("메서드 정상 요청", async () => { - const result = await diariesRepository.readDiariesByUser(user); - - expect(result[0]).toBeInstanceOf(Diary); + const naverUser = await User.findOne({ where: { userId: "naverUser" } }); + const diaries = await diariesRepository.readDiariesByUser(naverUser); + + expect(diaries[0]).toBeInstanceOf(Diary); + expect(diaries).toHaveLength(3); + + const expectedTitles = [ + "긍정 네이버 1", + "긍정 네이버 2", + "긍정 네이버 3", + ]; + expect(diaries.map((diary) => diary.title)).toEqual( + expect.arrayContaining(expectedTitles), + ); }); }); describe("updateDiary 통합 테스트", () => { it("메서드 정상 요청", async () => { - const shape = await Shape.findOne({ - where: { user: { userId: "commonUser" } }, - }); - const tags = [new Tag(), new Tag()]; + const oldUser = await User.findOne({ where: { userId: "oldUser" } }); + const shape = await Shape.findOne({ where: { id: 3 } }); + const tags = [await Tag.findOne({ where: { name: "Naver" } })]; const updateDiaryDto = new UpdateDiaryDto(); updateDiaryDto.title = "Test Title"; updateDiaryDto.content = "Updated Content"; updateDiaryDto.point = "1,1,1"; - (updateDiaryDto.date as any) = "2023-11-29"; - updateDiaryDto.tags = ["tag1", "tag2"]; + updateDiaryDto.date = new Date("2023-11-29"); + updateDiaryDto.tags = ["Naver"]; updateDiaryDto.shapeUuid = shape.uuid; - updateDiaryDto.uuid = createdDiary.uuid; + updateDiaryDto.uuid = "0c388311-61c4-41ad-8d4d-b7b1ce075ef2"; - const sentimentDto = new SentimentDto(); - sentimentDto.positiveRatio = 0; - sentimentDto.negativeRatio = 0; - sentimentDto.neutralRatio = 100; - sentimentDto.sentiment = sentimentStatus.NEUTRAL; + const sentimentDto = new SentimentDto( + 80, + 20, + 0, + sentimentStatus.POSITIVE, + ); const result = await diariesRepository.updateDiary( updateDiaryDto, updateDiaryDto.content, tags, - user, + oldUser, shape, sentimentDto, ); expect(result).toBeInstanceOf(Diary); - expect(result.content).toBe("Updated Content"); + expect(result).toMatchObject({ + title: "Test Title", + content: "Updated Content", + uuid: "0c388311-61c4-41ad-8d4d-b7b1ce075ef2", + positiveRatio: 80, + neutralRatio: 20, + negativeRatio: 0, + sentiment: "positive", + date: new Date("2023-11-29"), + tags: [{ id: 13, name: "Naver" }], + }); }); }); describe("getDiaryByUuid 통합 테스트", () => { it("메서드 정상 요청", async () => { - const result = await diariesRepository.getDiaryByUuid(createdDiary.uuid); + const uuid = "c8cd1721-d553-430d-b3cd-7246967445d4"; + const result = await diariesRepository.getDiaryByUuid(uuid); expect(result).toBeInstanceOf(Diary); - expect(result.title).toBe("Test Title"); + expect(result).toMatchObject({ + id: 5, + uuid: "c8cd1721-d553-430d-b3cd-7246967445d4", + title: "긍정 5", + positiveRatio: 99.9786, + negativeRatio: 0.0180166, + neutralRatio: 0.00338173, + sentiment: "positive", + date: new Date("2023-01-02T15:00:00.000Z"), + }); + expect(result.user.id).toBe(2); + expect(result.shape.id).toBe(2); + expect(result.tags).toEqual([ + { id: 1, name: "Old" }, + { id: 3, name: "긍정" }, + { id: 7, name: "1월" }, + ]); }); it("존재하지 않는 일기 uuid에 대한 요청", async () => { diff --git a/BE/test/int/diaries.service.int-spec.ts b/BE/test/int/diaries.service.int-spec.ts index 35939ee..a61afea 100644 --- a/BE/test/int/diaries.service.int-spec.ts +++ b/BE/test/int/diaries.service.int-spec.ts @@ -1,10 +1,8 @@ -import { RedisModule } from "@liaoliaots/nestjs-redis"; import { Test, TestingModule } from "@nestjs/testing"; -import { TypeOrmModule, getRepositoryToken } from "@nestjs/typeorm"; +import { TypeOrmModule } from "@nestjs/typeorm"; import { User } from "src/auth/users.entity"; import { typeORMTestConfig } from "src/configs/typeorm.test.config"; import { Diary } from "src/diaries/diaries.entity"; -import { DiariesModule } from "src/diaries/diaries.module"; import { DiariesRepository } from "src/diaries/diaries.repository"; import { CreateDiaryDto, @@ -22,101 +20,106 @@ import { HttpModule, HttpService } from "@nestjs/axios"; import { ReadDiaryDto } from "src/diaries/dto/diaries.read.dto"; import { createCipheriv, createDecipheriv } from "crypto"; import { of } from "rxjs"; +import { DataSource } from "typeorm"; +import { TransactionalTestContext } from "typeorm-transactional-tests"; +import { UsersRepository } from "src/auth/users.repository"; +import { NotFoundException } from "@nestjs/common"; describe("DiariesService 통합 테스트", () => { let diariesService: DiariesService; - let tagsRepository: TagsRepository; + let diariesRepository: DiariesRepository; let sentimentDto: SentimentDto; let httpService: HttpService; - let user: User; - let shape: Shape; - let createResult: Diary; - - jest.mock("rxjs", () => ({ - lastValueFrom: jest.fn().mockResolvedValue({ - data: { - document: { - confidence: { - positive: 0.1, - neutral: 0.2, - negative: 0.7, - }, - sentiment: "negative", - }, - }, - }), - })); + let dataSource: DataSource; + let transactionalContext: TransactionalTestContext; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [ - TypeOrmModule.forRoot(typeORMTestConfig), - HttpModule, - DiariesModule, - RedisModule.forRoot({ - readyLog: true, - config: { - host: "223.130.129.145", - port: 6379, - }, - }), - ], + imports: [TypeOrmModule.forRoot(typeORMTestConfig), HttpModule], providers: [ DiariesService, DiariesRepository, TagsRepository, ShapesRepository, - { - provide: HttpService, - useValue: { - post: jest.fn(), - }, - }, + UsersRepository, ], }).compile(); - diariesService = await moduleFixture.get(DiariesService); - tagsRepository = await moduleFixture.get(TagsRepository); - httpService = await moduleFixture.get(HttpService); - }); + diariesService = moduleFixture.get(DiariesService); + diariesRepository = moduleFixture.get(DiariesRepository); + httpService = moduleFixture.get(HttpService); + dataSource = moduleFixture.get(DataSource); - beforeEach(async () => { - sentimentDto = new SentimentDto(); - sentimentDto.positiveRatio = 0; - sentimentDto.negativeRatio = 0; - sentimentDto.neutralRatio = 100; - sentimentDto.sentiment = sentimentStatus.NEUTRAL; - user = await User.findOne({ where: { userId: "commonUser" } }); - shape = await Shape.findOne({ - where: { user: { userId: "commonUser" } }, - }); + sentimentDto = new SentimentDto(0, 100, 0, sentimentStatus.NEUTRAL); - jest - .spyOn(diariesService, "getTags") - .mockResolvedValueOnce([new Tag(), new Tag()]); jest .spyOn(diariesService, "getSentiment") .mockResolvedValueOnce(sentimentDto); - const createDiaryDto = new CreateDiaryDto(); - createDiaryDto.title = "Test Title"; - createDiaryDto.content = "Test Content"; - createDiaryDto.point = "1,1,1"; - (createDiaryDto.date as any) = "2023-11-29"; - createDiaryDto.tags = ["tag1", "tag2"]; - createDiaryDto.shapeUuid = shape.uuid; + const mockData: any = { + status: 200, + statusText: "OK", + headers: {}, + config: {}, + data: { + document: { + confidence: { + positive: 10.0, + neutral: 20.0, + negative: 70.0, + }, + sentiment: "negative", + }, + }, + }; + jest.spyOn(httpService, "post").mockImplementation(() => of(mockData)); + }); - createResult = await diariesService.writeDiary(createDiaryDto, user); + beforeEach(async () => { + transactionalContext = new TransactionalTestContext(dataSource); + await transactionalContext.start(); }); afterEach(async () => { - await jest.clearAllMocks(); + await transactionalContext.finish(); + }); + + afterAll(async () => { + await jest.restoreAllMocks(); }); describe("writeDiary 통합 테스트", () => { - it("메서드 정상 요청", async () => { - expect(createResult).toBeInstanceOf(Diary); - expect(createResult.title).toBe("Test Title"); + it("신규 유저 메서드 정상 요청", async () => { + const newUser = await User.findOne({ where: { userId: "newUser" } }); + const shape = await Shape.findOne({ where: { id: 2 } }); + + const createDiaryDto = new CreateDiaryDto(); + createDiaryDto.title = "Test Title"; + createDiaryDto.content = "Test Content"; + createDiaryDto.point = "1,1,1"; + createDiaryDto.date = new Date("2023-11-29"); + createDiaryDto.tags = ["Naver"]; + createDiaryDto.shapeUuid = shape.uuid; + + const result = await diariesService.writeDiary(createDiaryDto, newUser); + expect(result).toBeInstanceOf(Diary); + expect(result).toMatchObject({ + title: "Test Title", + positiveRatio: 0, + negativeRatio: 0, + neutralRatio: 100, + sentiment: "neutral", + date: new Date("2023-11-29"), + point: "1,1,1", + user: { id: 3 }, + shape: { id: 2 }, + deletedDate: null, + }); + + const decryptedContent = await diariesService.getDecryptedContent( + result.content, + ); + expect(decryptedContent).toBe("Test Content"); }); }); @@ -157,101 +160,122 @@ describe("DiariesService 통합 테스트", () => { describe("readDiary 통합 테스트", () => { it("메서드 정상 요청", async () => { const readDiaryDto = new ReadDiaryDto(); - readDiaryDto.uuid = createResult.uuid; - - jest - .spyOn(diariesService, "getDecryptedContent") - .mockResolvedValue("decrypted"); + readDiaryDto.uuid = "0c388311-61c4-41ad-8d4d-b7b1ce075ef2"; const result = await diariesService.readDiary(readDiaryDto); expect(result).toBeInstanceOf(Diary); - expect(result.title).toBe("Test Title"); + expect(result).toMatchObject({ + title: "부정 1", + content: "너무 싫어", + uuid: "0c388311-61c4-41ad-8d4d-b7b1ce075ef2", + positiveRatio: 0.00949954, + negativeRatio: 99.978, + neutralRatio: 0.0124967, + sentiment: "negative", + date: new Date("2023-03-03"), + tags: [ + { id: 1, name: "Old" }, + { id: 5, name: "9월" }, + { id: 8, name: "중립" }, + ], + }); }); }); describe("readDiariesByUser 통합 테스트", () => { it("메서드 정상 요청", async () => { - jest - .spyOn(diariesService, "getDecryptedContent") - .mockResolvedValue("decrypted2323"); - - const result = await diariesService.readDiariesByUser(user); - - expect(result[0]).toBeInstanceOf(Diary); + const naverUser = await User.findOne({ where: { userId: "naverUser" } }); + const diaries = await diariesService.readDiariesByUser(naverUser); + + const expectedValues = [ + { title: "긍정 네이버 1", content: "네이버는 너무 좋아" }, + { title: "긍정 네이버 2", content: "네이버는 너무 행복해" }, + { title: "긍정 네이버 3", content: "행복해" }, + ]; + + expect( + diaries.map((diary) => ({ + title: diary.title, + content: diary.content, + })), + ).toEqual(expectedValues); }); }); describe("modifyDiary 통합 테스트", () => { it("메서드 정상 요청", async () => { + const oldUser = await User.findOne({ where: { userId: "oldUser" } }); + const shape = await Shape.findOne({ where: { id: 3 } }); + const updateDiaryDto = new UpdateDiaryDto(); updateDiaryDto.title = "Test Title"; - updateDiaryDto.content = "Test Content"; + updateDiaryDto.content = "Updated Content"; updateDiaryDto.point = "3,3,3"; - (updateDiaryDto.date as any) = "2023-11-29"; - updateDiaryDto.tags = ["tag1", "tag2"]; + updateDiaryDto.date = new Date("2023-11-29"); + updateDiaryDto.tags = ["Naver"]; updateDiaryDto.shapeUuid = shape.uuid; - updateDiaryDto.uuid = createResult.uuid; + updateDiaryDto.uuid = "0c388311-61c4-41ad-8d4d-b7b1ce075ef2"; - const result = await diariesService.modifyDiary(updateDiaryDto, user); + const result = await diariesService.modifyDiary(updateDiaryDto, oldUser); expect(result).toBeInstanceOf(Diary); - expect(result.point).toBe("3,3,3"); + expect(result).toMatchObject({ + title: "Test Title", + uuid: "0c388311-61c4-41ad-8d4d-b7b1ce075ef2", + positiveRatio: 10, + neutralRatio: 20, + negativeRatio: 70, + sentiment: "negative", + date: new Date("2023-11-29"), + tags: [{ id: 13, name: "Naver" }], + point: "3,3,3", + }); + + const decryptedContent = await diariesService.getDecryptedContent( + result.content, + ); + expect(decryptedContent).toBe("Updated Content"); }); }); describe("deleteDiary 통합 테스트", () => { it("메서드 정상 요청", async () => { const deleteDiaryDto = new DeleteDiaryDto(); - deleteDiaryDto.uuid = createResult.uuid; + deleteDiaryDto.uuid = "0c388311-61c4-41ad-8d4d-b7b1ce075ef2"; await diariesService.deleteDiary(deleteDiaryDto); + + try { + await diariesRepository.getDiaryByUuid(deleteDiaryDto.uuid); + } catch (error) { + expect(error).toBeInstanceOf(NotFoundException); + expect(error.message).toBe("존재하지 않는 일기입니다."); + } }); }); describe("getTags 통합 테스트", () => { it("메서드 정상 요청", async () => { - const tagNames = ["tag", "tag"]; - const tag = new Tag(); - tag.name = "tag"; - - jest.spyOn(tagsRepository, "getTagByName").mockResolvedValue(tag); - jest.spyOn(tagsRepository, "createTag").mockResolvedValue(tag); + const tagNames = ["3월", "5월", "7월", "13월"]; const result = await diariesService.getTags(tagNames); expect(result[0]).toBeInstanceOf(Tag); - expect(result[0].name).toBe("tag"); + expect(result.map((tag) => tag.name)).toEqual( + expect.arrayContaining(tagNames), + ); }); }); describe("getSentimentByContent 통합 테스트", () => { it("메서드 정상 요청", async () => { - const result: any = { - status: 200, - statusText: "OK", - headers: {}, - config: {}, - data: { - document: { - confidence: { - positive: 0.1, - neutral: 0.2, - negative: 0.7, - }, - sentiment: "negative", - }, - }, - }; - - jest.spyOn(httpService, "post").mockImplementation(() => of(result)); - const sentimentDto = await diariesService.getSentimentByContent("test"); expect(sentimentDto).toEqual({ - positiveRatio: 0.1, - neutralRatio: 0.2, - negativeRatio: 0.7, + positiveRatio: 10, + neutralRatio: 20, + negativeRatio: 70, sentiment: "negative", }); }); @@ -259,59 +283,21 @@ describe("DiariesService 통합 테스트", () => { describe("getSentiment 통합 테스트", () => { it("1000자 이하 메서드 정상 요청", async () => { - const result: any = { - status: 200, - statusText: "OK", - headers: {}, - config: {}, - data: { - document: { - confidence: { - positive: 0.1, - neutral: 0.2, - negative: 0.7, - }, - sentiment: "negative", - }, - }, - }; - - jest.spyOn(httpService, "post").mockImplementation(() => of(result)); - const sentimentDto = await diariesService.getSentiment("test"); expect(sentimentDto).toEqual({ - positiveRatio: 0.1, - neutralRatio: 0.2, - negativeRatio: 0.7, + positiveRatio: 10, + neutralRatio: 20, + negativeRatio: 70, sentiment: "negative", }); }); it("1000자 초과 메서드 정상 요청", async () => { - const result: any = { - status: 200, - statusText: "OK", - headers: {}, - config: {}, - data: { - document: { - confidence: { - positive: 0.1, - neutral: 0.2, - negative: 0.7, - }, - sentiment: "negative", - }, - }, - }; - - jest.spyOn(httpService, "post").mockImplementation(() => of(result)); - const sentimentDto = await diariesService.getSentiment("a".repeat(1500)); - expect(sentimentDto.negativeRatio).toBeGreaterThan(0.69); - expect(sentimentDto.negativeRatio).toBeLessThan(0.71); + expect(sentimentDto.negativeRatio).toBeGreaterThan(69); + expect(sentimentDto.negativeRatio).toBeLessThan(71); expect(sentimentDto.sentiment).toBe("negative"); }); }); diff --git a/BE/test/int/purchase.repository.int-spec.ts b/BE/test/int/purchase.repository.int-spec.ts index f23ee55..3204705 100644 --- a/BE/test/int/purchase.repository.int-spec.ts +++ b/BE/test/int/purchase.repository.int-spec.ts @@ -4,29 +4,71 @@ import { User } from "src/auth/users.entity"; import { typeORMTestConfig } from "src/configs/typeorm.test.config"; import { Purchase } from "src/purchase/purchase.entity"; import { PurchaseRepository } from "src/purchase/purchase.repository"; +import { designEnum, domainEnum } from "src/utils/enum"; +import { DataSource } from "typeorm"; +import { TransactionalTestContext } from "typeorm-transactional-tests"; describe("PurchaseRepository 통합 테스트", () => { let purchaseRepository: PurchaseRepository; + let dataSource: DataSource; + let transactionalContext: TransactionalTestContext; beforeAll(async () => { - const module: TestingModule = await Test.createTestingModule({ + const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [TypeOrmModule.forRoot(typeORMTestConfig)], providers: [PurchaseRepository], }).compile(); - purchaseRepository = module.get(PurchaseRepository); + purchaseRepository = + moduleFixture.get(PurchaseRepository); + dataSource = moduleFixture.get(DataSource); + }); + + beforeEach(async () => { + transactionalContext = new TransactionalTestContext(dataSource); + await transactionalContext.start(); + }); + + afterEach(async () => { + await transactionalContext.finish(); + }); + + describe("purchaseDesign 메서드", () => { + it("신규 유저 메서드 정상 요청", async () => { + const userId = "newUser"; + const newUser = await User.findOne({ where: { userId } }); + + await purchaseRepository.purchaseDesign( + newUser, + domainEnum.GROUND, + designEnum.GROUND_2D, + ); + const result = await Purchase.find({ where: { user: { userId } } }); + expect(result).toHaveLength(1); + expect(result[0]).toMatchObject({ + domain: "ground", + design: "ground_2d", + user: { id: 3 }, + }); + }); }); describe("getDesignPurchaseList 메서드", () => { - it("메서드 정상 요청", async () => { - const user = await User.findOne({ where: { userId: "commonUser" } }); + it("기존 유저 메서드 정상 요청", async () => { + const result = await purchaseRepository.getDesignPurchaseList("oldUser"); - const purchaseList = await Purchase.find({ - where: { user: { userId: user.userId } }, + expect(result).toHaveLength(1); + expect(result[0]).toMatchObject({ + id: 1, + domain: "ground", + design: "ground_2d", + user: { id: 2 }, }); - const result = await purchaseRepository.getDesignPurchaseList(user); + }); - expect(result).toStrictEqual(purchaseList); + it("신규 유저 메서드 정상 요청", async () => { + const result = await purchaseRepository.getDesignPurchaseList("newUser"); + expect(result).toEqual([]); }); }); }); diff --git a/BE/test/int/purchase.service.int-spec.ts b/BE/test/int/purchase.service.int-spec.ts index 7e7d2af..216cdec 100644 --- a/BE/test/int/purchase.service.int-spec.ts +++ b/BE/test/int/purchase.service.int-spec.ts @@ -4,23 +4,17 @@ import { User } from "src/auth/users.entity"; import { UsersRepository } from "src/auth/users.repository"; import { typeORMTestConfig } from "src/configs/typeorm.test.config"; import { PurchaseDesignDto } from "src/purchase/dto/purchase.design.dto"; -import { Purchase } from "src/purchase/purchase.entity"; import { PurchaseRepository } from "src/purchase/purchase.repository"; import { PurchaseService } from "src/purchase/purchase.service"; -import { premiumStatus } from "src/utils/enum"; -import { DataSource, QueryRunner } from "typeorm"; +import { designEnum, domainEnum, premiumStatus } from "src/utils/enum"; +import { DataSource } from "typeorm"; +import { TransactionalTestContext } from "typeorm-transactional-tests"; describe("PurchaseService 통합 테스트", () => { let purchaseService: PurchaseService; + let purchaseDesignDto: PurchaseDesignDto; let dataSource: DataSource; - let queryRunner: QueryRunner; - - const userMockData = { - userId: "PurchaseServiceTest", - password: "PurchaseServiceTest", - nickname: "PurchaseServiceTest", - email: "test@test.com", - }; + let transactionalContext: TransactionalTestContext; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ @@ -30,121 +24,104 @@ describe("PurchaseService 통합 테스트", () => { purchaseService = moduleFixture.get(PurchaseService); dataSource = moduleFixture.get(DataSource); - queryRunner = dataSource.createQueryRunner(); - await queryRunner.connect(); + + purchaseDesignDto = new PurchaseDesignDto(); + purchaseDesignDto.domain = domainEnum.GROUND; + purchaseDesignDto.design = designEnum.GROUND_2D; }); beforeEach(async () => { - await queryRunner.startTransaction(); + transactionalContext = new TransactionalTestContext(dataSource); + await transactionalContext.start(); }); afterEach(async () => { - await queryRunner.rollbackTransaction(); - jest.restoreAllMocks(); - }); - - afterAll(async () => { - await queryRunner.release(); + await transactionalContext.finish(); }); - // // 별가루가 부족한 경우 테스트 - // // 이미 존재하는 디자인에 대한 경우 테스트 - // // 위 두 테스트는 테스트 DB 데이터 삭제 오류의 문제로 테스트 DB 독립화 이후 구현 예정 - describe("purchaseDesign & getDesignPurchaseList 메서드", () => { - it("메서드 정상 요청", async () => { - // 테스트 DB 독립 시 수정 필요 - const user = User.create({ - ...userMockData, - premium: premiumStatus.FALSE, - credit: 500, - }); - await queryRunner.manager.save(user); - - jest.spyOn(user, "save").mockImplementation(async () => { - return queryRunner.manager.save(user); - }); - - const purchase = new Purchase(); - jest.spyOn(Purchase, "create").mockReturnValue(purchase); - jest.spyOn(purchase, "save").mockImplementation(async () => { - return queryRunner.manager.save(purchase); - }); - jest.spyOn(Purchase, "find").mockImplementation(async (options) => { - return queryRunner.manager.find(Purchase, options); - }); - jest.spyOn(Purchase, "findOne").mockImplementation(async (options) => { - return queryRunner.manager.findOne(Purchase, options); - }); - - const purchaseDesignDto = new PurchaseDesignDto(); - purchaseDesignDto.domain = "GROUND"; - purchaseDesignDto.design = "GROUND_GREEN"; + it("네이버유저 메서드 정상 요청", async () => { + const naverUser = await User.findOne({ where: { userId: "naverUser" } }); + { + const result = await purchaseService.getDesignPurchaseList(naverUser); + expect(result).toStrictEqual({ + ground: [], + }); + } const { credit } = await purchaseService.purchaseDesign( - user, + naverUser, purchaseDesignDto, ); expect(credit).toBe(0); - const result = await purchaseService.getDesignPurchaseList(user); - expect(result).toStrictEqual({ - ground: ["#254117"], - sky: [], - }); + { + const result = await purchaseService.getDesignPurchaseList(naverUser); + expect(result).toStrictEqual({ + ground: ["ground_2d"], + }); + } }); - }); - describe("purchasePremium 메서드", () => { - it("프리미엄 구매 성공", async () => { - const user = User.create({ - ...userMockData, - premium: premiumStatus.FALSE, - credit: 500, + it("크레딧이 없는 신규유저 요청", async () => { + const newUser = await User.findOne({ where: { userId: "newUser" } }); + + const result = await purchaseService.getDesignPurchaseList(newUser); + expect(result).toStrictEqual({ + ground: [], }); - await queryRunner.manager.save(user); + await expect( + purchaseService.purchaseDesign(newUser, purchaseDesignDto), + ).rejects.toThrow(`보유한 별가루가 부족합니다. 현재 0 별가루`); + }); - jest.spyOn(user, "save").mockImplementation(async () => { - return queryRunner.manager.save(user); - }); + it("이미 ground_2d 디자인을 구매한 기존유저 요청", async () => { + const oldUser = await User.findOne({ where: { userId: "oldUser" } }); - const result = await purchaseService.purchasePremium(user); + const result = await purchaseService.getDesignPurchaseList(oldUser); + expect(result).toStrictEqual({ + ground: ["ground_2d"], + }); - expect(result.credit).toBe(150); - expect(user.premium).toBe(premiumStatus.TRUE); + await expect( + purchaseService.purchaseDesign(oldUser, purchaseDesignDto), + ).rejects.toThrow(`이미 구매한 디자인입니다.`); }); + }); - it("크레딧 부족으로 구매 실패", async () => { - const user = User.create({ - ...userMockData, - premium: premiumStatus.FALSE, - credit: 300, - }); - await queryRunner.manager.save(user); + describe("purchasePremium & getPremiumStatus 메서드", () => { + it("네이버유저 프리미엄 구매 성공", async () => { + const naverUser = await User.findOne({ where: { userId: "naverUser" } }); + { + const { premium } = await purchaseService.getPremiumStatus(naverUser); + expect(premium).toBe(premiumStatus.FALSE); + } + + const { credit } = await purchaseService.purchasePremium(naverUser); + expect(credit).toBe(150); + { + const { premium } = await purchaseService.getPremiumStatus(naverUser); + expect(premium).toBe(premiumStatus.TRUE); + } + }); - jest.spyOn(user, "save").mockImplementation(async () => { - return queryRunner.manager.save(user); - }); + it("크레딧이 없는 신규유저 요청", async () => { + const newUser = await User.findOne({ where: { userId: "newUser" } }); - await expect(purchaseService.purchasePremium(user)).rejects.toThrow( - `보유한 별가루가 부족합니다. 현재 ${user.credit} 별가루`, + await expect(purchaseService.purchasePremium(newUser)).rejects.toThrow( + `보유한 별가루가 부족합니다. 현재 0 별가루`, ); }); - it("프리미엄 중복 구매시 실패", async () => { - const user = User.create({ - ...userMockData, - premium: premiumStatus.TRUE, - credit: 500, - }); - await queryRunner.manager.save(user); - - jest.spyOn(user, "save").mockImplementation(async () => { - return queryRunner.manager.save(user); - }); + it("이미 프리미엄 사용자인 카카오유저 요청", async () => { + const kakaoUser = await User.findOne({ where: { userId: "kakaoUser" } }); + { + const { premium } = await purchaseService.getPremiumStatus(kakaoUser); + expect(premium).toBe(premiumStatus.TRUE); + } - await expect(purchaseService.purchasePremium(user)).rejects.toThrow( + await expect(purchaseService.purchasePremium(kakaoUser)).rejects.toThrow( "이미 프리미엄 사용자입니다.", ); }); diff --git a/BE/test/int/shapes.repository.int-spec.ts b/BE/test/int/shapes.repository.int-spec.ts index 2b502f0..56b84f0 100644 --- a/BE/test/int/shapes.repository.int-spec.ts +++ b/BE/test/int/shapes.repository.int-spec.ts @@ -1,93 +1,83 @@ -import { RedisModule } from "@liaoliaots/nestjs-redis"; +import { NotFoundException } from "@nestjs/common"; import { Test, TestingModule } from "@nestjs/testing"; import { TypeOrmModule } from "@nestjs/typeorm"; import { User } from "src/auth/users.entity"; import { typeORMTestConfig } from "src/configs/typeorm.test.config"; import { Shape } from "src/shapes/shapes.entity"; -import { ShapesModule } from "src/shapes/shapes.module"; import { ShapesRepository } from "src/shapes/shapes.repository"; +import { DataSource } from "typeorm"; +import { TransactionalTestContext } from "typeorm-transactional-tests"; describe("ShapesRepository 통합 테스트", () => { let shapesRepository: ShapesRepository; + let dataSource: DataSource; + let transactionalContext: TransactionalTestContext; + let commonUser: User; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [ - TypeOrmModule.forRoot(typeORMTestConfig), - ShapesModule, - RedisModule.forRoot({ - readyLog: true, - config: { - host: "223.130.129.145", - port: 6379, - }, - }), - ], + imports: [TypeOrmModule.forRoot(typeORMTestConfig)], providers: [ShapesRepository], }).compile(); - shapesRepository = - await moduleFixture.get(ShapesRepository); + shapesRepository = moduleFixture.get(ShapesRepository); + dataSource = moduleFixture.get(DataSource); + commonUser = await User.findOne({ + where: { userId: "commonUser" }, + }); + }); + + beforeEach(async () => { + transactionalContext = new TransactionalTestContext(dataSource); + await transactionalContext.start(); + }); + + afterEach(async () => { + await transactionalContext.finish(); }); afterAll(async () => {}); describe("createDefaultShapes 통합 테스트", () => { it("메서드 정상 요청", async () => { - const commonUser = await User.findOne({ - where: { userId: "commonUser" }, - }); + await dataSource.query("SET FOREIGN_KEY_CHECKS = 0"); + await Shape.delete({}); + await dataSource.query("SET FOREIGN_KEY_CHECKS = 1"); + expect(await Shape.find()).toHaveLength(0); await shapesRepository.createDefaultShapes(commonUser); + expect(await Shape.find()).toHaveLength(15); }); }); describe("getShapesByUser 통합 테스트", () => { it("메서드 정상 요청", async () => { - const commonUser = await User.findOne({ - where: { userId: "commonUser" }, - }); - const result: Shape[] = - await shapesRepository.getShapesByUser(commonUser); + await shapesRepository.getShapesByUser("commonUser"); - // 현재는 기본 모양 15개와 기존에 임시로 사용하던 기본 모양 3개가 모두 있음 - // 추후 임시 모양 삭제 시 15로 수정할 것 - expect(result.length).toBe(18); + expect(result).toHaveLength(15); }); }); describe("getShapeByShapePath 통합 테스트", () => { it("메서드 정상 요청", async () => { - const shape = await Shape.findOne({ - where: { - user: { - userId: "commonUser", - }, - }, - }); + const shapePath = "BasicShape10.svg"; + const result = await shapesRepository.getShapeByShapePath(shapePath); - const result = await shapesRepository.getShapeByShapePath( - shape.shapePath, - ); - - expect(result).toStrictEqual(shape); + expect(result.shapePath).toEqual(shapePath); + expect(result.uuid).toBe("c048ba4f-4ea9-4605-bcc9-e45103d68a4f"); + expect(result.user.userId).toBe("commonUser"); }); }); describe("getShapeByUuid 통합 테스트", () => { it("메서드 정상 요청", async () => { - const shape = await Shape.findOne({ - where: { - user: { - userId: "commonUser", - }, - }, - }); - - const result = await shapesRepository.getShapeByUuid(shape.uuid); + const uuid = "c048ba4f-4ea9-4605-bcc9-e45103d68a4f"; + const result = await shapesRepository.getShapeByUuid(uuid); - expect(result).toStrictEqual(shape); + expect(result.uuid).toEqual(uuid); + expect(result.shapePath).toBe("BasicShape10.svg"); + expect(result.user.userId).toBe("commonUser"); }); }); }); diff --git a/BE/test/int/shapes.service.int-spec.ts b/BE/test/int/shapes.service.int-spec.ts index cf28289..452e7de 100644 --- a/BE/test/int/shapes.service.int-spec.ts +++ b/BE/test/int/shapes.service.int-spec.ts @@ -4,6 +4,10 @@ import { ShapesService } from "src/shapes/shapes.service"; import { ShapesRepository } from "src/shapes/shapes.repository"; import { Shape } from "src/shapes/shapes.entity"; import { User } from "src/auth/users.entity"; +import { DataSource } from "typeorm"; +import { TransactionalTestContext } from "typeorm-transactional-tests"; +import { TypeOrmModule } from "@nestjs/typeorm"; +import { typeORMTestConfig } from "src/configs/typeorm.test.config"; import { defaultShapes } from "src/shapes/shapes.default"; jest.mock("src/utils/s3", () => ({ @@ -13,45 +17,52 @@ jest.mock("src/utils/s3", () => ({ describe("ShapesService 통합 테스트", () => { let shapeService: ShapesService; let shapesRepository: ShapesRepository; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ + let dataSource: DataSource; + let transactionalContext: TransactionalTestContext; + let oldUser: User; + const shapeUuid = "8d010933-03f6-48f1-aa9f-5e4771eaf28c"; + + beforeAll(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [TypeOrmModule.forRoot(typeORMTestConfig)], providers: [ShapesService, ShapesRepository], }).compile(); - shapeService = module.get(ShapesService); - shapesRepository = module.get(ShapesRepository); + shapeService = moduleFixture.get(ShapesService); + shapesRepository = moduleFixture.get(ShapesRepository); + dataSource = moduleFixture.get(DataSource); + + oldUser = await User.findOne({ where: { userId: "oldUser" } }); + }); + + beforeEach(async () => { + transactionalContext = new TransactionalTestContext(dataSource); + await transactionalContext.start(); }); afterEach(async () => { - await jest.clearAllMocks(); + await transactionalContext.finish(); + await jest.restoreAllMocks(); }); describe("getDefaultShapeFiles 메서드", () => { it("메서드 정상 요청", async () => { - const shape: Shape = new Shape(); - jest - .spyOn(shapesRepository, "getShapeByShapePath") - .mockResolvedValue(shape); - const shapes = await shapeService.getDefaultShapeFiles(); expect(shapes).toHaveLength(defaultShapes.length); expect(shapes.every((shape) => shape instanceof Shape)).toBe(true); + expect(shapes.map((shape) => shape.shapePath).sort()).toEqual( + defaultShapes.map((shape) => shape.shapePath).sort(), + ); }); }); describe("getShapeFileByUuid 메서드", () => { it("메서드 정상 요청", async () => { - const shape: Shape = new Shape(); - shape.user = new User(); - jest.spyOn(shapesRepository, "getShapeByUuid").mockResolvedValue(shape); - const shapeFile = await shapeService.getShapeFileByUuid( - "uuid", - new User(), + shapeUuid, + oldUser, ); - expect(shapeFile).toBe("shape_svg_string"); }); @@ -63,32 +74,19 @@ describe("ShapesService 통합 테스트", () => { jest.spyOn(shapesRepository, "getShapeByUuid").mockResolvedValue(shape); await expect( - shapeService.getShapeFileByUuid("uuid", new User()), + shapeService.getShapeFileByUuid("uuid", oldUser), ).rejects.toThrow(UnauthorizedException); }); }); describe("getShapesByUser 메서드", () => { it("메서드 정상 요청", async () => { - const shapeDefault: Shape = new Shape(); - const shapeUser: Shape = new Shape(); + const [shapeUuidList, shapeFileList] = + await shapeService.getShapesByUser(oldUser); - jest - .spyOn(shapeService, "getDefaultShapeFiles") - // 원래는 15개의 기본 모양이 있지만, mocking 용으로 3개의 모양만 반환하도록 함 - .mockResolvedValue([shapeDefault, shapeDefault, shapeDefault]); - jest - .spyOn(shapesRepository, "getShapesByUser") - .mockResolvedValue([shapeUser]); - jest - .spyOn(shapeService, "getShapeFileByUuid") - .mockResolvedValue("shape_svg_string"); - - const [shapeUuidList, shapeFileList] = await shapeService.getShapesByUser( - new User(), - ); - expect(shapeUuidList).toHaveLength(4); - expect(shapeFileList).toHaveLength(4); + expect(shapeUuidList).toHaveLength(15); + expect(shapeUuidList).toContain(shapeUuid); + expect(shapeFileList).toHaveLength(15); expect(shapeFileList.every((file) => file === "shape_svg_string")).toBe( true, ); diff --git a/BE/test/int/stat.service.int-spec.ts b/BE/test/int/stat.service.int-spec.ts index fb9353d..ece24ad 100644 --- a/BE/test/int/stat.service.int-spec.ts +++ b/BE/test/int/stat.service.int-spec.ts @@ -1,112 +1,175 @@ import { Test, TestingModule } from "@nestjs/testing"; import { TypeOrmModule } from "@nestjs/typeorm"; import { typeORMTestConfig } from "src/configs/typeorm.test.config"; -import { Diary } from "src/diaries/diaries.entity"; import { StatService } from "src/stat/stat.service"; +import { DataSource } from "typeorm"; +import { TransactionalTestContext } from "typeorm-transactional-tests"; describe("StatService 통합 테스트", () => { let service: StatService; + let dataSource: DataSource; + let transactionalContext: TransactionalTestContext; beforeAll(async () => { - const module: TestingModule = await Test.createTestingModule({ + const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [TypeOrmModule.forRoot(typeORMTestConfig)], providers: [StatService], }).compile(); - service = module.get(StatService); + service = moduleFixture.get(StatService); + dataSource = moduleFixture.get(DataSource); + }); + + beforeEach(async () => { + transactionalContext = new TransactionalTestContext(dataSource); + await transactionalContext.start(); }); afterEach(async () => { + await transactionalContext.finish(); await jest.restoreAllMocks(); }); describe("getTopThreeTagsByUser 메서드", () => { - it("메서드 정상 요청", async () => { + it("기존유저 메서드 정상 요청", async () => { const year = 2023; - const userId = 1; - - const mockQueryBuilder = { - select: jest.fn().mockReturnThis(), - addSelect: jest.fn().mockReturnThis(), - innerJoin: jest.fn().mockReturnThis(), - where: jest.fn().mockReturnThis(), - andWhere: jest.fn().mockReturnThis(), - groupBy: jest.fn().mockReturnThis(), - addGroupBy: jest.fn().mockReturnThis(), - orderBy: jest.fn().mockReturnThis(), - limit: jest.fn().mockReturnThis(), - getRawMany: jest.fn().mockResolvedValue([ - { tag: "태그1", id: 1, count: 10 }, - { tag: "태그2", id: 2, count: 9 }, - { tag: "태그3", id: 3, count: 8 }, - ]), + const userId = 2; + + const result = await service.getTopThreeTagsByUser(year, userId); + const expectedResult = { + first: { rank: 1, tag: "Old", id: 1, count: 15 }, + second: { rank: 2, tag: "중립", id: 8, count: 6 }, + third: { rank: 3, tag: "긍정", id: 3, count: 5 }, }; - jest - .spyOn(Diary, "createQueryBuilder") - .mockReturnValue(mockQueryBuilder as any); + expect(result).toEqual(expectedResult); + }); + + it("신규유저 메서드 정상 요청", async () => { + const year = 2023; + const userId = 3; const result = await service.getTopThreeTagsByUser(year, userId); + const expectedResult = {}; - expect(result).toEqual({ - first: { rank: 1, tag: "태그1", id: 1, count: 10 }, - second: { rank: 2, tag: "태그2", id: 2, count: 9 }, - third: { rank: 3, tag: "태그3", id: 3, count: 8 }, - }); + expect(result).toEqual(expectedResult); + }); + + it("네이버유저 메서드 정상 요청", async () => { + const year = 2023; + const userId = 4; + + const result = await service.getTopThreeTagsByUser(year, userId); + const expectedResult = { + first: { rank: 1, tag: "긍정", id: 3, count: 3 }, + second: { rank: 2, tag: "Naver", id: 13, count: 3 }, + third: { rank: 3, tag: "3월", id: 6, count: 1 }, + }; + + expect(result).toEqual(expectedResult); }); }); describe("getDiariesDateByUser 메서드", () => { - it("메서드 정상 요청", async () => { + it("기존유저 메서드 정상 요청", async () => { const year = 2023; - const userId = 1; + const userId = 2; const result = await service.getDiariesDateByUser(year, userId); + const expectedResult = { + "2023-01-03": { sentiment: "positive", count: 1 }, + "2023-01-06": { sentiment: "neutral", count: 1 }, + "2023-02-22": { sentiment: "neutral", count: 1 }, + "2023-03-03": { sentiment: "negative", count: 1 }, + "2023-03-17": { sentiment: "positive", count: 1 }, + "2023-05-20": { sentiment: "negative", count: 1 }, + "2023-06-06": { sentiment: "negative", count: 1 }, + "2023-08-01": { sentiment: "positive", count: 1 }, + "2023-09-04": { sentiment: "neutral", count: 1 }, + "2023-09-23": { sentiment: "positive", count: 1 }, + "2023-10-01": { sentiment: "negative", count: 1 }, + "2023-10-10": { sentiment: "negative", count: 1 }, + "2023-10-29": { sentiment: "positive", count: 1 }, + "2023-11-01": { sentiment: "neutral", count: 1 }, + "2023-12-25": { sentiment: "neutral", count: 1 }, + }; - expect(Object.keys(result).includes("2023-08-01")).toEqual(true); - expect(Object.keys(result).includes("2023-08-02")).toEqual(true); - expect(Object.keys(result).includes("2023-08-03")).toEqual(true); + expect(result).toEqual(expectedResult); }); - }); - describe("getTopThreeShapesByUser 메서드", () => { - it("메서드 정상 요청", async () => { + it("신규유저 메서드 정상 요청", async () => { + const year = 2023; + const userId = 3; + + const result = await service.getDiariesDateByUser(year, userId); + const expectedResult = {}; + + expect(result).toEqual(expectedResult); + }); + + it("카카오유저 메서드 정상 요청", async () => { const year = 2023; - const userId = 1; - const mockQueryBuilder = { - select: jest.fn().mockReturnThis(), - addSelect: jest.fn().mockReturnThis(), - innerJoin: jest.fn().mockReturnThis(), - where: jest.fn().mockReturnThis(), - andWhere: jest.fn().mockReturnThis(), - groupBy: jest.fn().mockReturnThis(), - addGroupBy: jest.fn().mockReturnThis(), - orderBy: jest.fn().mockReturnThis(), - limit: jest.fn().mockReturnThis(), - getRawMany: jest.fn().mockResolvedValue([ - { uuid: "5d94024c-0f41-4e42-b766-a4f298766f67", count: 6 }, - { uuid: "5c670cee-f1e1-42ee-9b97-1ff5f561049e", count: 5 }, - ]), + const userId = 5; + + const result = await service.getDiariesDateByUser(year, userId); + const expectedResult = { + "2023-06-13": { sentiment: "neutral", count: 2 }, }; - jest - .spyOn(Diary, "createQueryBuilder") - .mockReturnValue(mockQueryBuilder as any); + expect(result).toEqual(expectedResult); + }); + }); - const result = await service.getTopThreeShapesByUser(year, userId); + describe("getTopThreeShapesByUser 메서드", () => { + it("기존유저 메서드 정상 요청", async () => { + const year = 2023; + const userId = 2; - expect(result).toEqual({ + const result = await service.getTopThreeShapesByUser(year, userId); + const expectedResult = { first: { rank: 1, - uuid: "5d94024c-0f41-4e42-b766-a4f298766f67", + uuid: "eaa0f81e-c6a8-4446-8c5a-44ea1a50bee8", count: 6, }, second: { rank: 2, - uuid: "5c670cee-f1e1-42ee-9b97-1ff5f561049e", - count: 5, + uuid: "43cb83db-8089-447c-a146-bfa07336528b", + count: 6, }, - }); + third: { + rank: 3, + uuid: "7774e2c6-f5b4-47f6-887d-63a6230b4a30", + count: 3, + }, + }; + + expect(result).toEqual(expectedResult); + }); + + it("신규유저 메서드 정상 요청", async () => { + const year = 2023; + const userId = 3; + + const result = await service.getTopThreeShapesByUser(year, userId); + const expectedResult = {}; + + expect(result).toEqual(expectedResult); + }); + + it("네이버유저 메서드 정상 요청", async () => { + const year = 2023; + const userId = 4; + + const result = await service.getTopThreeShapesByUser(year, userId); + const expectedResult = { + first: { + rank: 1, + uuid: "43cb83db-8089-447c-a146-bfa07336528b", + count: 3, + }, + }; + expect(result).toEqual(expectedResult); }); }); }); diff --git a/BE/test/int/tags.repository.int-spec.ts b/BE/test/int/tags.repository.int-spec.ts index c174b55..e8f69b7 100644 --- a/BE/test/int/tags.repository.int-spec.ts +++ b/BE/test/int/tags.repository.int-spec.ts @@ -1,65 +1,65 @@ -import { RedisModule } from "@liaoliaots/nestjs-redis"; import { Test, TestingModule } from "@nestjs/testing"; import { TypeOrmModule } from "@nestjs/typeorm"; import { typeORMTestConfig } from "src/configs/typeorm.test.config"; import { TagsRepository } from "src/tags/tags.repository"; -import { TagsModule } from "src/tags/tags.module"; import { Tag } from "src/tags/tags.entity"; import { NotFoundException } from "@nestjs/common"; +import { TransactionalTestContext } from "typeorm-transactional-tests"; +import { DataSource } from "typeorm"; -describe("UsersRepository 통합 테스트", () => { +describe("TagsRepository 통합 테스트", () => { let tagsRepository: TagsRepository; + let dataSource: DataSource; + let transactionalContext: TransactionalTestContext; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [ - TypeOrmModule.forRoot(typeORMTestConfig), - TagsModule, - RedisModule.forRoot({ - readyLog: true, - config: { - host: "223.130.129.145", - port: 6379, - }, - }), - ], + imports: [TypeOrmModule.forRoot(typeORMTestConfig)], providers: [TagsRepository], }).compile(); tagsRepository = moduleFixture.get(TagsRepository); + dataSource = moduleFixture.get(DataSource); + }); + + beforeEach(async () => { + transactionalContext = new TransactionalTestContext(dataSource); + await transactionalContext.start(); + }); + + afterEach(async () => { + await transactionalContext.finish(); }); describe("createTags 메서드", () => { it("메서드 정상 요청", async () => { - const tagName = "tagTest"; + const tagName = "getTagByNameTagTest"; const result = await tagsRepository.createTag(tagName); expect(result).toBeInstanceOf(Tag); - expect(result.name).toBe("tagTest"); + expect(result.name).toBe("getTagByNameTagTest"); }); }); describe("getTagByName 메서드", () => { it("메서드 정상 요청", async () => { - const tagName = "tagTest"; + const tagName = "getTagByNameTagTest"; await tagsRepository.createTag(tagName); const result = await tagsRepository.getTagByName(tagName); expect(result).toBeInstanceOf(Tag); - expect(result.name).toBe("tagTest"); + expect(result.name).toEqual(tagName); }); it("존재하지 않는 태그명으로 요청 시 실패", async () => { - const tagName = "tagTest2"; - - const result = await tagsRepository.getTagByName(tagName); - + const tagName = "NoTagTest"; try { await tagsRepository.getTagByName(tagName); } catch (error) { expect(error).toBeInstanceOf(NotFoundException); + expect(error.message).toBe(`Can't find Tag with name: [${tagName}]`); } }); }); diff --git a/BE/test/int/user.repository.int-spec.ts b/BE/test/int/user.repository.int-spec.ts index fb7c4b3..ac53df1 100644 --- a/BE/test/int/user.repository.int-spec.ts +++ b/BE/test/int/user.repository.int-spec.ts @@ -7,30 +7,32 @@ import { UsersRepository } from "src/auth/users.repository"; import { CreateUserDto } from "src/auth/dto/users.dto"; import { ConflictException } from "@nestjs/common"; import { User } from "src/auth/users.entity"; -import { clearUserDb } from "src/utils/clearDb"; +import { DataSource } from "typeorm"; +import { TransactionalTestContext } from "typeorm-transactional-tests"; +import { premiumStatus, providerEnum } from "src/utils/enum"; describe("UsersRepository 통합 테스트", () => { let usersRepository: UsersRepository; + let dataSource: DataSource; + let transactionalContext: TransactionalTestContext; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [ - TypeOrmModule.forRoot(typeORMTestConfig), - AuthModule, - RedisModule.forRoot({ - readyLog: true, - config: { - host: "223.130.129.145", - port: 6379, - }, - }), - ], + imports: [TypeOrmModule.forRoot(typeORMTestConfig)], providers: [UsersRepository], }).compile(); - usersRepository = await moduleFixture.get(UsersRepository); + usersRepository = moduleFixture.get(UsersRepository); + dataSource = moduleFixture.get(DataSource); + }); + + beforeEach(async () => { + transactionalContext = new TransactionalTestContext(dataSource); + await transactionalContext.start(); + }); - await clearUserDb(moduleFixture, usersRepository); + afterEach(async () => { + await transactionalContext.finish(); }); describe("createUser 메서드", () => { @@ -42,9 +44,14 @@ describe("UsersRepository 통합 테스트", () => { createUserDto.nickname = "ValidNickname"; const result = await usersRepository.createUser(createUserDto); - expect(result).toBeInstanceOf(User); - expect(result.userId).toBe("ValidUser123"); + expect(result).toMatchObject({ + userId: "ValidUser123", + email: "valid.email@test.com", + provider: providerEnum.BYEOLSOOP, + credit: 0, + premium: premiumStatus.FALSE, + }); }); it("중복된 아이디로 요청 시 실패", async () => { @@ -58,32 +65,62 @@ describe("UsersRepository 통합 테스트", () => { await usersRepository.createUser(createUserDto); } catch (error) { expect(error).toBeInstanceOf(ConflictException); - } - }); - - it("중복된 이메일로 요청 시 실패", async () => { - const createUserDto = new CreateUserDto(); - createUserDto.userId = "ValidUser1234"; - createUserDto.email = "valid.email@test.com"; - createUserDto.password = "ValidPass123!"; - createUserDto.nickname = "ValidNickname"; - - try { - await usersRepository.createUser(createUserDto); - } catch (error) { - expect(error).toBeInstanceOf(ConflictException); + expect(error.message).toBe("중복된 아이디입니다."); } }); }); describe("getUserByUserId 메서드", () => { - it("메서드 정상 요청", async () => { + it("commonUser 메서드 정상 요청", async () => { const userId = "commonUser"; const result = await usersRepository.getUserByUserId(userId); expect(result).toBeInstanceOf(User); - expect(result.email).toBe("byeolsoop08@naver.com"); + expect(result).toMatchObject({ + userId: "commonUser", + email: "byeolsoop08@naver.com", + provider: providerEnum.BYEOLSOOP, + credit: 0, + premium: premiumStatus.FALSE, + }); + }); + + it("네이버유저 메서드 정상 요청", async () => { + const userId = "naverUser"; + + const result = await usersRepository.getUserByUserId(userId); + + expect(result).toBeInstanceOf(User); + expect(result).toMatchObject({ + userId: "naverUser", + email: "naverUser@naver.com", + provider: providerEnum.NAVER, + credit: 500, + premium: premiumStatus.FALSE, + }); + }); + + it("카카오유저 메서드 정상 요청", async () => { + const userId = "kakaoUser"; + + const result = await usersRepository.getUserByUserId(userId); + + expect(result).toBeInstanceOf(User); + expect(result).toMatchObject({ + userId: "kakaoUser", + email: "kakaoUser@kakao.com", + provider: providerEnum.KAKAO, + credit: 1000, + premium: premiumStatus.TRUE, + }); + }); + + it("존재하지 않는 사용자 id 조회", async () => { + const userId = "notFoundUser"; + + const result = await usersRepository.getUserByUserId(userId); + expect(result).toBeNull(); }); }); });