From 26d19ee942f98301894a6eaf7d9c5f8f10b9476a Mon Sep 17 00:00:00 2001 From: "sunzx0428@gmail.com" <57030648+sunzx0428@users.noreply.github.com> Date: Wed, 30 Sep 2026 10:08:39 +0900 Subject: [PATCH] =?UTF-8?q?fix/KD3-278:=20=EB=B0=9C=ED=91=9C=EC=9E=90?= =?UTF-8?q?=EB=A3=8C=20=EB=AA=A9=EB=A1=9D=EC=97=90=20=EB=B6=84=EB=B0=98=20?= =?UTF-8?q?=EA=B6=8C=ED=95=9C=20=EA=B2=80=EC=A6=9D=EC=9D=84=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/SubmissionFacade.java | 9 +++- .../presentation/SubmissionController.java | 4 +- .../application/SubmissionFacadeTest.java | 53 ++++++++++++++++++- 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/aics-api/src/main/java/kgu/developers/api/submission/application/SubmissionFacade.java b/aics-api/src/main/java/kgu/developers/api/submission/application/SubmissionFacade.java index 19d36401..8e15c785 100644 --- a/aics-api/src/main/java/kgu/developers/api/submission/application/SubmissionFacade.java +++ b/aics-api/src/main/java/kgu/developers/api/submission/application/SubmissionFacade.java @@ -294,7 +294,11 @@ public SubmissionResponse reopenSubmission(Long submissionId, String professorId } public MilestonePresentationsResponse getMilestonePresentations(Long milestoneId, String userId) { - validatePresentationMilestone(milestoneId); + Milestone milestone = validatePresentationMilestone(milestoneId); + if (!isActiveStudent(milestone.getSectionId(), userId) + && !sectionQueryService.isActiveSectionOwnedByProfessor(milestone.getSectionId(), userId)) { + throw new AccessDeniedException("해당 분반의 활성 학생 또는 담당 교수만 발표자료를 조회할 수 있습니다."); + } List submissions = submissionQueryService.getSubmissionsOrderedForPresentation(milestoneId); List teamIds = submissions.stream().map(Submission::getTeamId).distinct().toList(); @@ -491,11 +495,12 @@ private boolean isActiveStudent(Long sectionId, String userId) { .orElse(false); } - private void validatePresentationMilestone(Long milestoneId) { + private Milestone validatePresentationMilestone(Long milestoneId) { Milestone milestone = milestoneRepository.findById(milestoneId) .orElseThrow(() -> new MilestoneNotFoundException(milestoneId)); if (milestone.getType() != MilestoneType.PRESENTATION) { throw new SubmissionMilestoneTypeMismatchException(); } + return milestone; } } diff --git a/aics-api/src/main/java/kgu/developers/api/submission/presentation/SubmissionController.java b/aics-api/src/main/java/kgu/developers/api/submission/presentation/SubmissionController.java index 0921abab..505d9b5f 100644 --- a/aics-api/src/main/java/kgu/developers/api/submission/presentation/SubmissionController.java +++ b/aics-api/src/main/java/kgu/developers/api/submission/presentation/SubmissionController.java @@ -168,11 +168,13 @@ ResponseEntity reopenSubmission( @Operation( summary = "발표순서 정렬 팀별 공개자료 목록 API", description = """ - Description : 발표순서로 정렬된 팀별 공개자료 목록을 조회한다. 이전/다음 네비게이션은 이 배열로 프론트에서 처리. + Description : 해당 분반의 활성 학생 또는 담당 교수만 발표순서로 정렬된 팀별 공개자료 목록을 조회할 수 있다. + 이전/다음 네비게이션은 이 배열로 프론트에서 처리한다. Assignee : 담당자명 """ ) @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = MilestonePresentationsResponse.class))) + @ApiResponse(responseCode = "403", description = "해당 분반의 활성 학생 또는 담당 교수가 아님") ResponseEntity getMilestonePresentations(@PathVariable Long milestoneId, Authentication authentication); @Operation( diff --git a/aics-api/src/testFixtures/java/submission/application/SubmissionFacadeTest.java b/aics-api/src/testFixtures/java/submission/application/SubmissionFacadeTest.java index 95ed49a2..626a8de5 100644 --- a/aics-api/src/testFixtures/java/submission/application/SubmissionFacadeTest.java +++ b/aics-api/src/testFixtures/java/submission/application/SubmissionFacadeTest.java @@ -77,6 +77,7 @@ class SubmissionFacadeTest { private MilestoneRepository milestoneRepository; private SectionQueryService sectionQueryService; + private FakeEnrollmentRepository enrollmentRepository; private FakeTeamMemberRepository teamMemberRepository; private FakeSubmissionRepository submissionRepository; private FakeTeamRepository teamRepository; @@ -105,7 +106,7 @@ void setUp() { new FakeSubmissionMemberConfirmationRepository(); fileObjectRepository = new FakeFileObjectRepository(); fileStorage = new FakeFileStorage(); - FakeEnrollmentRepository enrollmentRepository = new FakeEnrollmentRepository(); + enrollmentRepository = new FakeEnrollmentRepository(); enrollmentRepository.save(Enrollment.create(SECTION_ID, LEADER, Role.STUDENT, Status.ACTIVE)); enrollmentRepository.save(Enrollment.create(SECTION_ID, MEMBER, Role.STUDENT, Status.ACTIVE)); userRepository = new FakeUserRepository(); @@ -412,6 +413,56 @@ void assignPresentationOrder_ThenListedInOrder() { assertThat(response.contents().get(1).teamName()).isEqualTo("우리팀"); } + @Test + @DisplayName("해당 분반의 활성 학생은 발표자료를 조회할 수 있다") + void getMilestonePresentations_AllowsActiveStudent() { + given(milestoneRepository.findById(MILESTONE_ID)).willReturn(Optional.of(presentationMilestone())); + + assertThat(submissionFacade.getMilestonePresentations(MILESTONE_ID, MEMBER).contents()).isEmpty(); + } + + @Test + @DisplayName("해당 분반의 담당 교수는 발표자료를 조회할 수 있다") + void getMilestonePresentations_AllowsSectionProfessor() { + given(milestoneRepository.findById(MILESTONE_ID)).willReturn(Optional.of(presentationMilestone())); + given(sectionQueryService.isActiveSectionOwnedByProfessor(SECTION_ID, PROFESSOR)).willReturn(true); + + assertThat(submissionFacade.getMilestonePresentations(MILESTONE_ID, PROFESSOR).contents()).isEmpty(); + } + + @Test + @DisplayName("다른 분반의 활성 학생은 발표자료를 조회할 수 없다") + void getMilestonePresentations_RejectsOtherSectionStudent() { + given(milestoneRepository.findById(MILESTONE_ID)).willReturn(Optional.of(presentationMilestone())); + enrollmentRepository.save(Enrollment.create(SECTION_ID + 1, NON_MEMBER, Role.STUDENT, Status.ACTIVE)); + + assertThatThrownBy(() -> submissionFacade.getMilestonePresentations(MILESTONE_ID, NON_MEMBER)) + .isInstanceOf(AccessDeniedException.class); + } + + @Test + @DisplayName("다른 분반의 담당 교수는 발표자료를 조회할 수 없다") + void getMilestonePresentations_RejectsOtherSectionProfessor() { + given(milestoneRepository.findById(MILESTONE_ID)).willReturn(Optional.of(presentationMilestone())); + given(sectionQueryService.isActiveSectionOwnedByProfessor(SECTION_ID + 1, PROFESSOR)).willReturn(true); + + assertThatThrownBy(() -> submissionFacade.getMilestonePresentations(MILESTONE_ID, PROFESSOR)) + .isInstanceOf(AccessDeniedException.class); + } + + @Test + @DisplayName("비활성 학생과 조교는 발표자료를 조회할 수 없다") + void getMilestonePresentations_RejectsInactiveStudentAndAssistant() { + given(milestoneRepository.findById(MILESTONE_ID)).willReturn(Optional.of(presentationMilestone())); + enrollmentRepository.save(Enrollment.create(SECTION_ID, "withdrawn", Role.STUDENT, Status.WITHDRAWN)); + enrollmentRepository.save(Enrollment.create(SECTION_ID, "assistant", Role.ASSISTANT, Status.ACTIVE)); + + assertThatThrownBy(() -> submissionFacade.getMilestonePresentations(MILESTONE_ID, "withdrawn")) + .isInstanceOf(AccessDeniedException.class); + assertThatThrownBy(() -> submissionFacade.getMilestonePresentations(MILESTONE_ID, "assistant")) + .isInstanceOf(AccessDeniedException.class); + } + @Test @DisplayName("발표자료 조회 시 해당 팀의 제안서 정보와 제출된 산출물(PDF, 영상 링크)이 함께 반환된다") void getMilestonePresentations_IncludesProjectAndArtifacts() throws Exception {