Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 4 additions & 4 deletions src/api/service/user-service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ export const userServiceRemote = () => ({

// 4. 알림 설정 변경
updateMyNotification: async (queryParams: UpdateMyNotificationQueryParams) => {
return apiV1.patch<User>(
`/users/notification?isNotificationEnabled=${queryParams.isNotificationEnabled}`,
);
return apiV1.patch<User>(`/users/notification`, null, {
params: { ...queryParams },
});
},

// 5. 유저 프로필 조회
Expand All @@ -50,7 +50,7 @@ export const userServiceRemote = () => ({
// 7. 닉네임 중복 검사
getNicknameAvailability: async (queryParams: GetNicknameAvailabilityQueryParams) => {
return apiV1.get<Availability>(`/users/nickname/availability`, {
params: { nickname: queryParams.nickName },
params: { ...queryParams },
});
},

Expand Down
152 changes: 152 additions & 0 deletions src/components/pages/user/profile/profile-edit-modal/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { act, render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { delay, http, HttpResponse } from 'msw';

import { API } from '@/api';
import { ModalProvider } from '@/components/ui';
import { server } from '@/mock/server';
import { createMockSuccessResponse } from '@/mock/service/common/common-mock';
import { mockUserItems } from '@/mock/service/user/user-mock';
import { User } from '@/types/service/user';

import { ProfileEditModal } from '.';

Expand Down Expand Up @@ -254,4 +258,152 @@ describe('ProfileEditModal 테스트', () => {
});
});
});

describe('폼 제출 테스트', () => {
describe('닉네임 중복 검사 테스트', () => {
test('닉네임 수정 시 닉네임 중복 검사가 실행된다.', async () => {
const user = userEvent.setup();

const getNicknameAvailabilitySpy = jest.spyOn(API.userService, 'getNicknameAvailability');

await renderWithProviders(<ProfileEditModal user={mockUser} />);

const nickNameInput = screen.getByDisplayValue(mockUser.nickName);
await user.clear(nickNameInput);
await user.type(nickNameInput, '새로운 닉네임');

const submitButton = screen.getByText('수정하기');
await user.click(submitButton);

await waitFor(() => {
expect(getNicknameAvailabilitySpy).toHaveBeenCalledWith({ nickName: '새로운 닉네임' });
});

getNicknameAvailabilitySpy.mockRestore();
});

test('중복된 닉네임일 경우 에러 메시지가 표시된다.', async () => {
const user = userEvent.setup();
await renderWithProviders(<ProfileEditModal user={mockUser} />);

const nickNameInput = screen.getByDisplayValue(mockUser.nickName);
await user.clear(nickNameInput);
await user.type(nickNameInput, '페르난도 토레스');

const submitButton = screen.getByText('수정하기');
await user.click(submitButton);

await waitFor(() => {
expect(screen.getByText('이미 사용중인 닉네임 입니다.')).toBeInTheDocument();
});
});

test('닉네임이 변경되지 않으면 중복 검사를 하지 않는다.', async () => {
const user = userEvent.setup();
const getNicknameAvailabilitySpy = jest.spyOn(API.userService, 'getNicknameAvailability');

await renderWithProviders(<ProfileEditModal user={mockUser} />);

const prevNickName = mockUser.nickName;
const nickNameInput = screen.getByDisplayValue(prevNickName);
await user.clear(nickNameInput);
await user.type(nickNameInput, prevNickName);

const submitButton = screen.getByText('수정하기');
await user.click(submitButton);

await waitFor(() => {
expect(getNicknameAvailabilitySpy).not.toHaveBeenCalledWith({
nickName: prevNickName,
});
});

getNicknameAvailabilitySpy.mockRestore();
});
});

describe('MBTI 제출 테스트', () => {
test('MBTI가 대문자로 변환되어 전송된다.', async () => {
const user = userEvent.setup();

const updateMyInfoSpy = jest.spyOn(API.userService, 'updateMyInfo');

await renderWithProviders(<ProfileEditModal user={mockUser} />);

const mbtiInput = screen.getByDisplayValue(mockUser.mbti);
await user.clear(mbtiInput);
await user.type(mbtiInput, 'enfp');

const submitButton = screen.getByText('수정하기');
await user.click(submitButton);

await waitFor(() => {
expect(updateMyInfoSpy).toHaveBeenCalledWith({ mbti: 'ENFP' });
});

updateMyInfoSpy.mockRestore();
});
});

describe('API 호출 테스트', () => {
test('이미지가 변경되지 않으면 updateMyImage API가 호출되지 않는다.', async () => {
const user = userEvent.setup();

const updateMyImageSpy = jest.spyOn(API.userService, 'updateMyImage');

await renderWithProviders(<ProfileEditModal user={mockUser} />);

const nickNameInput = screen.getByDisplayValue(mockUser.nickName);
await user.clear(nickNameInput);
await user.type(nickNameInput, '새로운 닉네임');

const messageInput = screen.getByDisplayValue(mockUser.profileMessage);
await user.clear(messageInput);
await user.type(messageInput, '새로운 소개글');

const mbtiInput = screen.getByDisplayValue(mockUser.mbti);
await user.clear(mbtiInput);
await user.type(mbtiInput, 'ISTJ');

const submitButton = screen.getByText('수정하기');
await user.click(submitButton);

await waitFor(() => {
expect(updateMyImageSpy).not.toHaveBeenCalled();
});

updateMyImageSpy.mockRestore();
});
});

describe('제출 버튼 상태 테스트', () => {
test('제출 중일 때 버튼이 "수정 중..."으로 변경되며 비활성화 된다', async () => {
server.use(
http.patch(`*/users/profile`, async ({ request }) => {
await delay(200);
const body = (await request.json()) as User;
return HttpResponse.json(
createMockSuccessResponse({
...mockUserItems[0],
...body,
}),
);
}),
);

const user = userEvent.setup();

await renderWithProviders(<ProfileEditModal user={mockUser} />);

const mbtiInput = screen.getByDisplayValue(mockUser.mbti);
await user.clear(mbtiInput);
await user.type(mbtiInput, 'enfp');

const submitButton = screen.getByText('수정하기');
await user.click(submitButton);

expect(await screen.findByRole('button', { name: '수정 중...' })).toBeDisabled();
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const ProfileEditModal = ({ user }: Props) => {
return {
form: '입력값을 확인해주세요',
fields: {
nickName: { message: '이미 사용 중인 닉네임입니다' },
nickName: { message: '이미 사용중인 닉네임 입니다.' },
},
};
}
Expand Down
133 changes: 118 additions & 15 deletions src/mock/service/user/user-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,68 @@ import { User } from '@/types/service/user';
import { createMockErrorResponse, createMockSuccessResponse } from '../common/common-mock';
import { mockUserItems } from './user-mock';

// 1. 팔로우 등록 모킹
const followUserMock = http.post(`*/users/follow`, async ({ request }) => {
const url = new URL(request.url);
const followNickname = url.searchParams.get('followNickname');
const user = mockUserItems.find((v) => v.nickName === followNickname);

if (user?.isFollow === false) {
return HttpResponse.json(
createMockSuccessResponse({
...user,
status: 201,
data: '팔로우 성공',
}),
);
}

if (user?.isFollow === null) {
return HttpResponse.json(
createMockErrorResponse({
status: 400,
detail: '회원 : 자기 자신을 팔로우할 수 없습니다.',
errorCode: 'NOT_SAME_FOLLOW',
}),
);
}

if (user?.isFollow === true) {
return HttpResponse.json(
createMockErrorResponse({
status: 400,
detail: '회원 : 이미 팔로우 중입니다.',
errorCode: 'ALREADY_EXIST_FOLLOW',
}),
);
}
});
Comment on lines 8 to 37
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 23, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

팔로우 상태 로직이 반대로 구현되어 있습니다.

현재 구현에서 isFollow === false일 때 "팔로우 성공"을 반환하고 있지만, 이는 반대입니다:

  • isFollow === false: 이미 팔로우하지 않은 상태 → 팔로우를 추가해야 함 ✅
  • isFollow === true: 이미 팔로우 중 → 에러를 반환해야 함 ✅

하지만 실제 팔로우 로직과 상태 업데이트가 누락되어 있습니다. 현재는 단순히 기존 user 객체를 반환만 하고, isFollow 상태를 true로 변경하지 않습니다.

🔎 수정 제안
 const followUserMock = http.post(`*/users/follow`, async ({ request }) => {
   const url = new URL(request.url);
   const followNickname = url.searchParams.get('followNickname');
   const user = mockUserItems.find((v) => v.nickName === followNickname);
+
+  if (!user) {
+    return HttpResponse.json(
+      createMockErrorResponse({
+        status: 404,
+        detail: '존재하지 않는 사용자입니다.',
+        errorCode: 'USER_NOT_FOUND',
+      }),
+      { status: 404 },
+    );
+  }

-  if (user?.isFollow === false) {
+  if (user.isFollow === false) {
+    // 팔로우 상태 업데이트 (실제로는 mockUserItems 업데이트 필요)
     return HttpResponse.json(
-      createMockSuccessResponse({
-        ...user,
-        status: 201,
-        data: '팔로우 성공',
-      }),
+      createMockSuccessResponse('팔로우 성공'),
+      { status: 201 },
     );
   }

-  if (user?.isFollow === null) {
+  if (user.nickName === mockUserItems[0].nickName) {
     return HttpResponse.json(
       createMockErrorResponse({
         status: 400,
         detail: '회원 : 자기 자신을 팔로우할 수 없습니다.',
         errorCode: 'NOT_SAME_FOLLOW',
       }),
+      { status: 400 },
     );
   }

-  if (user?.isFollow === true) {
+  if (user.isFollow === true) {
     return HttpResponse.json(
       createMockErrorResponse({
         status: 400,
         detail: '회원 : 이미 팔로우 중입니다.',
         errorCode: 'ALREADY_EXIST_FOLLOW',
       }),
+      { status: 400 },
     );
   }
 });

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Member Author

Choose a reason for hiding this comment

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

해당 로직에 오류가 있었습니다.

기존 createMockSuccessReponse 함수가 아래와 같이 구성되어있기 때문에,

export const createMockSuccessResponse = <T>(data: T): CommonSuccessResponse<T> => ({
  status: 200,
  success: true,
  data,
});

아래와 같이 팔로우 성공 응답을 수정하려고 합니다.
실제 api에서 팔로우를 성공할 때 user data를 반환하지 않습니다.

  if (user?.isFollow === false) {
    return HttpResponse.json(createMockSuccessResponse('팔로우 성공'));
  }

또한, isFollow가 false일때만 팔로우 요청이 성공해야하기 때문에 이 방식이 맞습니다.

Copy link
Contributor

Choose a reason for hiding this comment

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

@Chiman2937, 설명해주셔서 감사합니다! isFollow === false가 "현재 팔로우하지 않은 상태"를 의미한다는 점과 실제 API가 팔로우 성공 시 사용자 데이터를 반환하지 않는다는 점을 이해했습니다.

제안하신 방식이 실제 API 동작과 일치하므로 적절합니다:

if (user?.isFollow === false) {
  return HttpResponse.json(createMockSuccessResponse('팔로우 성공'));
}

한 가지 추가로 고려할 점은, 현재 mock은 mockUserItems 배열의 isFollow 상태를 업데이트하지 않기 때문에, 동일 테스트 내에서 연속적으로 팔로우 상태를 확인하는 경우 이전 상태가 유지됩니다. 테스트 시나리오에서 상태 지속성이 필요하지 않다면 현재 구현으로 충분합니다.


✏️ Learnings added
Learnt from: Chiman2937
Repo: WeGo-Together/WeGo_FrontEnd PR: 230
File: src/mock/service/user/user-handler.ts:8-43
Timestamp: 2025-12-23T09:36:26.773Z
Learning: In the WeGo_FrontEnd repository's user mock handlers (src/mock/service/user/user-handler.ts), the follow API mock should return only a success message without user data using createMockSuccessResponse('팔로우 성공'), as the actual API doesn't return user data on successful follow.

Learnt from: Chiman2937
Repo: WeGo-Together/WeGo_FrontEnd PR: 230
File: src/mock/service/user/user-handler.ts:8-43
Timestamp: 2025-12-23T09:36:26.774Z
Learning: In the WeGo_FrontEnd repository, isFollow === false represents "not currently following" state, meaning a follow action is allowed and should succeed.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


// 2. 유저 프로필 변경 모킹
const updateUserItemMock = http.patch(`*/users/profile`, async ({ request }) => {
const body = (await request.json()) as User;
return HttpResponse.json(
createMockSuccessResponse({
...mockUserItems[0],
...body,
}),
);
});

// 4. 알림 설정 변경 모킹
const updateMyNotificationMock = http.patch(`*/users/notification`, async ({ request }) => {
const url = new URL(request.url);
const isNotificationEnabled = url.searchParams.get('isNotificationEnabled');

return HttpResponse.json(
createMockSuccessResponse({
...mockUserItems[0],
isNotificationEnabled,
}),
);
});
Comment on lines 50 to 61
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

쿼리 파라미터의 타입 변환이 필요합니다.

Line 59에서 url.searchParams.get('isNotificationEnabled')string | null을 반환하지만, User 타입의 isNotificationEnabledboolean이어야 합니다. 이로 인해 타입 불일치가 발생합니다.

🔎 수정 제안
 const updateMyNotificationMock = http.patch(`*/users/notification`, async ({ request }) => {
   const url = new URL(request.url);
-  const isNotificationEnabled = url.searchParams.get('isNotificationEnabled');
+  const isNotificationEnabled = url.searchParams.get('isNotificationEnabled') === 'true';

   return HttpResponse.json(
     createMockSuccessResponse({
       ...mockUserItems[0],
       isNotificationEnabled,
     }),
   );
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 4. 알림 설정 변경 모킹
const updateMyNotificationMock = http.patch(`*/users/notification`, async ({ request }) => {
const url = new URL(request.url);
const isNotificationEnabled = url.searchParams.get('isNotificationEnabled');
return HttpResponse.json(
createMockSuccessResponse({
...mockUserItems[0],
isNotificationEnabled,
}),
);
});
// 4. 알림 설정 변경 모킹
const updateMyNotificationMock = http.patch(`*/users/notification`, async ({ request }) => {
const url = new URL(request.url);
const isNotificationEnabled = url.searchParams.get('isNotificationEnabled') === 'true';
return HttpResponse.json(
createMockSuccessResponse({
...mockUserItems[0],
isNotificationEnabled,
}),
);
});
🤖 Prompt for AI Agents
In src/mock/service/user/user-handler.ts around lines 56 to 67, the handler
reads isNotificationEnabled with url.searchParams.get which returns string|null
but the User type expects a boolean; convert the query value to a boolean
(handle null/default) before merging into the mock user. Parse the string
explicitly (e.g., compare to 'true' or use a safe boolean parse), provide a
sensible default when null, and assign the resulting boolean to
isNotificationEnabled so the response matches the User type.


// 5. 유저 프로필 조회 모킹
const getUserItemMock = http.get(`*/users/:userId`, ({ params }) => {
const id = Number(params.userId);
const user = mockUserItems.find((item) => item.userId === id);
Expand All @@ -23,39 +85,80 @@ const getUserItemMock = http.get(`*/users/:userId`, ({ params }) => {
return HttpResponse.json(createMockSuccessResponse(user));
});

// 7. 닉네임 중복 검사 모킹
const getNicknameAvailabilityMock = http.get(`*/users/nickname/availability`, ({ request }) => {
const url = new URL(request.url);
const nickname = url.searchParams.get('nickname');
const user = mockUserItems.find((item) => item.nickName === nickname);
return HttpResponse.json(
createMockSuccessResponse({
data: { available: !user },
}),
);
});
Comment on lines +82 to +92
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

응답 구조가 API 타입 정의와 일치하지 않습니다.

Line 94-96에서 { data: { available: !user } } 형태로 중첩된 객체를 반환하고 있지만, createMockSuccessResponse는 이미 { status, success, data } 구조를 생성합니다. 이로 인해 최종 응답이 { status: 200, success: true, data: { data: { available: boolean } } } 형태가 되어 불필요한 중첩이 발생합니다.

🔎 수정 제안
 const getNicknameAvailabilityMock = http.get(`*/users/nickname/availability`, ({ request }) => {
   const url = new URL(request.url);
-  const nickname = url.searchParams.get('nickname');
+  const nickName = url.searchParams.get('nickName');
-  const user = mockUserItems.find((item) => item.nickName === nickname);
+  const user = mockUserItems.find((item) => item.nickName === nickName);
   return HttpResponse.json(
-    createMockSuccessResponse({
-      data: { available: !user },
-    }),
+    createMockSuccessResponse({ available: !user }),
   );
 });

참고: 파라미터 이름도 nickname에서 nickName으로 변경하여 API 서비스 코드(Line 48 in src/api/service/user-service/index.ts)와 일치시켰습니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 7. 닉네임 중복 검사 모킹
const getNicknameAvailabilityMock = http.get(`*/users/nickname/availability`, ({ request }) => {
const url = new URL(request.url);
const nickname = url.searchParams.get('nickname');
const user = mockUserItems.find((item) => item.nickName === nickname);
return HttpResponse.json(
createMockSuccessResponse({
data: { available: !user },
}),
);
});
// 7. 닉네임 중복 검사 모킹
const getNicknameAvailabilityMock = http.get(`*/users/nickname/availability`, ({ request }) => {
const url = new URL(request.url);
const nickName = url.searchParams.get('nickName');
const user = mockUserItems.find((item) => item.nickName === nickName);
return HttpResponse.json(
createMockSuccessResponse({ available: !user }),
);
});
🤖 Prompt for AI Agents
In src/mock/service/user/user-handler.ts around lines 88 to 98, the mock handler
returns an extra nested "data" object and uses the wrong query param name;
change the search param from 'nickname' to 'nickName' and pass the availability
boolean directly to createMockSuccessResponse (i.e., call
createMockSuccessResponse({ data: { available: !user } }) should be replaced so
createMockSuccessResponse receives data: { available: !user } without adding
another data wrapper), ensuring the final response shape matches { status,
success, data: { available: boolean } }.


// 8. 본인 프로필 조회 모킹
const getMeItemMock = http.get(`*/users/me`, () => {
const id = 1;
const user = mockUserItems.find((item) => item.userId === id);
return HttpResponse.json(createMockSuccessResponse(user));
});

const updateUserItemMock = http.patch(`*/users`, async ({ request }) => {
const body = (await request.json()) as User;
// 7. 닉네임 중복 검사 모킹
const getEmailAvailabilityMock = http.get(`*/users/email/availability`, ({ request }) => {
const url = new URL(request.url);
const email = url.searchParams.get('email');
const user = mockUserItems.find((item) => item.email === email);
return HttpResponse.json(
createMockSuccessResponse({
...mockUserItems[0],
...body,
data: { available: !user },
}),
);
});
Comment on lines +101 to 111
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

응답 구조가 일관되지 않습니다.

Line 113-115에서 getNicknameAvailabilityMock과 동일한 불필요한 중첩 구조 문제가 있습니다.

🔎 수정 제안
-const getEmailAvailabilityMock = http.get(`*/users/email/availability`, ({ request }) => {
+const getEmailAvailabilityMock = http.get(`*/users/email/availability`, ({ request }) => {
   const url = new URL(request.url);
   const email = url.searchParams.get('email');
   const user = mockUserItems.find((item) => item.email === email);
   return HttpResponse.json(
-    createMockSuccessResponse({
-      data: { available: !user },
-    }),
+    createMockSuccessResponse({ available: !user }),
   );
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 7. 닉네임 중복 검사 모킹
const getEmailAvailabilityMock = http.get(`*/users/email/availability`, ({ request }) => {
const url = new URL(request.url);
const email = url.searchParams.get('email');
const user = mockUserItems.find((item) => item.email === email);
return HttpResponse.json(
createMockSuccessResponse({
...mockUserItems[0],
...body,
data: { available: !user },
}),
);
});
// 7. 닉네임 중복 검사 모킹
const getEmailAvailabilityMock = http.get(`*/users/email/availability`, ({ request }) => {
const url = new URL(request.url);
const email = url.searchParams.get('email');
const user = mockUserItems.find((item) => item.email === email);
return HttpResponse.json(
createMockSuccessResponse({ available: !user }),
);
});
🤖 Prompt for AI Agents
In src/mock/service/user/user-handler.ts around lines 107 to 117, the JSON
response for getEmailAvailabilityMock uses an extra/unnecessary nested structure
inconsistent with getNicknameAvailabilityMock; modify the response so the
success payload shape matches the nickname availability handler (i.e., return
createMockSuccessResponse({ data: { available: !user } }) without additional
nesting or wrappers), ensuring the final HttpResponse.json contains the same
top-level keys and structure as other availability endpoints.


const deleteUserItemMock = http.delete(`*/users`, async () => {
return HttpResponse.json(createMockSuccessResponse(null));
});
const unfollowUserMock = http.delete(`*/users/unfollow`, ({ request }) => {
const url = new URL(request.url);
const unFollowNickname = url.searchParams.get('unFollowNickname');
const user = mockUserItems.find((v) => v.nickName === unFollowNickname);

const followUserItemMock = http.post(`*/follows`, async () => {
return HttpResponse.json(createMockSuccessResponse(null));
});
if (user?.isFollow === false) {
return HttpResponse.json(
createMockSuccessResponse({
...user,
status: 200,
data: '팔로우 취소 성공',
}),
);
}

const unfollowUserItemMock = http.delete(`*/follows/:followId`, async () => {
return HttpResponse.json(createMockSuccessResponse(null));
if (user?.isFollow === null) {
return HttpResponse.json(
createMockErrorResponse({
status: 400,
detail: '회원 : 팔로우 취소 대상은 본인 될 수 없습니다.',
errorCode: 'NOT_SAME_FOLLOW',
}),
);
}

if (user?.isFollow === true) {
return HttpResponse.json(
createMockErrorResponse({
status: 400,
detail: '회원 : 팔로우 관계를 찾을 수 없습니다.',
errorCode: 'NOT_FOUND_FOLLOW',
}),
);
}
});
Comment on lines 113 to 141
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

언팔로우 상태 로직이 반대로 구현되어 있습니다.

Line 124에서 isFollow === false일 때 "팔로우 취소 성공"을 반환하지만, isFollow === false는 이미 팔로우하지 않은 상태를 의미하므로 언팔로우할 수 없습니다. 로직이 반대로 되어 있습니다:

  • isFollow === true: 현재 팔로우 중 → 언팔로우 가능 ✅
  • isFollow === false: 팔로우하지 않음 → 언팔로우 불가능 (에러)
🔎 수정 제안
 const unfollowUserMock = http.delete(`*/users/unfollow`, ({ request }) => {
   const url = new URL(request.url);
   const unFollowNickname = url.searchParams.get('unFollowNickname');
   const user = mockUserItems.find((v) => v.nickName === unFollowNickname);
+
+  if (!user) {
+    return HttpResponse.json(
+      createMockErrorResponse({
+        status: 404,
+        detail: '존재하지 않는 사용자입니다.',
+        errorCode: 'USER_NOT_FOUND',
+      }),
+      { status: 404 },
+    );
+  }

-  if (user?.isFollow === false) {
+  if (user.nickName === mockUserItems[0].nickName) {
+    return HttpResponse.json(
+      createMockErrorResponse({
+        status: 400,
+        detail: '회원 : 팔로우 취소 대상은 본인이 될 수 없습니다.',
+        errorCode: 'NOT_SAME_FOLLOW',
+      }),
+      { status: 400 },
+    );
+  }
+
+  if (user.isFollow === true) {
+    // 언팔로우 상태 업데이트 (실제로는 mockUserItems 업데이트 필요)
     return HttpResponse.json(
-      createMockSuccessResponse({
-        ...user,
-        status: 200,
-        data: '팔로우 취소 성공',
-      }),
+      createMockSuccessResponse('팔로우 취소 성공'),
     );
   }

-  if (user?.isFollow === null) {
-    return HttpResponse.json(
-      createMockErrorResponse({
-        status: 400,
-        detail: '회원 : 팔로우 취소 대상은 본인 될 수 없습니다.',
-        errorCode: 'NOT_SAME_FOLLOW',
-      }),
-    );
-  }
-
-  if (user?.isFollow === true) {
+  if (user.isFollow === false) {
     return HttpResponse.json(
       createMockErrorResponse({
         status: 400,
         detail: '회원 : 팔로우 관계를 찾을 수 없습니다.',
         errorCode: 'NOT_FOUND_FOLLOW',
       }),
+      { status: 400 },
     );
   }
 });

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/mock/service/user/user-handler.ts around lines 119 to 153, the unfollow
logic is inverted: currently it treats isFollow === false as a successful
unfollow; change it so isFollow === true returns the success response ("팔로우 취소
성공", status 200) and isFollow === false returns the appropriate error (status
400, errorCode 'NOT_FOUND_FOLLOW' with detail '회원 : 팔로우 관계를 찾을 수 없습니다.'); keep
the isFollow === null branch as the self-unfollow error (status 400, errorCode
'NOT_SAME_FOLLOW') and ensure response shapes and status codes remain
consistent.


export const userHandlers = [
followUserMock,
getUserItemMock,
updateMyNotificationMock,
getMeItemMock,
updateUserItemMock,
deleteUserItemMock,
followUserItemMock,
unfollowUserItemMock,
getNicknameAvailabilityMock,
getEmailAvailabilityMock,
unfollowUserMock,
];