diff --git a/backend/package-lock.json b/backend/package-lock.json index 3ab43fd..f5f008b 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -33,7 +33,7 @@ "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", - "@nestjs/testing": "^10.0.0", + "@nestjs/testing": "^10.3.3", "@types/cookie-parser": "^1.4.6", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", @@ -1893,9 +1893,9 @@ } }, "node_modules/@nestjs/testing": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.2.8.tgz", - "integrity": "sha512-9Kj5IQhM67/nj/MT6Wi2OmWr5YQnCMptwKVFrX1TDaikpY12196v7frk0jVjdT7wms7rV07GZle9I2z0aSjqtQ==", + "version": "10.3.3", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.3.tgz", + "integrity": "sha512-kX20GfjAImL5grd/i69uD/x7sc00BaqGcP2dRG3ilqshQUuy5DOmspLCr3a2C8xmVU7kzK4spT0oTxhe6WcCAA==", "dev": true, "dependencies": { "tslib": "2.6.2" @@ -12661,9 +12661,9 @@ } }, "@nestjs/testing": { - "version": "10.2.8", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.2.8.tgz", - "integrity": "sha512-9Kj5IQhM67/nj/MT6Wi2OmWr5YQnCMptwKVFrX1TDaikpY12196v7frk0jVjdT7wms7rV07GZle9I2z0aSjqtQ==", + "version": "10.3.3", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.3.tgz", + "integrity": "sha512-kX20GfjAImL5grd/i69uD/x7sc00BaqGcP2dRG3ilqshQUuy5DOmspLCr3a2C8xmVU7kzK4spT0oTxhe6WcCAA==", "dev": true, "requires": { "tslib": "2.6.2" diff --git a/backend/package.json b/backend/package.json index f35aa3c..05bd60e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -44,7 +44,7 @@ "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", - "@nestjs/testing": "^10.0.0", + "@nestjs/testing": "^10.3.3", "@types/cookie-parser": "^1.4.6", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", @@ -74,6 +74,9 @@ "ts" ], "rootDir": "src", + "modulePaths": [ + "/.." + ], "testRegex": ".*\\.spec\\.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" diff --git a/backend/src/lecture/lecture.controller.ts b/backend/src/lecture/lecture.controller.ts index 10fe023..806dc24 100644 --- a/backend/src/lecture/lecture.controller.ts +++ b/backend/src/lecture/lecture.controller.ts @@ -14,7 +14,6 @@ import { } from '@nestjs/common'; import { ApiBody, ApiHeader, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger'; import { Response } from 'express'; -import { UserService } from 'src/user/user.service'; import { CreateLectureDto } from './dto/create-lecture.dto'; import { LectureInfoDto } from './dto/response/response-lecture-info.dto'; import { UpdateLectureDto } from './dto/update-lecture.dto'; @@ -28,8 +27,7 @@ import { Types } from 'mongoose'; @Controller('lecture') export class LectureController { constructor( - private readonly lectureService: LectureService, - private readonly userService: UserService + private readonly lectureService: LectureService ) {} @UseGuards(CustomAuthGuard) @@ -43,8 +41,7 @@ export class LectureController { if (!req.user) { throw new HttpException('로그인 되지 않은 사용자입니다.', HttpStatus.UNAUTHORIZED); } - const user = await this.userService.findOneByEmail(req.user.email); - const code = await this.lectureService.createLecture(createLecture, user._id); + const code = await this.lectureService.createLecture(createLecture, req.user.email); res.status(HttpStatus.CREATED).send({ code: code }); } @@ -73,7 +70,7 @@ export class LectureController { throw new HttpException('유효하지 않은 강의 참여코드입니다.', HttpStatus.NOT_FOUND); } - await this.userService.updateLecture(req.user.email, enterCodeDocument); + await this.lectureService.updateLecture(req.user.email, enterCodeDocument); res.status(HttpStatus.OK).send(); } @@ -140,7 +137,7 @@ export class LectureController { throw new HttpException('로그인 되지 않은 사용자입니다.', HttpStatus.UNAUTHORIZED); } - const result = await this.userService.findLectureList(req.user.email); + const result = await this.lectureService.findLectureList(req.user.email); return res.status(HttpStatus.OK).send(result); } } diff --git a/backend/src/lecture/lecture.module.ts b/backend/src/lecture/lecture.module.ts index 02d7895..7b1f923 100644 --- a/backend/src/lecture/lecture.module.ts +++ b/backend/src/lecture/lecture.module.ts @@ -19,10 +19,9 @@ import { JwtModule } from '@nestjs/jwt'; { name: WhiteboardLog.name, schema: WhiteboardLogSchema }, { name: LectureSubtitle.name, schema: LectureSubtitleSchema } ]), - JwtModule + JwtModule, ], controllers: [LectureController], - providers: [LectureService, UserService], - exports: [LectureService, MongooseModule] + providers: [LectureService, UserService] }) export class LectureModule {} diff --git a/backend/src/lecture/lecture.service.ts b/backend/src/lecture/lecture.service.ts index 00a2389..ace0337 100644 --- a/backend/src/lecture/lecture.service.ts +++ b/backend/src/lecture/lecture.service.ts @@ -10,6 +10,7 @@ import { Lecture } from './schema/lecture.schema'; import { EnterCode } from './schema/lecture-code.schema'; import { generateRandomNumber } from 'src/utils/GenerateUtils'; import { LectureRecordDto } from './dto/response/response-lecture-record.dto'; +import { UserService } from 'src/user/user.service'; @Injectable() export class LectureService { @@ -21,14 +22,16 @@ export class LectureService { @InjectModel(WhiteboardLog.name) private whiteboardLogModel: Model, @InjectModel(LectureSubtitle.name) - private lectureSubtitleModel: Model + private lectureSubtitleModel: Model, + private readonly userService: UserService ) {} - async createLecture(createLectureDto: CreateLectureDto, id: Types.ObjectId) { + async createLecture(createLectureDto: CreateLectureDto, email: string) { + const user = await this.userService.findOneByEmail(email); const lecture = new this.lectureModel({ title: createLectureDto.title, description: createLectureDto.description, - presenter_id: id + presenter_id: user._id }); const lectureCode = new this.enterCodeModel({ code: await this.generateRoomCode(), @@ -103,4 +106,13 @@ export class LectureService { const audioFile = lecture.audio_file; return new LectureRecordDto(logs, subtitles, audioFile); } + + async updateLecture(email: string, enterCode: EnterCode) { + const lecture = await this.findLectureInfo(enterCode); + return await this.userService.updateLectureList(email, lecture.id); + } + + async findLectureList(email: string) { + return await this.userService.findLectureList(email); + } } diff --git a/backend/src/user/user.controller.ts b/backend/src/user/user.controller.ts index e66fe70..d14cb22 100644 --- a/backend/src/user/user.controller.ts +++ b/backend/src/user/user.controller.ts @@ -20,10 +20,6 @@ export class UserController { throw new HttpException('로그인 되지 않은 사용자입니다.', HttpStatus.UNAUTHORIZED); } const userInfo = await this.userService.findOneByEmail(req.user.email); - - if (!userInfo) { - throw new HttpException('사용자 정보가 존재하지 않습니다.', HttpStatus.NOT_FOUND); - } res.status(HttpStatus.OK).send(new UserInfoDto(userInfo)); } @@ -39,10 +35,6 @@ export class UserController { throw new HttpException('로그인 되지 않은 사용자입니다.', HttpStatus.UNAUTHORIZED); } const result = await this.userService.updateUsername(req.user.email, userUpdateDto.username); - if (!result) { - res.status(HttpStatus.NOT_FOUND).send(); - throw new HttpException('업데이트에 실패했습니다.', HttpStatus.NOT_FOUND); - } res.status(HttpStatus.OK).send(new UserInfoDto(result)); } } diff --git a/backend/src/user/user.module.ts b/backend/src/user/user.module.ts index 8bbc303..0f4ce8a 100644 --- a/backend/src/user/user.module.ts +++ b/backend/src/user/user.module.ts @@ -4,24 +4,18 @@ import { User, UserSchema } from './user.schema'; import { UserController } from './user.controller'; import { UserService } from './user.service'; import { JwtModule } from '@nestjs/jwt'; -import { LectureService } from 'src/lecture/lecture.service'; -import { Lecture, LectureSchema } from 'src/lecture/schema/lecture.schema'; -import { EnterCode, EnterCodeSchema } from 'src/lecture/schema/lecture-code.schema'; -import { WhiteboardLog, WhiteboardLogSchema } from 'src/lecture/schema/whiteboard-log.schema'; -import { LectureSubtitle, LectureSubtitleSchema } from 'src/lecture/lecture-subtitle.schema'; +import { LectureModule } from 'src/lecture/lecture.module'; @Module({ imports: [ MongooseModule.forFeature([ { name: User.name, schema: UserSchema }, - { name: Lecture.name, schema: LectureSchema }, - { name: EnterCode.name, schema: EnterCodeSchema }, - { name: WhiteboardLog.name, schema: WhiteboardLogSchema }, - { name: LectureSubtitle.name, schema: LectureSubtitleSchema } ]), - JwtModule + JwtModule, + LectureModule ], controllers: [UserController], - providers: [UserService, LectureService] + providers: [UserService], + exports: [UserModule] }) export class UserModule {} diff --git a/backend/src/user/user.service.spec.ts b/backend/src/user/user.service.spec.ts new file mode 100644 index 0000000..c45ca61 --- /dev/null +++ b/backend/src/user/user.service.spec.ts @@ -0,0 +1,102 @@ +import { Test } from '@nestjs/testing'; +import { UserService } from './user.service'; +import { getModelToken } from '@nestjs/mongoose'; +import { User } from './user.schema'; +import { Model, Types } from 'mongoose'; +import { NotFoundException } from '@nestjs/common'; + + +describe('UserModule', () => { + let service: UserService; + let model: Model; + beforeAll(async () => { + const module = await Test.createTestingModule({ + providers: [UserService, + { + provide: getModelToken(User.name), + useValue: Model + } + ] + }).compile(); + + service = module.get(UserService); + model = module.get>(getModelToken(User.name)); + }); + + it('should have UserService', () => { + expect(service).toBeDefined(); + }); + + describe('findOneByEmail', () => { + const id = new Types.ObjectId(); + const email = "test@gmail.com"; + + it('should throw NotFoundException', async () => { + jest.spyOn(model, 'findOne').mockResolvedValue(null); + await expect(service.findOneByEmail(email)).rejects.toThrow(NotFoundException); + }) + + it('should find user', async () => { + jest.spyOn(model, 'findOne').mockResolvedValue({ + _id: id, + email: email + }); + const user = await service.findOneByEmail(email); + expect(user._id).toEqual(id); + expect(user.email).toEqual(email); + }) + }); + + describe('updateUsername', () => { + const id = new Types.ObjectId(); + const email = "test@gmail.com"; + const username = "testname"; + + it('should throw NotFoundException', async () => { + jest.spyOn(model, 'findOneAndUpdate').mockResolvedValue(null); + await expect(service.updateUsername(email, username)).rejects.toThrow(NotFoundException); + }) + + it('should update username', async () => { + jest.spyOn(model, 'findOneAndUpdate').mockResolvedValue({ + _id: id, + email: email, + username: username + }); + + const user = await service.updateUsername(email, username); + expect(user.username).toEqual(username); + }) + }); + + + describe('findLectureList', () => { + const email = "test@gmail.com"; + + it('should throw NotFoundException', async () => { + jest.spyOn(model, 'findOne').mockResolvedValue(null); + await expect(service.findLectureList(email)).rejects.toThrow(NotFoundException); + }) + }); + + describe('updateLectureList', () => { + const email = 'test@gmail.com'; + const id = new Types.ObjectId(); + + it('should throw NotFoundException', async () => { + jest.spyOn(model, 'findOneAndUpdate').mockResolvedValue(null); + await expect(service.updateLectureList(email, id)).rejects.toThrow(NotFoundException); + }) + + it('should call with', async () => { + const findOneAndUpdateMock = jest.spyOn(model, 'findOneAndUpdate').mockResolvedValue({}); + + await service.updateLectureList(email, id); + expect(findOneAndUpdateMock).toHaveBeenCalledWith( + { email: email }, + { $push: { lecture_id: id } }, + { new: true } + ); + }) + }); +}); \ No newline at end of file diff --git a/backend/src/user/user.service.ts b/backend/src/user/user.service.ts index 3960fa8..1d4c761 100644 --- a/backend/src/user/user.service.ts +++ b/backend/src/user/user.service.ts @@ -1,44 +1,50 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { User, UserDocument } from './user.schema'; -import { Model } from 'mongoose'; -import { LectureService } from 'src/lecture/lecture.service'; -import { EnterCode } from 'src/lecture/schema/lecture-code.schema'; +import { Model, Types } from 'mongoose'; @Injectable() export class UserService { constructor( - @InjectModel(User.name) private userModel: Model, - private lectureService: LectureService + @InjectModel(User.name) private userModel: Model ) {} async findOneByEmail(email: string): Promise { - return await this.userModel.findOne({ email: email }); + const user = await this.userModel.findOne({ email: email }); + if (!user) { + throw new NotFoundException('user not found'); + } + return user; } async updateUsername(email: string, username: string) { - return await this.userModel.findOneAndUpdate({ email: email }, { username: username }, { new: true }); + const user = await this.userModel.findOneAndUpdate({ email: email }, { username: username }, { new: true }); + if (!user) { + throw new NotFoundException('user not found'); + } + return user; } - async updateLecture(email: string, enterCode: EnterCode) { - const lecture = await this.lectureService.findLectureInfo(enterCode); - return await this.userModel.findOneAndUpdate( - { email: email }, - { $push: { lecture_id: lecture.id } }, - { new: true } - ); + async findLectureList(email: string) { + let user = await this.findOneByEmail(email); + user = await user.populate({ + path: 'lecture_id', + select: '-__v', + match: { is_end: true }, + populate: { path: 'presenter_id', select: '-_id username' } + }); + return user.lecture_id; } - async findLectureList(email: string) { - return ( - await ( - await this.findOneByEmail(email) - ).populate({ - path: 'lecture_id', - select: '-__v', - match: { is_end: true }, - populate: { path: 'presenter_id', select: '-_id username' } - }) - ).lecture_id; + async updateLectureList(email: string, id: Types.ObjectId) { + const user = await this.userModel.findOneAndUpdate( + { email: email }, + { $push: { lecture_id: id } }, + { new: true } + ); + if (!user) { + throw new NotFoundException('user not found'); + } + return user; } }