Skip to content

refactor: 호스트 대학교 이미지 업로드 multipart/form-data 방식으로 전환#571

Merged
whqtker merged 2 commits into
mainfrom
fix/host-university-multipart-upload
Jun 22, 2026
Merged

refactor: 호스트 대학교 이미지 업로드 multipart/form-data 방식으로 전환#571
whqtker merged 2 commits into
mainfrom
fix/host-university-multipart-upload

Conversation

@whqtker

@whqtker whqtker commented Jun 22, 2026

Copy link
Copy Markdown
Member

Summary

  • 기존: 파일 선택 시 /file/admin/university/logo, /file/admin/university/background 엔드포인트로 즉시 업로드 후 URL을 form state에 저장
  • 변경: 파일 선택 시 로컬 state에 보관 → 폼 submit 시 multipart/form-data로 request JSON + 파일을 함께 전송
  • 백엔드 리팩토링(#782)에서 삭제된 파일 업로드 전용 엔드포인트 대신 대학 생성/수정 API에 통합된 새 방식 적용

주요 변경사항

  • admin.ts: HostUniversityPayload에서 logoImageUrl, backgroundImageUrl 제거; createHostUniversity, updateHostUniversity 함수가 multipart/form-data 방식으로 변경; 구 업로드 전용 함수(uploadAdminUniversityLogo, uploadAdminUniversityBackground) 제거
  • HostUniversityTab.tsx: 이미지 파일을 로컬 state로 보관 후 submit 시 전달; 파일 선택 시 URL.createObjectURL()로 즉시 미리보기 표시; create 모드에서 파일 미선택 시 에러 toast 표시
  • 테스트 업데이트: 새로운 동작(즉시 업로드 없음, submit 시 파일 전달) 기준으로 재작성

Test plan

  • 호스트 대학교 생성: 파일 선택 시 미리보기 확인, submit 시 정상 생성 확인
  • 호스트 대학교 수정: 기존 이미지 미리보기 표시, 신규 파일 선택 시 교체 확인
  • create 모드에서 이미지 미선택 후 submit 시 에러 toast 표시 확인
  • 단위 테스트 통과 확인

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jun 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
solid-connect-university-web Ready Ready Preview, Comment Jun 22, 2026 4:20pm
solid-connect-web-admin Ready Ready Preview, Comment Jun 22, 2026 4:20pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
solid-connection-web Skipped Skipped Jun 22, 2026 4:20pm

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@whqtker, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 42 minutes and 47 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 31bf9e46-fb22-4115-ab39-46b00d6d96f8

📥 Commits

Reviewing files that changed from the base of the PR and between 67a2977 and ae0bb8e.

📒 Files selected for processing (1)
  • apps/admin/src/components/features/univ-apply-infos/tabs/HostUniversityTab.tsx

Walkthrough

  1. API 계약 변경

    • HostUniversityPayload에서 logoImageUrl/backgroundImageUrl 필드가 제거되었습니다.
    • uploadAdminUniversityLogo, uploadAdminUniversityBackground 메서드가 삭제되었습니다.
    • createHostUniversity/updateHostUniversityFormData (multipart) 전송 방식으로 변경되었습니다.
  2. 폼 상태 타입 교체

    • HostUniversityFormState가 도입되어 logoImageUrl/backgroundImageUrl 프리뷰 URL을 포함합니다.
    • EMPTY_FORM, detailToForm, toPayload 변환 함수가 새 타입 기준으로 수정되었습니다.
  3. 파일 선택 즉시 프리뷰 + 제출 시 전송 방식 도입

    • 파일 선택 시 URL.createObjectURL로 프리뷰 URL을 생성하고 폼 상태에 즉시 반영합니다.
    • 생성 모드에서 로고/배경 파일이 없으면 에러 토스트를 표시하고 제출을 중단합니다.
    • 제출 버튼의 disabled 조건에서 isUploading 의존성이 제거되었습니다.
  4. 테스트 재구성

    • URL 전역 stub 설정/해제 로직이 추가되었습니다.
    • 기존 업로드 API 호출 검증 테스트가 프리뷰/제출/인자 검증 테스트로 교체되었습니다.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • wibaek
  • manNomi
  • enunsnv
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 주요 변경사항을 명확하게 요약합니다: 호스트 대학교 이미지 업로드를 multipart/form-data 방식으로 전환하는 리팩토링.
Description check ✅ Passed PR 설명이 변경사항의 요약, 주요 수정 파일들, 테스트 계획을 포함하여 필수 정보를 대부분 다룹니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/host-university-multipart-upload

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
apps/admin/src/lib/api/admin.test.ts (2)

23-43: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

2) request 파트는 MIME 타입뿐 아니라 JSON 본문도 검증해 주세요.

지금은 application/json 타입만 확인해서, 직렬화 필드 누락/오타 회귀를 잡기 어렵습니다. Blob.text()로 파싱 후 핵심 필드를 함께 검증하는 편이 안전합니다.

diff 제안
 it("createHostUniversity sends multipart/form-data with request JSON blob and files", async () => {
   post.mockResolvedValue({ data: { id: 1, koreanName: "테스트 대학교" } });
@@
   const requestBlob = formData.get("request") as Blob;
   expect(requestBlob.type).toBe("application/json");
+  await expect(requestBlob.text()).resolves.toBe(JSON.stringify(request));
 });

 it("updateHostUniversity sends multipart/form-data with optional files", async () => {
@@
   const formData = put.mock.calls[0]?.[1] as FormData;
   expect(formData.get("logoFile")).toBe(logoFile);
   expect(formData.get("backgroundFile")).toBeNull();
+  const requestBlob = formData.get("request") as Blob;
+  expect(requestBlob.type).toBe("application/json");
+  await expect(requestBlob.text()).resolves.toBe(JSON.stringify(request));
 });

Also applies to: 45-62

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/admin/src/lib/api/admin.test.ts` around lines 23 - 43, The test for
createHostUniversity currently only validates the MIME type of the request blob
but does not validate the actual JSON content, which could miss serialization
errors like missing or misspelled fields. After retrieving the requestBlob from
FormData in the test, use the Blob.text() method to parse the blob content, then
parse it as JSON and validate that the critical fields (koreanName, englishName,
formatName, countryCode, regionCode) from the original request object are
correctly present in the JSON with their expected values.

64-79: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

1) 세 번째 update 테스트에도 호출 경로 계약 검증을 고정해 주세요.

현재 케이스는 FormData 내부 키만 확인해서, 잘못된 URL로 put이 호출되는 회귀를 놓칠 수 있습니다. 호출 경로/인자 검증을 추가해 주세요.

diff 제안
 it("updateHostUniversity omits both file parts when no files are provided", async () => {
   put.mockResolvedValue({ data: { id: 1 } });
   const request = {
     koreanName: "대학교",
     englishName: "University",
     formatName: "U",
     countryCode: "JP",
     regionCode: "ASIA",
   };

   await adminApi.updateHostUniversity(1, request);

+  expect(put).toHaveBeenCalledWith("/admin/host-universities/1", expect.any(FormData));
   const formData = put.mock.calls[0]?.[1] as FormData;
   expect(formData.get("logoFile")).toBeNull();
   expect(formData.get("backgroundFile")).toBeNull();
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/admin/src/lib/api/admin.test.ts` around lines 64 - 79, The test
"updateHostUniversity omits both file parts when no files are provided" only
validates the FormData contents but does not verify that the put method was
called with the correct URL path and arguments. Add an assertion to validate the
first argument (the URL/endpoint) passed to the put mock call, similar to how
other update tests validate the call path, to prevent regressions where the
request goes to the wrong endpoint. Use put.mock.calls[0]?.[0] to access the URL
argument and add an expect assertion to verify it matches the expected endpoint
for updating a host university with ID 1.
apps/admin/src/components/features/univ-apply-infos/tabs/HostUniversityTab.test.tsx (1)

57-64: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

waitFor + getByRole + toBeTruthy() 대신 findByRole 사용을 권장합니다.

  1. getByRole은 요소를 찾지 못하면 예외를 던지므로 toBeTruthy() 단언은 불필요합니다.
  2. findByRolewaitFor + getByRole을 결합한 것으로, 더 간결하고 관용적인 패턴입니다.
♻️ 권장 수정
 it("shows logo preview immediately after file selection without calling the API", async () => {
   await openCreateModal();
   const file = new File(["logo"], "logo.png", { type: "image/png" });
   fireEvent.change(screen.getByLabelText("로고 이미지 파일"), { target: { files: [file] } });

-  await waitFor(() => expect(screen.getByRole("img", { name: "로고 미리보기" })).toBeTruthy());
+  await screen.findByRole("img", { name: "로고 미리보기" });
   expect(toastError).not.toHaveBeenCalled();
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/admin/src/components/features/univ-apply-infos/tabs/HostUniversityTab.test.tsx`
around lines 57 - 64, The test is using an inefficient pattern with waitFor
combined with getByRole and toBeTruthy assertion. Replace the waitFor with
getByRole pattern (line with "await waitFor(() => expect(screen.getByRole("img",
{ name: "로고 미리보기" })).toBeTruthy())") with a direct call to screen.findByRole
instead, which is the idiomatic approach in React Testing Library that combines
waiting and querying in one step. The findByRole method will automatically wait
for the element to appear and throw if it does not, making the explicit
toBeTruthy assertion unnecessary.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/admin/src/components/features/univ-apply-infos/tabs/HostUniversityTab.tsx`:
- Around line 205-215: The handleLogoChange and handleBackgroundChange functions
create blob URLs using URL.createObjectURL without ever revoking them, causing
memory leaks. In each handler, check if the form already has an existing blob
URL (logoImageUrl or backgroundImageUrl respectively) and revoke it using
URL.revokeObjectURL before creating a new one. Additionally, ensure that when
the modal closes in the closeModal function or when the component unmounts, any
remaining blob URLs for both logo and background images are properly revoked to
clean up memory. Consider adding a useEffect cleanup function to handle
unmounting scenarios as well.

---

Nitpick comments:
In
`@apps/admin/src/components/features/univ-apply-infos/tabs/HostUniversityTab.test.tsx`:
- Around line 57-64: The test is using an inefficient pattern with waitFor
combined with getByRole and toBeTruthy assertion. Replace the waitFor with
getByRole pattern (line with "await waitFor(() => expect(screen.getByRole("img",
{ name: "로고 미리보기" })).toBeTruthy())") with a direct call to screen.findByRole
instead, which is the idiomatic approach in React Testing Library that combines
waiting and querying in one step. The findByRole method will automatically wait
for the element to appear and throw if it does not, making the explicit
toBeTruthy assertion unnecessary.

In `@apps/admin/src/lib/api/admin.test.ts`:
- Around line 23-43: The test for createHostUniversity currently only validates
the MIME type of the request blob but does not validate the actual JSON content,
which could miss serialization errors like missing or misspelled fields. After
retrieving the requestBlob from FormData in the test, use the Blob.text() method
to parse the blob content, then parse it as JSON and validate that the critical
fields (koreanName, englishName, formatName, countryCode, regionCode) from the
original request object are correctly present in the JSON with their expected
values.
- Around line 64-79: The test "updateHostUniversity omits both file parts when
no files are provided" only validates the FormData contents but does not verify
that the put method was called with the correct URL path and arguments. Add an
assertion to validate the first argument (the URL/endpoint) passed to the put
mock call, similar to how other update tests validate the call path, to prevent
regressions where the request goes to the wrong endpoint. Use
put.mock.calls[0]?.[0] to access the URL argument and add an expect assertion to
verify it matches the expected endpoint for updating a host university with ID
1.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7774a1cb-c028-4860-8ab1-40fe272eedd7

📥 Commits

Reviewing files that changed from the base of the PR and between 942f35d and 67a2977.

📒 Files selected for processing (4)
  • apps/admin/src/components/features/univ-apply-infos/tabs/HostUniversityTab.test.tsx
  • apps/admin/src/components/features/univ-apply-infos/tabs/HostUniversityTab.tsx
  • apps/admin/src/lib/api/admin.test.ts
  • apps/admin/src/lib/api/admin.ts

@vercel vercel Bot temporarily deployed to Preview – solid-connection-web June 22, 2026 16:19 Inactive
@whqtker whqtker self-assigned this Jun 22, 2026
@whqtker whqtker merged commit 2db882f into main Jun 22, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant