← 개발 로그 목록

website: dev → main 승격, prod를 dev/stage와 동일한 스키마·기능으로 맞추기

/ 7분 분량 / 개발 로그

멋쟁이사자처럼 경희대 웹사이트 프로젝트에서 dev 브랜치를 main으로 병합하는 대규모 릴리스를 진행했습니다. 멤버 인증, 프로젝트 쇼케이스, 모집 알림 메일 등 그동안 dev/stage에만 있던 기능들을 prod와 동일한 수준으로 맞추는 작업이었습니다.

요약

이번 커밋은 dev → main 승격 PR(#161)의 머지 커밋입니다. 커밋 메시지 그대로 "dev → main 승격 — prod를 dev/stage와 동일 스키마·기능으로 (#133 준비)"가 목적을 잘 설명해줍니다. 날짜는 2026-07-23, 브랜치는 main이고, 변경된 파일이 200개가 넘고 추가 9,500줄, 삭제 1,018줄이라는 규모에서 알 수 있듯 단일 기능 하나를 위한 커밋이 아니라 그동안 dev에서 쌓인 여러 마일스톤(#117 멤버 인증, #119 프로젝트 쇼케이스, #126 모집 알림 메일, #121 각종 권한 경계 정리 등)을 한꺼번에 prod로 올리는 커밋입니다.

배경 및 목적

지금까지는 dev/stage 환경에만 새 기능이 배포되고 prod는 예전 스키마·기능 수준에 머물러 있었습니다. 이 상태가 길어지면 dev에서 검증된 기능과 실제 서비스 중인 prod 사이의 간극이 커지고, 다음 마일스톤(#133)을 준비하려면 이 간극부터 없애야 했습니다. 그래서 이번 머지는 "새 기능을 추가한다"기보다는 "이미 dev/stage에서 검증된 것들을 prod 스키마·코드베이스에 맞춰 넣는다"는 성격이 강합니다.

구현 내용

변경된 파일 목록을 보면 크게 네 갈래로 나뉩니다.

1. 백엔드 도메인 확장
member, project, recruitment, feed 패키지 전반에 걸쳐 새 엔티티·서비스·컨트롤러가 대거 추가됐습니다.

  • MemberAuthController/MemberAuthService — 학번 기반 멤버 로그인, 리프레시 토큰, 첫 로그인 비밀번호 강제 변경
  • ProjectController/ProjectService + ProjectImage, ProjectParticipant — 프로젝트 쇼케이스 CRUD와 참여자 관리
  • RecruitmentManagementController/RecruitmentOpenEmailEventListener — 모집 열림 전이 시 구독자 대상 메일 발송
  • MagicLinkTokenService 등 매직링크 기반 익명 글쓰기 토큰 발급/검증

2. 테스트 코드 대폭 보강
상위 5개 변경 파일 중 3개가 테스트 코드일 정도로 이번 커밋은 테스트에 공을 들였습니다.

  • ProjectControllerTest.java (+565줄): 목록/상세/생성/수정/삭제/숨김 처리 전체를 커버하며, 특히 "공동 참여자가 만든 사람이 아니어도 수정 가능한지", "참여자 중복 등록을 막는지" 같은 세밀한 케이스까지 확인합니다.
  • MemberAuthControllerTest.java (+393줄): 로그인 잠금(5회 실패 시 423), 리프레시 토큰 만료/변조, 비밀번호 변경 시 정책 검증 등을 다룹니다.
  • RecruitmentOpenEmailHttpEndToEndIntegrationTest.java (+241줄): Mailpit 컨테이너를 띄워 실제 SMTP로 메일이 나가는지, 동시 요청이 와도 중복 발송 없이 정확히 1통씩 가는지를 검증하는 E2E 테스트입니다.

3. STATE_SPACE_QA.md 신설 (+248줄)
이번 커밋에서 눈에 띄는 문서입니다. "상태공간트리 + 백트래킹" 방식으로 테스트 케이스를 뽑아낸 과정을 정리했는데, 단순 카티션 곱이 아니라 "부모 조건에 따라 자식 축 자체가 생기거나 사라진다"는 점을 트리로 표현합니다. 예를 들어 익명 사용자에게는 mustChangePassword라는 축 자체가 존재하지 않는다는 걸 트리 구조로 가지치기했습니다. 이 문서 안에는 실제로 리뷰 과정에서 발견한 버그 4건도 기록돼 있는데, 그중 하나가 MemberPasswordGuardFilter가 원래 "경로가 /api/member/인가"만 보다가 /api/projects 같은 네임스페이스 밖 엔드포인트는 놓치고 있었다는 것이었습니다. 이걸 "쓰기 메서드(POST/PUT/PATCH/DELETE)인가"로 일반화해서 해결했다고 문서에 남겨뒀습니다.

4. pm/qa/criteria/일반사용자.yaml 신설 (+932줄)
Given-When-Then 형식으로 익명 방문자 관점의 성공 기준을 정리한 초대형 문서입니다. 랜딩, 모집 알림, 블로그, 프로젝트, 멤버·운영진, 권한 경계까지 전 영역을 status: dev / pr-### / spec-only로 분류해서 "지금 테스트 가능한지"와 "스펙만 있고 미구현인지"를 명확히 구분해뒀습니다.

기술적 의사결정

상태공간트리 기반 QA 설계
카티션 곱으로 테스트 케이스를 뽑으면 "익명인데 토큰이 유효함" 같은 존재할 수 없는 조합까지 기계적으로 생겨버립니다. 그래서 트리 구조로 "부모가 정해지면 자식 축 자체가 달라진다"는 원칙을 세웠습니다. STATE_SPACE_QA.md에 나온 대로, 원래는 Actor(익명/멤버/어드민) 밑에 로그인/로그아웃/리프레시 엔드포인트를 전부 몰아넣었다가, 코드를 다시 보니 로그인/로그아웃/리프레시는 access_token이 아니라 refresh_token 쿠키만 보는 완전히 다른 자격증명 축이라는 걸 깨닫고 트리를 A(리소스 접근)와 B(인증 모듈 자기 자신)로 쪼갰다는 시행착오가 문서에 그대로 남아 있습니다.

Testcontainers + Mailpit으로 실제 메일 발송 검증
모집 알림 메일 기능은 단순히 EmailService를 목(mock)으로 처리해서 "호출됐는지"만 확인하는 게 아니라, 실제 SMTP 서버(Mailpit)를 컨테이너로 띄우고 진짜 메일이 도착하는지, 동시 요청 시 락이 제대로 걸려 중복 발송이 안 되는지까지 검증했습니다. 이 과정에서 메일 본문의 링크가 어드민 도메인(admin.likelion-khu.com)이 아니라 공개 사이트 도메인을 가리켜야 한다는 리뷰 지적도 반영돼서, frontend-base-url과 public-site-url 설정을 분리했습니다.

배운 점 및 개선점

이번 작업에서 가장 인상 깊었던 부분은 QA 문서 자체가 코드 변경 이력을 함께 담고 있다는 점입니다. STATE_SPACE_QA.md에는 "이 트리가 실제로 잡아낸 버그 4건"이라는 섹션이 있는데, 여기에는 버그를 고친 결과뿐 아니라 "왜 이 축을 트리에 추가하게 됐는지"까지 기록돼 있습니다. 예를 들어 참여자가 1명뿐인 프로젝트로만 테스트하다 보니 "참여자 집합에 있는가"를 검증해야 할 로직이 실수로 "첫 참여자인가"로 짜여 있어도 안 드러나는 상태였다는 지적은, 테스트 커버리지의 편향을 스스로 찾아낸 좋은 사례였습니다.

다음 단계로는 문서에 spec-only로 표시된 항목들(지원폼, 최고관리자 승계, 운영진 소개 관리 등)을 실제 구현으로 옮기는 작업이 남아 있고, 일반사용자.yaml에 적힌 몇 가지 갭(모집 알림 재발송 rate limit 부재, 이메일 대소문자 정규화 누락 등)도 이슈화가 필요한 상태입니다.

참고 자료

  • backend/src/test/STATE_SPACE_QA.md — 상태공간트리 QA 방법론 전체
  • pm/qa/GUIDE.md — QA 문서 작성 규칙
  • pm/qa/criteria/일반사용자.yaml — 익명 사용자 관점 성공 기준