Skip to content

Commit

Permalink
[NDD-281]: 문제집 객체를 생성한다 (2h / 1h) (#113)
Browse files Browse the repository at this point in the history
* feat: 컨트롤러와 서비스 레이어 추가

* feat: 문제집 생성을 위한 dto 구현

* feat: 문제집이 카테고리를 다대일로 가지며, 카테고리는 회원을 가지지 않게 기능 구현

* refactor: workbook과 Category연결로 테스트 리펙토링

* feat: 문제집 생성 요청을 위한 dto 구현

* test: 비즈니스 로직 단위테스트 추가

* feat: 단위테스트를 통과하는 비즈니스 로직 구현

* test: 통합테스트 구현

* refactor: Workbook의 name -> title로 수정

* test: 컨트롤러 단위테스트 구현

* feat: 컨트롤러 로직 구현

* test: 컨트롤러 통합 테스트 구현 && 이에 따라 dto의 데코레이터 수정

* test: workbook.name에 대한 잔재 제거
  • Loading branch information
JangAJang authored Nov 28, 2023
1 parent 7eb7ae7 commit 3e0f0e7
Show file tree
Hide file tree
Showing 22 changed files with 620 additions and 261 deletions.
28 changes: 24 additions & 4 deletions BE/src/answer/controller/answer.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ import { workbookFixture } from '../../workbook/fixture/workbook.fixture';
import { WorkbookRepository } from '../../workbook/repository/workbook.repository';
import { WorkbookModule } from '../../workbook/workbook.module';
import { Workbook } from '../../workbook/entity/workbook';
import { CategoryRepository } from '../../category/repository/category.repository';
import { categoryFixtureWithId } from '../../category/fixture/category.fixture';

describe('AnswerController 단위테스트', () => {
let controller: AnswerController;
Expand Down Expand Up @@ -109,6 +111,7 @@ describe('AnswerController 통합테스트', () => {
let questionRepository: QuestionRepository;
let answerRepository: AnswerRepository;
let memberRepository: MemberRepository;
let categoryRepository: CategoryRepository;

beforeAll(async () => {
const modules = [
Expand Down Expand Up @@ -142,6 +145,8 @@ describe('AnswerController 통합테스트', () => {
moduleFixture.get<QuestionRepository>(QuestionRepository);
answerRepository = moduleFixture.get<AnswerRepository>(AnswerRepository);
memberRepository = moduleFixture.get<MemberRepository>(MemberRepository);
categoryRepository =
moduleFixture.get<CategoryRepository>(CategoryRepository);
});

beforeEach(async () => {
Expand All @@ -155,6 +160,7 @@ describe('AnswerController 통합테스트', () => {
it('쿠키를 가지고 원본 질문에 답변 생성을 요청하면 201코드와 생성된 질문의 Response가 반환된다.', async () => {
//given
const token = await authService.login(memberFixturesOAuthRequest);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'testQuestion'),
Expand All @@ -173,8 +179,9 @@ describe('AnswerController 통합테스트', () => {

it('쿠키를 가지고 복사된 질문에 답변 생성을 요청하면 원본에 대한 답변으로 저장하고 201코드와 생성된 질문의 Response가 반환된다.', async () => {
//given
const member = await memberRepository.save(memberFixture);
await memberRepository.save(memberFixture);
const token = await authService.login(memberFixturesOAuthRequest);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'testQuestion'),
Expand All @@ -195,7 +202,8 @@ describe('AnswerController 통합테스트', () => {

it('쿠키가 존재하지 않으면 401에러를 반환한다.', async () => {
//given
const member = await memberRepository.save(memberFixture);
await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'testQuestion'),
Expand Down Expand Up @@ -229,8 +237,9 @@ describe('AnswerController 통합테스트', () => {

it('content가 존재하지 않으면 400코드를 반환한다.', async () => {
//given
const member = await memberRepository.save(memberFixture);
await memberRepository.save(memberFixture);
const token = await authService.login(memberFixturesOAuthRequest);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'testQuestion'),
Expand All @@ -251,6 +260,7 @@ describe('AnswerController 통합테스트', () => {
it('토큰을 가지고 존재하는 질문에 대해 존재하는 답변으로 대표답변 설정을 요청하면 성공적으로 변경해준다.', async () => {
//given
const member = await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'testQuestion'),
Expand All @@ -271,6 +281,7 @@ describe('AnswerController 통합테스트', () => {
it('토큰이 없으면 권한없음 처리한다.', async () => {
//given
const member = await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'testQuestion'),
Expand All @@ -290,6 +301,7 @@ describe('AnswerController 통합테스트', () => {
it('다른 사람의 질문 id로 대표답변을 수정하려하면 403코드를 반환한다', async () => {
//given
const member = await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'testQuestion'),
Expand All @@ -311,6 +323,7 @@ describe('AnswerController 통합테스트', () => {
it('질문 id가 존재하지 않으면 404코드를 반환한다', async () => {
//given
const member = await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'testQuestion'),
Expand All @@ -331,7 +344,8 @@ describe('AnswerController 통합테스트', () => {

it('답변 id가 존재하지 않으면 404코드를 반환한다', async () => {
//given
const member = await memberRepository.save(memberFixture);
await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'testQuestion'),
Expand All @@ -352,6 +366,7 @@ describe('AnswerController 통합테스트', () => {
it('질문을 조회할 때 회원 정보가 없으면 모든 답변이 최신순으로 정렬된다. ', async () => {
//given
const member = await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'testQuestion'),
Expand All @@ -374,6 +389,7 @@ describe('AnswerController 통합테스트', () => {
it('질문을 조회할 때 회원 정보가 있으면 모든 등록한 DefaultAnswer부터 정렬된다. ', async () => {
//given
const member = await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'testQuestion'),
Expand Down Expand Up @@ -401,6 +417,7 @@ describe('AnswerController 통합테스트', () => {
it('존재하지 않는 질문의 id를 조회하면 404에러를 반환한다. ', async () => {
//given
const member = await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'testQuestion'),
Expand All @@ -425,6 +442,7 @@ describe('AnswerController 통합테스트', () => {
it('답변을 삭제할 때 자신의 댓글을 삭제하려고 하면 204코드와 함께 성공한다. ', async () => {
//given
const member = await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'testQuestion'),
Expand All @@ -445,6 +463,7 @@ describe('AnswerController 통합테스트', () => {
it('쿠키가 없으면 401에러를 반환한다.', async () => {
//given
const member = await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'testQuestion'),
Expand All @@ -461,6 +480,7 @@ describe('AnswerController 통합테스트', () => {
it('다른 사람의 답변을 삭제하면 403에러를 반환한다.', async () => {
//given
const member = await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'testQuestion'),
Expand Down
57 changes: 37 additions & 20 deletions BE/src/answer/service/answer.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ import {
import { Workbook } from '../../workbook/entity/workbook';
import { WorkbookModule } from '../../workbook/workbook.module';
import { WorkbookForbiddenException } from '../../workbook/exception/workbook.exception';
import { categoryFixtureWithId } from '../../category/fixture/category.fixture';
import { CategoryRepository } from '../../category/repository/category.repository';
import { CategoryModule } from '../../category/category.module';
import { Category } from '../../category/entity/category';

describe('AnswerService 단위 테스트', () => {
let service: AnswerService;
Expand Down Expand Up @@ -148,7 +152,7 @@ describe('AnswerService 단위 테스트', () => {
Workbook.of(
'FE 테스트',
'테스트용 FE 문제집입니다.',
'FE',
categoryFixtureWithId,
new Member(
100,
'janghee@janghee.com',
Expand All @@ -175,15 +179,17 @@ describe('AnswerService 통합테스트', () => {
let memberRepository: MemberRepository;
let answerRepository: AnswerRepository;
let answerService: AnswerService;
let categoryRepository: CategoryRepository;

beforeAll(async () => {
const modules = [
MemberModule,
AnswerModule,
QuestionModule,
WorkbookModule,
CategoryModule,
];
const entities = [Answer, Question, Member, Answer, Workbook];
const entities = [Answer, Question, Member, Answer, Workbook, Category];

const moduleFixture = await createIntegrationTestModule(modules, entities);
app = moduleFixture.createNestApplication();
Expand All @@ -196,6 +202,8 @@ describe('AnswerService 통합테스트', () => {
questionRepository =
moduleFixture.get<QuestionRepository>(QuestionRepository);
memberRepository = moduleFixture.get<MemberRepository>(MemberRepository);
categoryRepository =
moduleFixture.get<CategoryRepository>(CategoryRepository);
});

beforeEach(async () => {
Expand All @@ -209,33 +217,36 @@ describe('AnswerService 통합테스트', () => {
it('질문에 대한 응답을 추가할 수 있다.', async () => {
//given
const member = await memberRepository.save(memberFixture);
await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'testQuestion'),
);
//
// //when
// const createAnswerRequest = new CreateAnswerRequest(
// question.id,
// 'testAnswer',
// );
// const answerResponse = await answerService.addAnswer(
// createAnswerRequest,
// member,
// );
//
// //then
// const answer = await answerRepository.findByContentMemberIdAndQuestionId(
// 'testAnswer',
// member.id,
// question.id,
// );
// expect(answerResponse).toEqual(AnswerResponse.from(answer, member));

//when
const createAnswerRequest = new CreateAnswerRequest(
question.id,
'testAnswer',
);
const answerResponse = await answerService.addAnswer(
createAnswerRequest,
member,
);

//then
const answer = await answerRepository.findByContentMemberIdAndQuestionId(
'testAnswer',
member.id,
question.id,
);
expect(answerResponse).toEqual(AnswerResponse.from(answer, member));
});

it('복사된 질문에 답변을 추가해도, 원본 질문에 저장된다.', async () => {
//given
const member = await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const originalQuestion = await questionRepository.save(
Question.of(workbook, null, 'originalQuestion'),
Expand Down Expand Up @@ -268,6 +279,7 @@ describe('AnswerService 통합테스트', () => {
it('Member와 알맞은 Questin이 온다면, 정상적으로 대표 답변을 설정해준다.', async () => {
//given
const member = await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'test'),
Expand Down Expand Up @@ -301,6 +313,7 @@ describe('AnswerService 통합테스트', () => {
new Date(),
),
);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'test'),
Expand All @@ -324,6 +337,7 @@ describe('AnswerService 통합테스트', () => {
it('대표답변으로 설정하면 처음으로 온다.', async () => {
//given
const member = await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'test'),
Expand Down Expand Up @@ -351,6 +365,7 @@ describe('AnswerService 통합테스트', () => {
it('답변을 삭제할 때 대표답변이라면 답변을 삭제하고 게시물의 대표답변은 null이 된다.', async () => {
//given
const member = await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'test'),
Expand Down Expand Up @@ -383,6 +398,7 @@ describe('AnswerService 통합테스트', () => {
new Date(),
),
);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'test'),
Expand Down Expand Up @@ -413,6 +429,7 @@ describe('AnswerService 통합테스트', () => {
new Date(),
),
);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixture);
const question = await questionRepository.save(
Question.of(workbook, null, 'test'),
Expand Down
70 changes: 5 additions & 65 deletions BE/src/category/controller/category.controller.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,13 @@
import {
Body,
Controller,
Delete,
Get,
Post,
Query,
Req,
UseGuards,
} from '@nestjs/common';
import {
ApiBody,
ApiCookieAuth,
ApiOperation,
ApiResponse,
ApiTags,
} from '@nestjs/swagger';
import { Controller, Get } from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { CategoryService } from '../service/category.service';
import { Request } from 'express';
import { CreateCategoryRequest } from '../dto/createCategoryRequest';
import { Member } from '../../member/entity/member';
import { AuthGuard } from '@nestjs/passport';
import { createApiResponseOption } from '../../util/swagger.util';
import { CategoryListResponse } from '../dto/categoryListResponse';
import { TokenService } from '../../token/service/token.service';
import { getTokenValue } from '../../util/token.util';

@Controller('/api/category')
@ApiTags('category')
export class CategoryController {
constructor(
private categoryService: CategoryService,
private tokenService: TokenService,
) {}

@Post()
@UseGuards(AuthGuard('jwt'))
@ApiCookieAuth() // 문서 상에 자물쇠 아이콘을 표시하여 쿠키가 필요하다는 것을 나타냄
@ApiOperation({
summary: '카테고리를 추가한다.',
})
@ApiBody({ type: CreateCategoryRequest })
@ApiResponse(createApiResponseOption(201, '카테고리 추가', null))
async createCategory(
@Req() req: Request,
@Body() createCategoryRequest: CreateCategoryRequest,
) {
return await this.categoryService.createCategory(
createCategoryRequest,
req.user as Member,
);
}
constructor(private categoryService: CategoryService) {}

@Get()
@ApiOperation({
Expand All @@ -62,26 +20,8 @@ export class CategoryController {
CategoryListResponse,
),
)
async findCategories(@Req() req: Request) {
const token = getTokenValue(req);
const member = await this.tokenService.findMemberByToken(token);
const categories = await this.categoryService.findUsingCategories(member);

async findCategories() {
const categories = await this.categoryService.findUsingCategories();
return CategoryListResponse.of(categories);
}

@Delete()
@UseGuards(AuthGuard('jwt'))
@ApiCookieAuth()
@ApiOperation({
summary: '카테고리를 삭제한다.',
})
@ApiResponse(createApiResponseOption(204, '카테고리 삭제', null))
async deleteCategoryById(
@Req() req: Request,
@Query('categoryId') categoryId: number,
) {
const member = req.user as Member;
await this.categoryService.deleteCategoryById(member, categoryId);
}
}
Loading

0 comments on commit 3e0f0e7

Please sign in to comment.