diff --git a/frontend/src/apis/group.ts b/frontend/src/apis/group.ts index 6cc3374c6..d4af7b359 100644 --- a/frontend/src/apis/group.ts +++ b/frontend/src/apis/group.ts @@ -1,23 +1,22 @@ import { ERROR_BOUNDARY_IGNORE_ERROR, INVALID_REVIEW_PASSWORD_MESSAGE } from '@/constants'; -import { PasswordResponse, ReviewGroupData } from '@/types'; +import { DataForReviewRequestCode, PasswordResponse, ReviewGroupData } from '@/types'; import createApiErrorMessage from './apiErrorMessageCreator'; import endPoint from './endpoints'; -export interface DataForReviewRequestCode { - revieweeName: string; - projectName: string; - groupAccessCode?: string; -} +export const postDataForReviewRequestCodeApi = async ({ + groupAccessCode, + ...commonRequestData +}: DataForReviewRequestCode) => { + const requestData = groupAccessCode ? { ...commonRequestData, groupAccessCode } : commonRequestData; -export const postDataForReviewRequestCodeApi = async (dataForReviewRequestCode: DataForReviewRequestCode) => { const response = await fetch(endPoint.postingDataForReviewRequestCode, { method: 'POST', headers: { 'Content-Type': 'application/json', }, // TODO : 회원 리뷰 링크 API 문서 나오면 비밀번호 관련해 변경해야함 - body: JSON.stringify(dataForReviewRequestCode), + body: JSON.stringify(requestData), }); if (!response.ok) { diff --git a/frontend/src/components/reviewURL/URLGeneratorForm/components/URLGeneratorButton/index.tsx b/frontend/src/components/reviewURL/URLGeneratorForm/components/URLGeneratorButton/index.tsx index 56cdbd175..5f9cb4cfe 100644 --- a/frontend/src/components/reviewURL/URLGeneratorForm/components/URLGeneratorButton/index.tsx +++ b/frontend/src/components/reviewURL/URLGeneratorForm/components/URLGeneratorButton/index.tsx @@ -1,6 +1,6 @@ -import { DataForReviewRequestCode } from '@/apis/group'; import { Button } from '@/components'; import { HOM_EVENT_NAME } from '@/constants'; +import { DataForReviewRequestCode } from '@/types'; import { debounce, trackEventInAmplitude } from '@/utils'; import usePostDataForReviewRequestCode, { diff --git a/frontend/src/components/reviewURL/URLGeneratorForm/hooks/usePostDataForReviewRequestCode/index.ts b/frontend/src/components/reviewURL/URLGeneratorForm/hooks/usePostDataForReviewRequestCode/index.ts index 92f7a24f1..2a28db1ac 100644 --- a/frontend/src/components/reviewURL/URLGeneratorForm/hooks/usePostDataForReviewRequestCode/index.ts +++ b/frontend/src/components/reviewURL/URLGeneratorForm/hooks/usePostDataForReviewRequestCode/index.ts @@ -1,7 +1,8 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { DataForReviewRequestCode, postDataForReviewRequestCodeApi } from '@/apis/group'; +import { postDataForReviewRequestCodeApi } from '@/apis/group'; import { GROUP_QUERY_KEY } from '@/constants'; +import { DataForReviewRequestCode } from '@/types'; export interface UsePostDataForReviewRequestCodeProps { handleAPISuccess: (data: any) => void; handleAPIError: (error: Error) => void; diff --git a/frontend/src/components/reviewURL/URLGeneratorForm/hooks/usePostDataForReviewRequestCode/test.tsx b/frontend/src/components/reviewURL/URLGeneratorForm/hooks/usePostDataForReviewRequestCode/test.tsx index 81f889419..fce57d153 100644 --- a/frontend/src/components/reviewURL/URLGeneratorForm/hooks/usePostDataForReviewRequestCode/test.tsx +++ b/frontend/src/components/reviewURL/URLGeneratorForm/hooks/usePostDataForReviewRequestCode/test.tsx @@ -1,19 +1,14 @@ import { renderHook, act, waitFor } from '@testing-library/react'; -import { VALID_REVIEW_GROUP_REVIEW_REQUEST_CODE } from '@/mocks/mockData/group'; +import { VALID_REVIEW_REQUEST_CODE } from '@/mocks/mockData/group'; import QueryClientWrapper from '@/queryTestSetup/QueryClientWrapper'; +import { DataForReviewRequestCode } from '@/types'; import usePostDataForReviewRequestCode from '.'; describe('usePostDataForReviewRequestCode', () => { - it('ReviewRequestCode를 발급받을 수 있다.', async () => { - // given - const dataForReviewRequestCode = { - revieweeName: 'ollie', - projectName: 'review-me', - groupAccessCode: '1234', - }; - + // 공통 로직: renderHook 호출 및 초기화 + const setupHook = () => { const { result } = renderHook( () => usePostDataForReviewRequestCode({ @@ -24,15 +19,38 @@ describe('usePostDataForReviewRequestCode', () => { }), { wrapper: QueryClientWrapper }, ); + return result; + }; + + // 공통 테스트 로직 + const testReviewRequestCode = async (dataForReviewRequestCode: DataForReviewRequestCode, expectedCode: string) => { + const result = setupHook(); - // when act(() => { result.current.mutate(dataForReviewRequestCode); }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); - // then - expect(result.current.data.reviewRequestCode).toEqual(VALID_REVIEW_GROUP_REVIEW_REQUEST_CODE); + expect(result.current.data.reviewRequestCode).toEqual(expectedCode); + }; + + it('비회원 - ReviewRequestCode를 발급받을 수 있다.', async () => { + const dataForReviewRequestCode = { + revieweeName: 'ollie', + projectName: 'review-me', + groupAccessCode: '1234', + }; + + await testReviewRequestCode(dataForReviewRequestCode, VALID_REVIEW_REQUEST_CODE.nonMember); + }); + + it('회원용 - ReviewRequestCode를 발급받을 수 있다.', async () => { + const dataForReviewRequestCode = { + revieweeName: 'ollie', + projectName: 'review-me', + }; + + await testReviewRequestCode(dataForReviewRequestCode, VALID_REVIEW_REQUEST_CODE.member); }); }); diff --git a/frontend/src/constants/amplitudeEventName.ts b/frontend/src/constants/amplitudeEventName.ts index a9b406408..db35cce3b 100644 --- a/frontend/src/constants/amplitudeEventName.ts +++ b/frontend/src/constants/amplitudeEventName.ts @@ -24,8 +24,8 @@ export const PAGE_VISITED_EVENT_NAME: { [key in Exclude]: s detailedReview: '[page] 리뷰 상세 보기 페이지', reviewWriting: '[page] 리뷰 작성 페이지', reviewWritingComplete: '[page] 리뷰 작성 완료 페이지', - reviewLinks: '[page] 리뷰 링크 관리 페이지', - writtenReview: '[page] 작성한 리뷰 확인 페이지', + reviewLinks: '[page] 회원 전용 - 리뷰 링크 관리 페이지', + writtenReview: '[page] 회원 전용 - 작성한 리뷰 확인 페이지', }; export const REVIEW_WRITING_EVENT_NAME = { diff --git a/frontend/src/constants/route.ts b/frontend/src/constants/route.ts index b8b220cfa..cbc3b4cae 100644 --- a/frontend/src/constants/route.ts +++ b/frontend/src/constants/route.ts @@ -1,12 +1,13 @@ -// TODO: ROUTE -> ROUTE 및 상수 인덱스에 추가하기 +import { makeRoutePath } from '@/utils'; + export const ROUTE = { home: '/', - reviewList: 'user/review-list', - reviewWriting: 'user/review-writing', - reviewWritingComplete: 'user/review-writing-complete', - detailedReview: 'user/detailed-review', - reviewZone: 'user/review-zone', - reviewCollection: 'user/review-collection', - reviewLinks: 'user/review-links', - writtenReview: 'user/written-review', + reviewList: makeRoutePath('review-list'), + reviewWriting: makeRoutePath('review-writing'), + reviewWritingComplete: makeRoutePath('review-writing-complete'), + detailedReview: makeRoutePath('detailed-review'), + reviewZone: makeRoutePath('review-zone'), + reviewCollection: makeRoutePath('review-collection'), + reviewLinks: makeRoutePath('review-links', true), + writtenReview: makeRoutePath('written-review', true), }; diff --git a/frontend/src/hooks/reviewGroup/useGetReviewGroupData/test.ts b/frontend/src/hooks/reviewGroup/useGetReviewGroupData/test.ts index 14c0b3a60..2b352e7f9 100644 --- a/frontend/src/hooks/reviewGroup/useGetReviewGroupData/test.ts +++ b/frontend/src/hooks/reviewGroup/useGetReviewGroupData/test.ts @@ -1,6 +1,6 @@ import { renderHook, waitFor } from '@testing-library/react'; -import { VALID_REVIEW_GROUP_REVIEW_REQUEST_CODE } from '@/mocks/mockData'; +import { VALID_REVIEW_REQUEST_CODE } from '@/mocks/mockData'; import QueryClientWrapper from '@/queryTestSetup/QueryClientWrapper'; import useGetReviewGroupData from '.'; @@ -8,7 +8,7 @@ import useGetReviewGroupData from '.'; describe('리뷰 연결 페이지 리뷰 그룹 데이터 요청 테스트', () => { it('유효한 reviewRequestCode여야 라뷰 상세 페이지 데이터를 불러온다.', async () => { const { result } = renderHook( - () => useGetReviewGroupData({ reviewRequestCode: VALID_REVIEW_GROUP_REVIEW_REQUEST_CODE }), + () => useGetReviewGroupData({ reviewRequestCode: VALID_REVIEW_REQUEST_CODE.nonMember }), { wrapper: QueryClientWrapper, }, diff --git a/frontend/src/mocks/handlers/group.ts b/frontend/src/mocks/handlers/group.ts index ea4098818..d320280dc 100644 --- a/frontend/src/mocks/handlers/group.ts +++ b/frontend/src/mocks/handlers/group.ts @@ -2,32 +2,39 @@ import { http, HttpResponse } from 'msw'; import endPoint, { REVIEW_GROUP_DATA_API_PARAMS, REVIEW_GROUP_DATA_API_URL } from '@/apis/endpoints'; import { API_ERROR_MESSAGE, INVALID_REVIEW_PASSWORD_MESSAGE } from '@/constants'; +import { getRequestBody } from '@/utils/mockingUtils'; import { MOCK_AUTH_TOKEN_NAME, REVIEW_GROUP_DATA, - VALID_REVIEW_GROUP_REVIEW_REQUEST_CODE, + VALID_REVIEW_REQUEST_CODE, VALIDATED_PASSWORD, } from '../mockData/group'; // NOTE: reviewRequestCode 생성 정상 응답 const postDataForReviewRequestCode = () => { - return http.post(endPoint.postingDataForReviewRequestCode, async () => { - return HttpResponse.json({ reviewRequestCode: VALID_REVIEW_GROUP_REVIEW_REQUEST_CODE }, { status: 200 }); + return http.post(endPoint.postingDataForReviewRequestCode, async ({ request }) => { + // request body의 존재 검증 + const bodyResult = await getRequestBody(request); + + if (bodyResult instanceof Error) return HttpResponse.json({ error: bodyResult.message }, { status: 400 }); + const { nonMember: nonMemberReviewRequestCode, member: memberReviewRequestCode } = VALID_REVIEW_REQUEST_CODE; + return HttpResponse.json( + { + reviewRequestCode: 'groupAccessCode' in bodyResult ? nonMemberReviewRequestCode : memberReviewRequestCode, + }, + { status: 200 }, + ); }); }; const postPassWordValidation = () => { return http.post(endPoint.checkingPassword, async ({ request, cookies }) => { - // request body의 존재 검증 - if (!request.body) return HttpResponse.json({ error: API_ERROR_MESSAGE[400] }, { status: 400 }); - - const rawBody = await request.body.getReader().read(); - const textDecoder = new TextDecoder(); - const bodyText = textDecoder.decode(rawBody.value); + const bodyResult = await getRequestBody(request); + if (bodyResult instanceof Error) return HttpResponse.json({ error: bodyResult.message }, { status: 400 }); // request에 포함된 값들의 검증 시작 - const { reviewRequestCode, groupAccessCode: password } = JSON.parse(bodyText); + const { reviewRequestCode, groupAccessCode: password } = bodyResult; // 유효하지 않은 비밀번호인 경우 if (password !== VALIDATED_PASSWORD) { @@ -67,7 +74,7 @@ const getReviewGroupData = () => { //요청 url에서 reviewRequestCode 추출 const reviewRequestCode = params.get(queryString.reviewRequestCode); - if (reviewRequestCode === VALID_REVIEW_GROUP_REVIEW_REQUEST_CODE) { + if (reviewRequestCode) { return HttpResponse.json(REVIEW_GROUP_DATA); } diff --git a/frontend/src/mocks/mockData/group.ts b/frontend/src/mocks/mockData/group.ts index 5ead4de84..c08726e39 100644 --- a/frontend/src/mocks/mockData/group.ts +++ b/frontend/src/mocks/mockData/group.ts @@ -12,4 +12,7 @@ export const REVIEW_GROUP_DATA: ReviewGroupData = { }; /**리뷰 연결 페이지에서 유효한 reviewRequestCode */ -export const VALID_REVIEW_GROUP_REVIEW_REQUEST_CODE = `ABCD1234`; +export const VALID_REVIEW_REQUEST_CODE = { + nonMember: `ABCD1234`, + member: 'MEMBER1234', +}; diff --git a/frontend/src/pages/ReviewLinkPage/components/ReviewLinkDashboard/index.tsx b/frontend/src/pages/ReviewLinkPage/components/ReviewLinkDashboard/index.tsx index e6e6f3324..2470a7415 100644 --- a/frontend/src/pages/ReviewLinkPage/components/ReviewLinkDashboard/index.tsx +++ b/frontend/src/pages/ReviewLinkPage/components/ReviewLinkDashboard/index.tsx @@ -8,7 +8,7 @@ const ReviewLinkDashboard = () => { return ( - + diff --git a/frontend/src/types/reviewGroup.ts b/frontend/src/types/reviewGroup.ts index b5494310f..e13d44d17 100644 --- a/frontend/src/types/reviewGroup.ts +++ b/frontend/src/types/reviewGroup.ts @@ -6,3 +6,9 @@ export interface ReviewGroupData { revieweeName: string; projectName: string; } + +export interface DataForReviewRequestCode { + revieweeName: string; + projectName: string; + groupAccessCode?: string; +} diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts index 332b11083..1d9a3a07e 100644 --- a/frontend/src/utils/index.ts +++ b/frontend/src/utils/index.ts @@ -13,3 +13,4 @@ export * from './highlight/index'; export * from './testUtils'; export * from './analytics'; export * from './validateInput'; +export * from './path'; diff --git a/frontend/src/utils/mockingUtils.ts b/frontend/src/utils/mockingUtils.ts new file mode 100644 index 000000000..e8bbe06e8 --- /dev/null +++ b/frontend/src/utils/mockingUtils.ts @@ -0,0 +1,19 @@ +import { DefaultBodyType, StrictRequest } from 'msw'; + +import { API_ERROR_MESSAGE } from '@/constants'; + +/** + * api 요청 body를 탐색해 결과를 반환하는 함수 + * @param request mock handler의 request + * @returns request body가 있다면 경우 body 객체 반환, 그렇지 않을 경우 Error 반환 + */ +export const getRequestBody = async (request: StrictRequest) => { + // request body의 존재 검증 + if (!request.body) return new Error(API_ERROR_MESSAGE[400]); + + const rawBody = await request.body.getReader().read(); + const textDecoder = new TextDecoder(); + const bodyText = textDecoder.decode(rawBody.value); + + return JSON.parse(bodyText); +}; diff --git a/frontend/src/utils/path.ts b/frontend/src/utils/path.ts new file mode 100644 index 000000000..0dd136e35 --- /dev/null +++ b/frontend/src/utils/path.ts @@ -0,0 +1,5 @@ +export const makeRoutePath = (pageName: string, isForMember: boolean = false) => { + let basic = 'user'; + if (isForMember) basic += '/logged-in'; + return `${basic}/${pageName}`; +};