Skip to content

test: pin who can see which event across every read path - #27

Merged
sexypoo merged 1 commit into
mainfrom
agent/16-visibility-matrix-test
Oct 14, 2026
Merged

sexypoo merged 1 commit into
mainfrom
agent/16-visibility-matrix-test

Conversation

@sexypoo

@sexypoo sexypoo commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

변경 요약

공개 범위(비공개·그룹·친구·전체) 경계를 자동 테스트로 고정합니다 (ROADMAP SEC-010). 테스트만 추가했고, 테스트 중 실제 누출은 발견되지 않아 프로덕션 코드는 바꾸지 않았습니다.

api/src/test/java/app/toogeduler/web/VisibilityMatrixTest.java (@SpringBootTest + MockMvc + H2, 실제 JWT로 인증)

  • 일정 주인이 PRIVATE, GROUP(그룹 A), FRIENDS, PUBLIC, GROUP+FRIENDS 일정 다섯 개를 가진다. 제목·메모·장소에 [SECRET-<종류>] 표식을 넣어, 응답 본문에 표식이 있으면 노출로 판정한다.
  • 보는 사람 8명: 본인 / 같은 그룹 멤버 / 수락된 친구 / 친구 요청 대기 중 / 그룹에서 내보내진 사람(실제 DELETE /api/groups/{id}/members/{userId}로 내보냄) / 다른 그룹(B) 멤버 / 무관한 로그인 사용자 / 비로그인
  • 조회 경로 8개: GET /api/events, GET /api/events/{id}, GET /api/groups/{A}/events, GET /api/groups/{B}/events, GET /api/friends/{owner}/events, GET /api/public/events, GET /api/public/events/{token}, GET /api/groups/{A}/availability
  • viewerSeesOnlyWhatTheVisibilityAllows: 8×8 = 64개 조합 파라미터화. 각 조합마다 HTTP 상태, 보여야 할 일정의 제목·메모·장소, 나머지 일정 표식이 없는지, 주인 이메일이 없는지를 확인한다. 기대값 표는 클래스 Javadoc에 있다.
  • nobodyButTheOwnerCanChangeAnEvent: 주인이 아닌 7명 × PUT/PATCH move/DELETE = 21개 조합. 모든 일정에 대해 4xx(비로그인 401, 그 외 403)가 나오고 DB의 제목·시작시각·공개 범위가 그대로인지 확인한다.
  • 가용시간: 14~15시 비공개 일정 시간대에 주인은 availablePeople에서 빠지기만 하고("바쁨"), 응답에 일정 내용은 없다.

발견한 것

  • 누출 없음. 모든 완료 조건을 현재 코드가 만족한다.
  • 참고(누출 아님): 수락된 친구가 한 명이라도 있으면 본인이 GET /api/friends/{내 id}/events로 "친구에게 보이는 내 캘린더"를 볼 수 있다(acceptedFriendship이 내 쪽 id와도 일치하기 때문). 본인 일정이라 매트릭스에 그대로 기록만 했다.
  • 참고: 비공개 일정은 그룹 캘린더(/api/groups/{id}/events)에서 "바쁨" 블록으로도 보이지 않고 아예 빠진다. 가용시간에서만 바쁨으로 반영된다. 이슈의 "바쁨으로만 보임"보다 더 엄격한 동작이라 누출이 아니며, 정책 변경은 범위 밖이라 그대로 뒀다.

검증

  • cd api && ./mvnw -B -q test -Dtest=VisibilityMatrixTest: 85개 통과 (64 + 21)
  • cd api && ./mvnw -B -q test 전체: 137개 중 136개 통과. 실패한 1개는 기존 GroupEventAnnouncementTest이고 이 PR과 무관하다. origin/main에서 그 테스트만 단독으로 돌려도 로컬(JDK 24)에서 실패하는데, CI(JDK 21)의 main에서는 통과한다. event.getGroups()(HashSet) 순회 순서에 따라 메시지에 들어가는 그룹 이름이 달라지는 것으로 보인다. CI 결과로 확인해야 한다.
  • cd web && npm ci && npm run typecheck && npm run build: 통과 (web은 변경 없음)

완료 조건

  • VisibilityMatrixTest.java에 보는 사람 × 조회 경로 조합이 파라미터화 테스트로 존재한다
  • PRIVATE 일정은 본인 외 모든 경로에서 제목·메모·장소가 노출되지 않는다(가용시간에서는 바쁨으로만 반영)
  • GROUP 일정은 해당 그룹 멤버에게만 상세가 보이고, 내보내진 멤버·다른 그룹 멤버에게는 보이지 않는다
  • FRIENDS 일정은 수락된 친구에게만 보이고, 요청 대기 중인 사람에게는 보이지 않는다
  • PUBLIC 일정만 /api/public/**에 나오며, 응답에 소유자 이메일이 포함되지 않는다
  • 남의 일정에 대한 PUT/PATCH move/DELETE/GET /api/events/{id}는 4xx를 반환한다
  • ./mvnw test가 CI에서 통과한다: 이 PR의 85개 테스트는 CI에서 모두 통과했지만, 기존의 간헐적 실패 테스트 GroupEventAnnouncementTest 때문에 api 잡이 빨간색이다. 별도 이슈 #28로 분리했다.

Closes #16

🤖 Generated with Claude Code

Add VisibilityMatrixTest: 8 viewers (owner, group member, accepted friend,
pending requester, kicked member, other-group member, stranger, anonymous)
x 8 read paths, plus PUT/move/DELETE by every non-owner. No leak found.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sexypoo sexypoo added the agent 에이전트가 만든 PR label Oct 14, 2026
@vercel

vercel Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
toogeduler Ready Ready Preview Oct 14, 2026 12:33am UTC

@sexypoo sexypoo left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

QA 판정: HUMAN (1라운드)

CI

  • web: 통과 / Vercel: 통과 (web 변경 없음)
  • api: 실패. 137개 중 1개 실패: GroupEventAnnouncementTest.otherMembersAreNotifiedOnceAndTheOwnerIsNot. 이 PR의 VisibilityMatrixTest 85개는 CI에서 모두 통과함.

실패가 이 PR과 무관한가: 그렇다 (원인 기준)

  • 이 PR은 테스트 파일 1개(VisibilityMatrixTest.java)만 추가하고, 프로덕션 코드와 기존 테스트는 건드리지 않음.
  • GroupEventAnnouncementTest는 Spring 컨텍스트·DB 없이 Mockito mock만 쓰는 순수 단위 테스트라 새 테스트와 공유하는 상태가 없음.
  • CI 로그상 이준(2L)과 박소라(3L)에게 알림이 정확히 2건 저장됐고, 문구 검증만 실패함. NotificationService.announceGroupEvent가 event.getGroups()(HashSet)를 순회하는데 Group이 equals/hashCode를 재정의하지 않아 identity hash 순서를 따름 → 이준이 "소친친친" 문구를 받으면 실패. #28 분석과 일치.
  • QA가 직접 origin/main(이 PR 없음)에서 이 테스트만 3번 돌렸더니 3번 모두 실패함 (로컬 JDK 24). main 코드 자체의 결함임.
  • 주의: identity hash는 같은 JVM에서 그 전까지 만든 객체 수에 따라 달라지기 때문에, 테스트 클래스가 하나 추가된 것만으로 CI에서 결과가 뒤집혔을 가능성이 높음. 원인은 아니지만 이 PR을 지금 머지하면 main CI가 빨간색이 될 가능성이 높다.

완료 조건 (6/7)

  1. 보는 사람 × 조회 경로 파라미터화 테스트: 충족 (8×8=64, 이슈의 7명·7경로에 다른 그룹 멤버·그룹 B 경로 추가)
  2. PRIVATE 비노출: 충족. 단, 그룹 캘린더에서는 "바쁨"이 아니라 아예 빠짐 (이슈 문구보다 엄격, PR 본문에 기록됨)
  3. GROUP 일정: 충족 (내보내기는 실제 DELETE API로 수행, 그룹 B 멤버 확인)
  4. FRIENDS 일정: 충족 (요청 대기 중인 사람 403)
  5. PUBLIC만 /api/public/**, 주인 이메일 미노출: 충족 (모든 200 응답에 이메일 검사)
  6. 남의 일정 PUT/PATCH move/DELETE/GET 4xx: 충족 (7명 × 3 × 5개 일정, DB 불변 확인)
  7. ./mvnw test CI 통과: 미충족 (#28 때문)

코드 리뷰

  • 보호 경로 변경 없음, 229줄(400 미만), needs-human 라벨 없음.
  • 기존 테스트 삭제·비활성화 없음. 별도 H2 DB 이름을 써서 다른 테스트와 데이터가 섞이지 않음.
  • 사소한 참고(머지 차단 아님): 대기 중 친구 요청은 "상대가 주인에게 보낸" 방향만 있고 "주인이 보낸" 방향은 없음. 후속 보강 거리.

HUMAN인 이유

PR 코드는 문제없지만 CI가 빨간색이고, 그 해결(#28)은 알림 문구 정책(여러 그룹에 속한 사람에게 어느 그룹 이름을 쓸지)에 대한 PM 결정이 필요해서 이 PR 안에서 고칠 일이 아님. 아래 중 사람이 선택해야 함:

  • (권장) #28을 먼저 고친 뒤 이 PR을 main에 맞춰 다시 돌리고, 초록색이면 PASS 처리
  • 또는 main이 빨간색이 되는 것을 감수하고 지금 머지
  • 덧붙여, 비공개 일정이 그룹 캘린더에서 "바쁨"으로 보이지 않고 빠지는 현재 동작을 정책으로 받아들일지도 확인 필요

@sexypoo sexypoo added the needs-human 사람 판단·검토 필요 (자동 머지 금지) label Oct 14, 2026
@sexypoo

sexypoo commented Oct 14, 2026

Copy link
Copy Markdown
Owner Author

🤖 오케스트레이터: QA 판정이 HUMAN이라 자동 머지하지 않았습니다.

  • 이 PR의 테스트(85개)는 모두 통과했습니다. CI api 잡이 실패한 원인은 기존 GroupEventAnnouncementTest입니다. NotificationService.announceGroupEvent가 HashSet을 순회해서 실행할 때마다 결과가 달라지며, #28로 분리해 두었습니다.
  • #28을 고치려면 여러 그룹에 속한 멤버에게 보낼 알림 문구를 정해야 해서, 사람의 판단이 필요합니다.
  • 권장 순서: #28을 해결합니다 → 이 PR을 main 기준으로 다시 CI에 돌립니다 → 초록색이면 머지합니다.
  • 함께 확인해 주세요: 지금은 비공개 일정이 그룹 캘린더에서 아예 빠지고, 가용시간에서만 '바쁨'으로 반영됩니다. 이 동작을 정책으로 받아들일지 정해 주세요.

@sexypoo

sexypoo commented Oct 14, 2026

Copy link
Copy Markdown
Owner Author

결정 (고은님 위임 → Claude)

비공개 일정은 그룹 캘린더에서 아예 보이지 않고, "함께 되는 시간" 계산에서만 바쁨으로 반영한다를 정책으로 확정합니다.

🤖 Generated with Claude Code

@sexypoo
sexypoo merged commit d336f52 into main Oct 14, 2026
3 of 4 checks passed

This branch was successfully deployed

1 active deployment
Preview — 30070908 Deployed Oct 14, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent 에이전트가 만든 PR needs-human 사람 판단·검토 필요 (자동 머지 금지)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

내 비공개 일정이 친구·그룹·공개 화면 어디에서도 새지 않는다 (공개 범위 권한 테스트)

1 participant