diff --git a/README.md b/README.md index 10ba0b64..18488ae5 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,14 @@
-섬네일 +섬네일 -[💻 WeView](https://weview.club) | [📽 발표 영상](https://www.youtube.com/watch?v=-cujznqSkC4) | [📷 데모 영상](https://youtu.be/bK0whd-zuck) | [📝 팀 위키](https://github.com/boostcampwm-2022/web06-weview/wiki) +
+
+ +### [💻 WeView](https://weview.club) | [📽 발표 영상](https://www.youtube.com/watch?v=-cujznqSkC4) | [📷 데모 영상](https://youtu.be/bK0whd-zuck) | [📝 팀 위키](https://github.com/boostcampwm-2022/web06-weview/wiki) + +
@@ -17,9 +22,9 @@ **💡 WeView 팀은 사용자가 즐길 수 있는 가치를 전달하는 방법으로 3가지를 생각했어요.** > 1. 간단하게 코드를 확인하고 리뷰 할 수 있어요. -> +> > 2. 보고 싶은 코드를 쉽게 찾을 수 있어요. -> +> > 3. 한눈에 코드들이 들어오는 디자인을 생각했어요. ## 저희 ‍김가네 식구들을 소개할게요. @@ -48,11 +53,10 @@ 더 궁금하시다면 아래를 참고해주세요. -- 주요 기능이 궁금해요. 👉 [WeView 사용 설명서 바로가기]() -- 프로젝트를 직접 설치하고 싶어요. 👉 [설치 가이드 바로가기]() -- 협업 방식을 알아보고 싶어요. 👉 [협업 가이드 바로가기]() -- 사용한 기술이 궁금해요. 👉 [기술 스펙 바로가기]() -- 기술을 선정한 이유가 궁금해요. 👉 [기술 선정 이유 바로가기]() -- 프로젝트를 하면서 겪은 이야기가 궁금해요. 👉 [기술 블로그 바로가기]() +- 주요 기능이 궁금해요. 👉 [WeView 사용 설명서 바로가기](https://github.com/boostcampwm-2022/web06-weview/wiki/%EC%82%AC%EC%9A%A9-%EC%84%A4%EB%AA%85%EC%84%9C) +- 프로젝트를 직접 설치하고 싶어요. 👉 [설치 가이드 바로가기](https://github.com/boostcampwm-2022/web06-weview/wiki/%EC%84%A4%EC%B9%98-%EA%B0%80%EC%9D%B4%EB%93%9C) +- 협업 방식을 알아보고 싶어요. 👉 [협업 가이드 바로가기](https://github.com/boostcampwm-2022/web06-weview/wiki/%F0%9F%93%9C-%ED%98%91%EC%97%85-%EA%B0%80%EC%9D%B4%EB%93%9C) +- 사용한 기술이 궁금해요. 👉 [기술 스펙 바로가기](https://github.com/boostcampwm-2022/web06-weview/wiki/%F0%9F%97%82-%EA%B8%B0%EC%88%A0-%EC%8A%A4%ED%8E%99) +- 프로젝트를 하면서 겪은 이야기가 궁금해요. 👉 [기술 블로그 바로가기](https://github.com/boostcampwm-2022/web06-weview/wiki/%F0%9F%92%BB-%EA%B0%9C%EB%B0%9C-%EB%B8%94%EB%A1%9C%EA%B7%B8) 다른 이야기들은 [📝 팀 위키](https://github.com/boostcampwm-2022/web06-weview/wiki)에서 확인해 보실 수 있어요. diff --git a/client/src/constants/search.ts b/client/src/constants/search.ts index 60b2c967..1d07735c 100644 --- a/client/src/constants/search.ts +++ b/client/src/constants/search.ts @@ -15,3 +15,4 @@ export const REVIEW_COUNT_ITEMS = [ ]; export const MAX_SEARCH_HISTORY = 5; +export const MAX_SEARCH_TAGS_COUNT = 5; diff --git a/client/src/hooks/useSearch.ts b/client/src/hooks/useSearch.ts index 870a4a47..6d9f6c7d 100644 --- a/client/src/hooks/useSearch.ts +++ b/client/src/hooks/useSearch.ts @@ -14,6 +14,7 @@ import { } from "@/utils/label"; import { LABEL_NAME } from "@/constants/label"; import { formatTag } from "@/utils/regExpression"; +import { MAX_SEARCH_TAGS_COUNT } from "@/constants/search"; interface UseLabelResult { word: string; @@ -127,7 +128,11 @@ const useSearch = ( }; const handleInsertTag = (e: KeyboardEvent): void => { - if (word.length === 0 || !isEnterKey(e.key)) { + if ( + word.length === 0 || + !isEnterKey(e.key) || + MAX_SEARCH_TAGS_COUNT <= labels.length + ) { return; } const newLabel = createLabel(formatTag(word.trim()), LABEL_NAME.TAGS); diff --git a/scheduler-server/src/ranking/ranking.service.ts b/scheduler-server/src/ranking/ranking.service.ts index e3e86a5f..33d9198f 100644 --- a/scheduler-server/src/ranking/ranking.service.ts +++ b/scheduler-server/src/ranking/ranking.service.ts @@ -75,28 +75,28 @@ export class RankingService { return; } - const timeWaste = this.calcTimeWaste(idx); + const timeWeight = this.calcTimeWeight(idx); for (const tag in tagCounts) { if (totalTagCounts[tag]) { - totalTagCounts[tag] += tagCounts[tag] * timeWaste; + totalTagCounts[tag] += tagCounts[tag] * timeWeight; continue; } - totalTagCounts[tag] = tagCounts[tag] * timeWaste; + totalTagCounts[tag] = tagCounts[tag] * timeWeight; } }); return totalTagCounts; } - /** + /* * 시간 가중치는 현재 큐의 인덱스와 배열의 크기에 따라 정해진다 */ - private calcTimeWaste(idx: number): number { + private calcTimeWeight(idx: number): number { const offset = idx - this.index + ARRAY_SIZE - 1; - const timeWaste = ((offset % ARRAY_SIZE) + 1) / ARRAY_SIZE; + const timeWeight = ((offset % ARRAY_SIZE) + 1) / ARRAY_SIZE; - return timeWaste; + return timeWeight; } private addPrevInfo(newRanking: any[]) { diff --git a/server/package.json b/server/package.json index ba63f3b1..fce99a14 100644 --- a/server/package.json +++ b/server/package.json @@ -90,7 +90,7 @@ "^.+\\.(t|j)s$": "ts-jest" }, "collectCoverageFrom": [ - "**/*.service.(t|j)s" + "src/**/*.service.(t|j)s" ], "coverageDirectory": "./coverage", "testEnvironment": "node" diff --git a/server/src/domain/report/report.service.spec.ts b/server/src/domain/report/report.service.spec.ts new file mode 100644 index 00000000..94a5f8b2 --- /dev/null +++ b/server/src/domain/report/report.service.spec.ts @@ -0,0 +1,111 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { PostAlreadyReportedException } from 'src/exception/post-already-reported.exception'; +import { PostNotFoundException } from 'src/exception/post-not-found.exception'; +import { UserNotFoundException } from 'src/exception/user-not-found.exception'; +import { Post } from '../post/post.entity'; +import { PostRepository } from '../post/post.repository'; +import { User } from '../user/user.entity'; +import { UserRepository } from '../user/user.repository'; +import { ReportCreateRequestDto } from './dto/controller-request.dto'; +import { Report } from './report.entity'; +import { ReportRepository } from './report.repository'; +import { ReportService } from './report.service'; + +describe('신고 서비스', () => { + let testingModule: TestingModule; + let reportService: ReportService; + let reportRepository: ReportRepository; + let userRepository: UserRepository; + let postRepository: PostRepository; + + beforeAll(async () => { + testingModule = await Test.createTestingModule({ + providers: [ + ReportService, + { + provide: ReportRepository, + useValue: {}, + }, + { + provide: PostRepository, + useValue: {}, + }, + { + provide: UserRepository, + useValue: {}, + }, + ], + }).compile(); + }); + + beforeEach(() => { + reportService = testingModule.get(ReportService); + reportRepository = testingModule.get(ReportRepository); + postRepository = testingModule.get(PostRepository); + userRepository = testingModule.get(UserRepository); + }); + + describe('신고하기', () => { + it('존재하지 않는 유저가 신고를 하면 UserNotFoundException이 발생한다.', async () => { + // given + const userId = 1; + const postId = 1; + const requestDto = new ReportCreateRequestDto(); + + userRepository.findOneBy = jest.fn(); + postRepository.findOneBy = jest.fn(); + + // when + try { + await reportService.report(userId, postId, requestDto); + throw new Error(); + } catch (err) { + // then + expect(err).toBeInstanceOf(UserNotFoundException); + } + }); + + it('존재하지 않는 포스트에 신고를 하면 PostNotFoundException이 발생한다.', async () => { + // given + const userId = 1; + const postId = 1; + const requestDto = new ReportCreateRequestDto(); + + userRepository.findOneBy = jest.fn(async () => new User()); + postRepository.findOneBy = jest.fn(); + + // when + try { + await reportService.report(userId, postId, requestDto); + throw new Error(); + } catch (err) { + // then + expect(err).toBeInstanceOf(PostNotFoundException); + } + }); + + it('신고한 포스트를 다시 신고하면 PostAlreadyReportedException이 발생한다.', async () => { + // given + const userId = 1; + const postId = 1; + const requestDto = new ReportCreateRequestDto(); + + userRepository.findOneBy = jest.fn(async () => new User()); + postRepository.findOneBy = jest.fn(async () => new Post()); + reportRepository.findOneBy = jest.fn(); + reportRepository.insert = jest.fn(); + + // when + try { + await reportService.report(userId, postId, requestDto); + reportRepository.findOneBy = jest.fn(async () => new Report()); + await reportService.report(userId, postId, requestDto); + throw new Error(); + } catch (err) { + // then + expect(err).toBeInstanceOf(PostAlreadyReportedException); + expect(reportRepository.insert).toHaveBeenCalledTimes(1); + } + }); + }); +}); diff --git a/server/src/domain/review/review.service.spec.ts b/server/src/domain/review/review.service.spec.ts new file mode 100644 index 00000000..b597c80c --- /dev/null +++ b/server/src/domain/review/review.service.spec.ts @@ -0,0 +1,174 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { PostNotFoundException } from 'src/exception/post-not-found.exception'; +import { UserNotFoundException } from 'src/exception/user-not-found.exception'; +import { Post } from '../post/post.entity'; +import { PostRepository } from '../post/post.repository'; +import { User } from '../user/user.entity'; +import { UserRepository } from '../user/user.repository'; +import { + ReviewGetAllRequestDto, + ReviewWriteRequestDto, +} from './dto/controller-request.dto'; +import { Review } from './review.entity'; +import { ReviewRepository } from './review.repository'; +import { ReviewService } from './review.service'; + +describe('리뷰 서비스', () => { + let testingModule: TestingModule; + let reviewService: ReviewService; + let reviewRepository: ReviewRepository; + let postRepository: PostRepository; + let userRepository: UserRepository; + + beforeAll(async () => { + testingModule = await Test.createTestingModule({ + providers: [ + ReviewService, + { + provide: ReviewRepository, + useValue: {}, + }, + { + provide: PostRepository, + useValue: {}, + }, + { + provide: UserRepository, + useValue: {}, + }, + ], + }).compile(); + }); + + beforeEach(() => { + reviewService = testingModule.get(ReviewService); + reviewRepository = testingModule.get(ReviewRepository); + postRepository = testingModule.get(PostRepository); + userRepository = testingModule.get(UserRepository); + }); + + describe('리뷰 작성', () => { + it('존재하지 않는 유저가 리뷰를 작성하면 UserNotFoundException이 발생한다.', async () => { + // given + const dto: ReviewWriteRequestDto = { + postId: 1, + content: '내용', + }; + const userId = 1; + + userRepository.findOneBy = jest.fn(() => null); + postRepository.findOneBy = jest.fn(); + + // when + try { + await reviewService.write(userId, dto); + throw new Error(); + } catch (err) { + // then + expect(userRepository.findOneBy).toHaveBeenCalledTimes(1); + expect(err).toBeInstanceOf(UserNotFoundException); + } + }); + + it('존재하지 않는 포스트에 리뷰를 작성하면 PostNotFoundException이 발생한다.', async () => { + // given + const dto: ReviewWriteRequestDto = { + postId: 1, + content: '내용', + }; + const userId = 1; + + userRepository.findOneBy = jest.fn(async () => new User()); + postRepository.findOneBy = jest.fn(() => null); + + // when + try { + await reviewService.write(userId, dto); + throw new Error(); + } catch (err) { + expect(postRepository.findOneBy).toHaveBeenCalledTimes(1); + expect(err).toBeInstanceOf(PostNotFoundException); + } + }); + + it('유저 정보와 포스트 정보로 리뷰 작성에 성공한다.', async () => { + // given + const dto: ReviewWriteRequestDto = { + postId: 1, + content: '내용', + }; + const userId = 1; + + userRepository.findOneBy = jest.fn(async () => new User()); + postRepository.findOneBy = jest.fn(async () => new Post()); + postRepository.increaseReviewCount = jest.fn(async () => new Post()); + reviewRepository.insert = jest.fn(); + + // when + await reviewService.write(userId, dto); + + // then + expect(reviewRepository.insert).toHaveBeenCalledTimes(1); + }); + }); + + describe('리뷰 목록 조회', () => { + const REQUEST_CNT = 3; + it('리뷰를 한 번에 최대 REQUEST_CNT개까지 반환한다.', async () => { + // given + const reviews = Array.from({ length: REQUEST_CNT + 1 }, (v, i) => { + const review = new Review(); + review.id = i + 1; + review.user = new User(); + return review; + }); + const postId = 1; + const requestDto = new ReviewGetAllRequestDto(); + + reviewRepository.find = jest.fn(async () => reviews); + + // when + const dtos = await reviewService.getReviewsOfPost(postId, requestDto); + + // then + expect(dtos.reviews.length).toEqual(REQUEST_CNT); + expect(dtos.isLast).toEqual(false); + }); + + it('리뷰가 요청한 개수보다 적거나 같으면 isLast가 true다.', async () => { + // given + const reviews = Array.from({ length: REQUEST_CNT }, (v, i) => { + const review = new Review(); + review.id = i + 1; + review.user = new User(); + return review; + }); + const postId = 1; + const requestDto = new ReviewGetAllRequestDto(); + + reviewRepository.find = jest.fn(async () => reviews); + + // when + const dtos = await reviewService.getReviewsOfPost(postId, requestDto); + + // then + expect(dtos.lastId).toEqual(dtos.reviews[dtos.reviews.length - 1].id); + expect(dtos.isLast).toEqual(true); + }); + + it('리뷰가 없으면 isLast가 true다.', async () => { + // given + const postId = 1; + const requestDto = new ReviewGetAllRequestDto(); + + reviewRepository.find = jest.fn(); + + // when + const dtos = await reviewService.getReviewsOfPost(postId, requestDto); + + // then + expect(dtos.reviews.length).toEqual(0); + expect(dtos.isLast).toEqual(true); + }); + }); +});