Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NDD-285]: 문제집 삭제 로직 구현 (1h / 1h) #128

Merged
merged 6 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 134 additions & 1 deletion BE/src/workbook/controller/workbook.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import {
memberFixturesOAuthRequest,
mockReqWithMemberFixture,
oauthRequestFixture,
otherMemberFixture,
} from '../../member/fixture/member.fixture';
import { WorkbookIdResponse } from '../dto/workbookIdResponse';
import { Request, Response } from 'express';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import { CategoryRepository } from '../../category/repository/category.repository';
import { MemberRepository } from '../../member/repository/member.repository';
Expand All @@ -36,12 +38,16 @@ import {
} from '../../category/fixture/category.fixture';
import * as request from 'supertest';
import { CreateWorkbookRequest } from '../dto/createWorkbookRequest';
import { WorkbookNotFoundException } from '../exception/workbook.exception';
import {
WorkbookForbiddenException,
WorkbookNotFoundException,
} from '../exception/workbook.exception';
import { WorkbookResponse } from '../dto/workbookResponse';
import { WorkbookTitleResponse } from '../dto/workbookTitleResponse';
import { CategoryNotFoundException } from '../../category/exception/category.exception';
import { WorkbookRepository } from '../repository/workbook.repository';
import { UpdateWorkbookRequest } from '../dto/updateWorkbookRequest';
import { ManipulatedTokenNotFiltered } from '../../token/exception/token.exception';

describe('WorkbookController 단위테스트', () => {
let controller: WorkbookController;
Expand All @@ -51,6 +57,7 @@ describe('WorkbookController 단위테스트', () => {
findWorkbookTitles: jest.fn(),
findSingleWorkbook: jest.fn(),
updateWorkbook: jest.fn(),
deleteWorkbookById: jest.fn(),
};
const mockTokenService = {};

Expand Down Expand Up @@ -209,6 +216,75 @@ describe('WorkbookController 단위테스트', () => {
expect(result.content).toBe(workbookFixtureWithId.content);
});
});

describe('문제집을 삭제한다', () => {
const response = {
status: jest.fn().mockReturnThis(),
send: jest.fn(),
} as unknown as Response;

it('member와 id를 주면 문제집을 삭제한다', async () => {
//given

//when
mockWorkbookService.deleteWorkbookById.mockResolvedValue(undefined);

//then
await expect(
controller.deleteAnswer(mockReqWithMemberFixture, 1, response),
).resolves.toBeUndefined();
});

it('member가 null이면 ManipulatedToken예외처리한다', async () => {
//given

//when
mockWorkbookService.deleteWorkbookById.mockRejectedValue(
new ManipulatedTokenNotFiltered(),
);

//then
await expect(
controller.deleteAnswer(
{ user: null } as unknown as Request,
1,
response,
),
).rejects.toThrow(new ManipulatedTokenNotFiltered());
});

it('다른 회원이 삭제요청하면 WorkbookForbiddenException처리한다', async () => {
//given

//when
mockWorkbookService.deleteWorkbookById.mockRejectedValue(
new WorkbookForbiddenException(),
);

//then
await expect(
controller.deleteAnswer(
{ user: otherMemberFixture } as unknown as Request,
1,
response,
),
).rejects.toThrow(new WorkbookForbiddenException());
});

it('없는 문제집을 삭제요청하ㅑ면 WorkbookNotFoundException처리한다', async () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오타 발견했습니다!! 수정 부탁드려요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

//given

//when
mockWorkbookService.deleteWorkbookById.mockRejectedValue(
new WorkbookNotFoundException(),
);

//then
await expect(
controller.deleteAnswer(mockReqWithMemberFixture, 11412, response),
).rejects.toThrow(new WorkbookNotFoundException());
});
});
});

describe('WorkbookController 통합테스트', () => {
Expand Down Expand Up @@ -696,6 +772,63 @@ describe('WorkbookController 통합테스트', () => {
});
});

describe('문제집을 삭제한다', () => {
it('문제집을 성공적으로 삭제한다', async () => {
//given
await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

통합 테스트 시 member와 category를 save하는 것은 공통 로직인거 같은데 beforeEach로 하나의 함수로 리팩터링 하는건 어떨까요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const workbook = await workbookRepository.save(workbookFixtureWithId);

//when & then
const token = await authService.login(memberFixturesOAuthRequest);
const agent = request.agent(app.getHttpServer());
await agent
.delete(`/api/workbook/${workbook.id}`)
.set('Cookie', [`accessToken=${token}`])
.expect(204);
});

it('쿠키가 없으면 401에러를 반환한다', async () => {
//given
await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixtureWithId);

//when & then
const agent = request.agent(app.getHttpServer());
await agent.delete(`/api/workbook/${workbook.id}`).expect(401);
});

it('다른 사람의 문제집 삭제 요청을 하면 403에러를 반환한다', async () => {
//given
await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
const workbook = await workbookRepository.save(workbookFixtureWithId);

//when & then
const token = await authService.login(oauthRequestFixture);
const agent = request.agent(app.getHttpServer());
await agent
.delete(`/api/workbook/${workbook.id}`)
.set('Cookie', [`accessToken=${token}`])
.expect(403);
});

it('존재하지 않는 문제집을 삭제하려하면 404에러를 반환한다', async () => {
//given
await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);

//when & then
const token = await authService.login(memberFixturesOAuthRequest);
const agent = request.agent(app.getHttpServer());
await agent
.delete(`/api/workbook/124123`)
.set('Cookie', [`accessToken=${token}`])
.expect(404);
});
});

afterEach(async () => {
await workbookRepository.query('delete from Workbook');
await workbookRepository.query('delete from Member');
Expand Down
23 changes: 22 additions & 1 deletion BE/src/workbook/controller/workbook.controller.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
Post,
Query,
Req,
Res,
UseGuards,
} from '@nestjs/common';
import { WorkbookService } from '../service/workbook.service';
Expand All @@ -19,7 +21,7 @@ import {
ApiTags,
} from '@nestjs/swagger';
import { createApiResponseOption } from '../../util/swagger.util';
import { Request } from 'express';
import { Request, Response } from 'express';
import { CreateWorkbookRequest } from '../dto/createWorkbookRequest';
import { Member } from '../../member/entity/member';
import { WorkbookIdResponse } from '../dto/workbookIdResponse';
Expand Down Expand Up @@ -116,4 +118,23 @@ export class WorkbookController {
req.user as Member,
);
}

@Delete('/:workbookId')
@UseGuards(AuthGuard('jwt'))
@ApiCookieAuth()
@ApiOperation({
summary: '문제집 수정',
})
@ApiResponse(createApiResponseOption(204, '문제집 수정 완료', null))
async deleteAnswer(
@Req() req: Request,
@Param('workbookId') workbookId: number,
@Res() res: Response,
) {
await this.workbookService.deleteWorkbookById(
workbookId,
req.user as Member,
);
res.status(204).send();
}
}
4 changes: 4 additions & 0 deletions BE/src/workbook/repository/workbook.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,8 @@ export class WorkbookRepository {
async update(workbook: Workbook) {
return await this.repository.update({ id: workbook.id }, workbook);
}

async remove(workbook: Workbook) {
await this.repository.remove(workbook);
}
}
122 changes: 122 additions & 0 deletions BE/src/workbook/service/workbook.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ describe('WorkbookService 단위테스트', () => {
findMembersWorkbooks: jest.fn(),
findSingleWorkbook: jest.fn(),
update: jest.fn(),
remove: jest.fn(),
};

beforeAll(async () => {
Expand Down Expand Up @@ -274,6 +275,63 @@ describe('WorkbookService 단위테스트', () => {
}
});
});

describe('문제집 삭제', () => {
it('문제집을 성공적으로 삭제한다.', async () => {
//given

//when
mockWorkbookRepository.findById.mockResolvedValue(workbookFixtureWithId);
mockWorkbookRepository.remove.mockResolvedValue(undefined);

//then
await expect(
service.deleteWorkbookById(workbookFixtureWithId.id, memberFixture),
).resolves.toBeUndefined();
});

it('회원이 null이면 ManipulatedToken예외처리한다.', async () => {
//given

//when
mockWorkbookRepository.findById.mockResolvedValue(workbookFixtureWithId);
mockWorkbookRepository.remove.mockResolvedValue(undefined);

//then
await expect(
service.deleteWorkbookById(workbookFixtureWithId.id, null),
).rejects.toThrow(new ManipulatedTokenNotFiltered());
});

it('회원이 다른 사람이라면 WorkbookForbidden예외처리한다.', async () => {
//given

//when
mockWorkbookRepository.findById.mockResolvedValue(workbookFixtureWithId);
mockWorkbookRepository.remove.mockResolvedValue(undefined);

//then
await expect(
service.deleteWorkbookById(
workbookFixtureWithId.id,
otherMemberFixture,
),
).rejects.toThrow(new WorkbookForbiddenException());
});

it('문제집이 존재하지 않는다면 WorkbookNotFoundException예외처리한다.', async () => {
//given

//when
mockWorkbookRepository.findById.mockResolvedValue(null);
mockWorkbookRepository.remove.mockResolvedValue(undefined);

//then
await expect(
service.deleteWorkbookById(workbookFixtureWithId.id, memberFixture),
).rejects.toThrow(new WorkbookNotFoundException());
});
});
});

describe('WorkbookService 통합테스트', () => {
Expand Down Expand Up @@ -570,6 +628,70 @@ describe('WorkbookService 통합테스트', () => {
});
});

describe('문제집 삭제', () => {
it('문제집 삭제를 성공한다.', async () => {
//given
await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
await workbookRepository.save(workbookFixtureWithId);

//when

//then
await expect(
workbookService.deleteWorkbookById(
workbookFixtureWithId.id,
memberFixture,
),
).resolves.toBeUndefined();
});

it('문제집 삭제시 회원이 없으면 Manipulated예외처리한다..', async () => {
//given
await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
await workbookRepository.save(workbookFixtureWithId);

//when

//then
await expect(
workbookService.deleteWorkbookById(workbookFixtureWithId.id, null),
).rejects.toThrow(new ManipulatedTokenNotFiltered());
});

it('다른 회원이 문제집 수정을 요청하면 WorkbookForbidden예외처리한다.', async () => {
//given
await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
await workbookRepository.save(workbookFixtureWithId);

//when

//then
await expect(
workbookService.deleteWorkbookById(
workbookFixtureWithId.id,
otherMemberFixture,
),
).rejects.toThrow(new WorkbookForbiddenException());
});

it('존재하지 않는 문제집 삭제를 요청하면 WorkbookNotFoundExeption예외처리한다.', async () => {
//given
await memberRepository.save(memberFixture);
await categoryRepository.save(categoryFixtureWithId);
await workbookRepository.save(workbookFixtureWithId);

//when

//then
await expect(
workbookService.deleteWorkbookById(1244232, memberFixture),
).rejects.toThrow(new WorkbookNotFoundException());
});
});

afterEach(async () => {
await workbookRepository.query('delete from Workbook');
await workbookRepository.query('delete from Member');
Expand Down
8 changes: 8 additions & 0 deletions BE/src/workbook/service/workbook.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,12 @@ export class WorkbookService {
await this.workbookRepository.update(workbook);
return WorkbookResponse.of(workbook);
}

async deleteWorkbookById(workbookId: number, member: Member) {
validateManipulatedToken(member);
const workbook = await this.workbookRepository.findById(workbookId);
validateWorkbook(workbook);
validateWorkbookOwner(workbook, member);
await this.workbookRepository.remove(workbook);
}
}
Loading