diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRoundRequest.java b/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRoundRequest.java index 2d77c9b5..8ea59e6f 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRoundRequest.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRoundRequest.java @@ -11,6 +11,7 @@ import java.time.LocalDate; import java.time.LocalTime; +import java.util.UUID; @Getter @Builder @@ -23,6 +24,15 @@ ) public class AttendanceRoundRequest { + @NotNull(message = "세션 ID는 필수입니다") + @Schema( + description = "회차가 속할 세션의 ID", + example = "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + type = "string", + format = "uuid" + ) + private UUID sessionId; + @Schema( description = "라운드 진행 날짜 (YYYY-MM-DD 형식)", example = "2025-11-06", diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRoundResponse.java b/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRoundResponse.java index e600bd5b..3ba856f4 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRoundResponse.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceRoundResponse.java @@ -7,12 +7,9 @@ import lombok.Getter; import lombok.NoArgsConstructor; import org.sejongisc.backend.attendance.entity.AttendanceRound; -import org.sejongisc.backend.attendance.entity.RoundStatus; -import org.sejongisc.backend.attendance.entity.AttendanceStatus; import java.time.LocalDate; import java.time.LocalTime; -import java.util.List; import java.util.UUID; @Getter @@ -21,7 +18,7 @@ @AllArgsConstructor @Schema( title = "출석 라운드 응답", - description = "출석 라운드의 상세 정보. 라운드 상태, 시간, 출석 현황 통계를 포함합니다." + description = "출석 라운드의 상세 정보. 라운드 날짜, 시간, 상태를 포함합니다." ) public class AttendanceRoundResponse { @@ -42,107 +39,40 @@ public class AttendanceRoundResponse { @Schema( description = "라운드 출석 시작 시간", - example = "10:00", + example = "10:00:00", type = "string", format = "time" ) - @JsonFormat(pattern = "HH:mm") + @JsonFormat(pattern = "HH:mm:ss") private LocalTime startTime; - @Schema( - description = "라운드 출석 종료 시간 (startTime + allowedMinutes)", - example = "10:30", - type = "string", - format = "time" - ) - @JsonFormat(pattern = "HH:mm") - private LocalTime endTime; - @Schema( description = "출석 가능한 시간 (분단위)", - example = "30" - ) - private Integer allowedMinutes; - - @Schema( - description = "라운드의 현재 상태. UPCOMING(시작 전), ACTIVE(진행 중), CLOSED(종료됨)", - example = "ACTIVE", - implementation = RoundStatus.class - ) - private RoundStatus roundStatus; - - @Schema( - description = "라운드의 이름/제목. 예: 1주차, 2주차 등", - example = "1주차" - ) - private String roundName; - - @Schema( - description = "정시 출석자 수", example = "20" ) - private Long presentCount; - - @Schema( - description = "지각 출석자 수", - example = "5" - ) - private Long lateCount; + private Integer availableMinutes; @Schema( - description = "결석자 수", - example = "3" + description = "라운드의 현재 상태. (upcoming, active, closed 등)", + example = "active" ) - private Long absentCount; - - @Schema( - description = "총 출석자 수", - example = "28" - ) - private Long totalAttendees; + private String status; /** * 엔티티를 DTO로 변환 - * roundStatus는 실시간으로 계산되어 반환됨 - * 출석 통계는 단일 루프로 효율적으로 계산됨 + * status는 실시간으로 계산되어 반환됨 */ public static AttendanceRoundResponse fromEntity(AttendanceRound round) { - // attendances 리스트가 null일 수 있으므로 방어 - var attendances = round.getAttendances(); - if (attendances == null) { - attendances = List.of(); - } - - // 단일 루프로 모든 통계를 효율적으로 계산 - long presentCount = 0; - long lateCount = 0; - long absentCount = 0; - - for (var attendance : attendances) { - if (attendance.getAttendanceStatus() == AttendanceStatus.PRESENT) { - presentCount++; - } else if (attendance.getAttendanceStatus() == AttendanceStatus.LATE) { - lateCount++; - } else if (attendance.getAttendanceStatus() == AttendanceStatus.ABSENT) { - absentCount++; - } - } - // 현재 시간 기준으로 라운드 상태를 실시간 계산 - RoundStatus currentStatus = round.calculateCurrentStatus(); + // RoundStatus.getValue()를 사용하여 명시적이고 안전한 변환 + String statusString = round.calculateCurrentStatus().getValue(); return AttendanceRoundResponse.builder() .roundId(round.getRoundId()) .roundDate(round.getRoundDate()) .startTime(round.getStartTime()) - .endTime(round.getEndTime()) - .allowedMinutes(round.getAllowedMinutes()) - .roundStatus(currentStatus) - .roundName(round.getRoundName()) - .presentCount(presentCount) - .lateCount(lateCount) - .absentCount(absentCount) - .totalAttendees((long) attendances.size()) + .availableMinutes(round.getAllowedMinutes()) + .status(statusString) .build(); } } diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceSessionResponse.java b/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceSessionResponse.java index afbb7250..e82e813e 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceSessionResponse.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/dto/AttendanceSessionResponse.java @@ -3,10 +3,8 @@ import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.v3.oas.annotations.media.Schema; import lombok.*; -import org.sejongisc.backend.attendance.entity.SessionStatus; -import org.sejongisc.backend.attendance.entity.SessionVisibility; -import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.UUID; @Getter @@ -15,7 +13,7 @@ @AllArgsConstructor @Schema( title = "출석 세션 응답", - description = "출석 세션의 상세 정보. 세션 설정, 상태, 남은 시간, 참여자 수 등을 포함합니다." + description = "출석 세션의 상세 정보. 세션 설정, 기본 시간, 위치 등을 포함합니다." ) public class AttendanceSessionResponse { @@ -27,84 +25,50 @@ public class AttendanceSessionResponse { @Schema( description = "세션의 제목/이름", - example = "2024년 10월 동아리 정기 모임" + example = "금융 IT팀 세션" ) private String title; @Schema( - description = "출석 체크인이 가능한 시간 윈도우 (초 단위)", - example = "1800" + description = "세션 개최 위치 정보", + example = "{\"lat\": 37.5499, \"lng\": 127.0751}" ) - private Integer windowSeconds; + private LocationInfo location; @Schema( - description = "출석 완료 시 지급할 포인트", - example = "10" + description = "세션의 기본 시작 시간", + example = "18:30:00" ) - private Integer rewardPoints; + @JsonFormat(pattern = "HH:mm:ss") + private LocalTime defaultStartTime; @Schema( - description = "세션 개최 위치의 위도", - example = "37.4979" + description = "출석 인정 시간 (분 단위)", + example = "30" ) - private Double latitude; + private Integer defaultAvailableMinutes; @Schema( - description = "세션 개최 위치의 경도", - example = "127.0276" - ) - private Double longitude; - - @Schema( - description = "GPS 기반 위치 검증 반경 (미터 단위)", + description = "출석 완료 시 지급할 포인트", example = "100" ) - private Integer radiusMeters; - - @Schema( - description = "세션의 공개 범위. PUBLIC(공개) 또는 PRIVATE(비공개)", - example = "PUBLIC", - implementation = SessionVisibility.class - ) - private SessionVisibility visibility; - - @Schema( - description = "세션의 현재 상태. UPCOMING(예정), OPEN(진행중), CLOSED(종료)", - example = "OPEN", - implementation = SessionStatus.class - ) - private SessionStatus status; - - @Schema( - description = "세션 레코드 생성 시간", - example = "2024-10-31 10:00:00" - ) - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime createdAt; - - @Schema( - description = "세션 레코드 최종 수정 시간", - example = "2024-10-31 11:30:00" - ) - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private LocalDateTime updatedAt; - - @Schema( - description = "현재부터 체크인 마감까지 남은 시간 (초 단위). 음수이면 마감됨", - example = "1234" - ) - private Long remainingSeconds; + private Integer rewardPoints; @Schema( - description = "현재 체크인이 가능한 상태인지 여부. " + - "true면 지금 체크인 가능, false면 불가능", + description = "세션의 공개 여부. true(공개) 또는 false(비공개)", example = "true" ) - private boolean checkInAvailable; - - @Schema( - description = "현재 세션에 참여한 학생 수", - example = "25" - ) - private Integer participantCount; + private Boolean isVisible; + + @Getter + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class LocationInfo { + @Schema(description = "위도", example = "37.5499") + private Double lat; + + @Schema(description = "경도", example = "127.0751") + private Double lng; + } } \ No newline at end of file diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/entity/RoundStatus.java b/backend/src/main/java/org/sejongisc/backend/attendance/entity/RoundStatus.java index 2e1be229..cfc25142 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/entity/RoundStatus.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/entity/RoundStatus.java @@ -4,17 +4,29 @@ * 라운드(주차) 상태 */ public enum RoundStatus { - UPCOMING("진행 예정"), - ACTIVE("진행 중"), - CLOSED("마감됨"); + UPCOMING("진행 예정", "upcoming"), + ACTIVE("진행 중", "active"), + CLOSED("마감됨", "closed"); private final String description; + private final String value; - RoundStatus(String description) { + RoundStatus(String description, String value) { this.description = description; + this.value = value; } public String getDescription() { return description; } + + /** + * API 응답에 사용할 문자열 값 반환 + * toString().toLowerCase()와 달리 명시적이고 안전함 + * + * @return API 응답용 상태값 (lowercase) + */ + public String getValue() { + return value; + } } diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceRoundService.java b/backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceRoundService.java index 3184006e..64c6fb9e 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceRoundService.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceRoundService.java @@ -71,6 +71,8 @@ public AttendanceRoundResponse createRound(UUID sessionId, AttendanceRoundReques AttendanceRound saved = attendanceRoundRepository.save(round); session.getRounds().add(saved); + // 양방향 관계를 DB에 반영하기 위해 세션도 저장 + attendanceSessionRepository.save(session); log.info("✅ 라운드 생성 완료 - sessionId: {}, roundId: {}, roundDate: {}, roundStatus: {}", sessionId, saved.getRoundId(), saved.getRoundDate(), saved.getRoundStatus()); diff --git a/backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceSessionService.java b/backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceSessionService.java index 95053976..46df8939 100644 --- a/backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceSessionService.java +++ b/backend/src/main/java/org/sejongisc/backend/attendance/service/AttendanceSessionService.java @@ -15,8 +15,8 @@ import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.List; -import java.util.Random; import java.util.UUID; import java.util.stream.Collectors; @@ -323,40 +323,37 @@ private String generateRandomCode() { /** * AttendanceSession 엔티티를 Response DTO로 변환 - * - 남은 시간, 체크인 가능 여부, 참여자 수 계산 - * - 현재 시간 기준으로 동적 정보 생성 + * - 기본 세션 정보: 제목, 시작 시간, 출석 인정 시간, 보상 포인트 + * - 위치 정보: location 객체 (lat, lng) + * - 공개 여부: isVisible boolean */ private AttendanceSessionResponse convertToResponse(AttendanceSession session) { - LocalDateTime now = LocalDateTime.now(); - LocalDateTime endTime = session.getStartsAt().plusSeconds(session.getWindowSeconds()); + // 위치 정보 변환 (location이 존재하면 LocationInfo 객체 생성, 없으면 null) + AttendanceSessionResponse.LocationInfo location = null; + if (session.getLocation() != null) { + location = AttendanceSessionResponse.LocationInfo.builder() + .lat(session.getLocation().getLat()) + .lng(session.getLocation().getLng()) + .build(); + } - Long remainingSeconds = null; - boolean checkInAvailable = false; + // defaultAvailableMinutes: windowSeconds를 분 단위로 변환 + Integer defaultAvailableMinutes = (int) (session.getWindowSeconds() / 60); - if (now.isBefore(session.getStartsAt())) { - remainingSeconds = java.time.Duration.between(now, session.getStartsAt()).getSeconds(); - } else if (now.isBefore(endTime)) { - remainingSeconds = java.time.Duration.between(now, endTime).getSeconds(); - checkInAvailable = session.getStatus() == SessionStatus.OPEN; - } + // defaultStartTime: startsAt에서 시간 부분만 추출 + LocalTime defaultStartTime = session.getStartsAt().toLocalTime(); - Long participantCount = attendanceRepository.countByAttendanceSession(session); + // isVisible: 공개 세션이면 true, 비공개면 false + Boolean isVisible = session.getVisibility() == SessionVisibility.PUBLIC; return AttendanceSessionResponse.builder() .attendanceSessionId(session.getAttendanceSessionId()) .title(session.getTitle()) - .windowSeconds(session.getWindowSeconds()) + .location(location) + .defaultStartTime(defaultStartTime) + .defaultAvailableMinutes(defaultAvailableMinutes) .rewardPoints(session.getRewardPoints()) - .latitude(session.getLocation() != null ? session.getLocation().getLat() : null) - .longitude(session.getLocation() != null ? session.getLocation().getLng() : null) - .radiusMeters(session.getLocation() != null ? session.getLocation().getRadiusMeters() : null) - .visibility(session.getVisibility()) - .status(session.getStatus()) - .createdAt(session.getCreatedDate()) - .updatedAt(session.getUpdatedDate()) - .remainingSeconds(remainingSeconds) - .checkInAvailable(checkInAvailable) - .participantCount(participantCount.intValue()) + .isVisible(isVisible) .build(); } diff --git a/backend/src/test/java/org/sejongisc/backend/attendance/controller/AttendanceSessionControllerTest.java b/backend/src/test/java/org/sejongisc/backend/attendance/controller/AttendanceSessionControllerTest.java index d6a9d89c..e0f997ef 100644 --- a/backend/src/test/java/org/sejongisc/backend/attendance/controller/AttendanceSessionControllerTest.java +++ b/backend/src/test/java/org/sejongisc/backend/attendance/controller/AttendanceSessionControllerTest.java @@ -5,7 +5,6 @@ import org.junit.jupiter.api.Test; import org.sejongisc.backend.attendance.dto.AttendanceSessionRequest; import org.sejongisc.backend.attendance.dto.AttendanceSessionResponse; -import org.sejongisc.backend.attendance.entity.SessionStatus; import org.sejongisc.backend.attendance.entity.SessionVisibility; import org.sejongisc.backend.attendance.service.AttendanceSessionService; import org.springframework.beans.factory.annotation.Autowired; @@ -18,6 +17,7 @@ import org.springframework.test.web.servlet.MockMvc; import java.time.LocalDateTime; +import java.time.LocalTime; import java.util.Arrays; import java.util.List; import java.util.UUID; @@ -62,17 +62,14 @@ void createSession_success() throws Exception { AttendanceSessionResponse response = AttendanceSessionResponse.builder() .attendanceSessionId(UUID.randomUUID()) .title("세투연 정규 세션") - .tag("금융IT") - .startsAt(request.getStartsAt()) - .windowSeconds(1800) - .code("123456") + .defaultStartTime(LocalDateTime.now().plusHours(1).toLocalTime()) + .defaultAvailableMinutes(30) .rewardPoints(10) - .latitude(37.5665) - .longitude(126.9780) - .radiusMeters(100) - .visibility(SessionVisibility.PUBLIC) - .status(SessionStatus.UPCOMING) - .participantCount(0) + .location(AttendanceSessionResponse.LocationInfo.builder() + .lat(37.5665) + .lng(126.9780) + .build()) + .isVisible(true) .build(); when(attendanceSessionService.createSession(any(AttendanceSessionRequest.class))).thenReturn(response); @@ -83,15 +80,11 @@ void createSession_success() throws Exception { .content(objectMapper.writeValueAsString(request))) .andExpect(status().isCreated()) .andExpect(jsonPath("$.title").value("세투연 정규 세션")) - .andExpect(jsonPath("$.tag").value("금융IT")) - .andExpect(jsonPath("$.code").value("123456")) .andExpect(jsonPath("$.rewardPoints").value(10)) - .andExpect(jsonPath("$.latitude").value(37.5665)) - .andExpect(jsonPath("$.longitude").value(126.9780)) - .andExpect(jsonPath("$.radiusMeters").value(100)) - .andExpect(jsonPath("$.visibility").value("PUBLIC")) - .andExpect(jsonPath("$.status").value("UPCOMING")) - .andExpect(jsonPath("$.participantCount").value(0)); + .andExpect(jsonPath("$.location.lat").value(37.5665)) + .andExpect(jsonPath("$.location.lng").value(126.9780)) + .andExpect(jsonPath("$.isVisible").value(true)) + .andExpect(jsonPath("$.defaultAvailableMinutes").value(30)); } @Test @@ -132,36 +125,6 @@ void createSession_fail_validation() throws Exception { .andExpect(status().isInternalServerError()); } - @Test - @DisplayName("출석 코드로 세션 조회 성공") - @WithMockUser - void getSessionByCode_success() throws Exception { - //given - String code = "123456"; - AttendanceSessionResponse response = AttendanceSessionResponse.builder() - .attendanceSessionId(UUID.randomUUID()) - .title("세투연 정규 세션") - .code(code) - .startsAt(LocalDateTime.now().plusMinutes(30)) - .windowSeconds(1800) - .status(SessionStatus.UPCOMING) - .participantCount(5) - .remainingSeconds(1800L) - .checkInAvailable(false) - .build(); - - when(attendanceSessionService.getSessionByCode(code)).thenReturn(response); - - //then - mockMvc.perform(get("/api/attendance/sessions/code/{code}", code)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.code").value(code)) - .andExpect(jsonPath("$.title").value("세투연 정규 세션")) - .andExpect(jsonPath("$.participantCount").value(5)) - .andExpect(jsonPath("$.remainingSeconds").value(1800)) - .andExpect(jsonPath("$.checkInAvailable").value(false)); - } - @Test @DisplayName("세션 상세 조회 성공") @WithMockUser @@ -171,11 +134,10 @@ void getSession_success() throws Exception { AttendanceSessionResponse response = AttendanceSessionResponse.builder() .attendanceSessionId(sessionId) .title("세투연 정규 세션") - .code("123456") - .startsAt(LocalDateTime.now().plusHours(1)) - .windowSeconds(1800) - .status(SessionStatus.UPCOMING) - .participantCount(0) + .defaultStartTime(LocalTime.of(10, 0)) + .defaultAvailableMinutes(30) + .rewardPoints(10) + .isVisible(true) .build(); when(attendanceSessionService.getSessionById(sessionId)).thenReturn(response); @@ -184,8 +146,7 @@ void getSession_success() throws Exception { mockMvc.perform(get("/api/attendance/sessions/{sessionId}", sessionId)) .andExpect(status().isOk()) .andExpect(jsonPath("$.attendanceSessionId").value(sessionId.toString())) - .andExpect(jsonPath("$.title").value("세투연 정규 세션")) - .andExpect(jsonPath("$.code").value("123456")); + .andExpect(jsonPath("$.title").value("세투연 정규 세션")); } @Test @@ -197,18 +158,18 @@ void getPublicSessions_success() throws Exception { AttendanceSessionResponse.builder() .attendanceSessionId(UUID.randomUUID()) .title("정규 세션 1") - .code("111111") - .status(SessionStatus.UPCOMING) - .visibility(SessionVisibility.PUBLIC) - .participantCount(10) + .defaultStartTime(LocalTime.of(10, 0)) + .defaultAvailableMinutes(30) + .rewardPoints(10) + .isVisible(true) .build(), AttendanceSessionResponse.builder() .attendanceSessionId(UUID.randomUUID()) .title("정규 세션 2") - .code("222222") - .status(SessionStatus.OPEN) - .visibility(SessionVisibility.PUBLIC) - .participantCount(5) + .defaultStartTime(LocalTime.of(14, 0)) + .defaultAvailableMinutes(30) + .rewardPoints(15) + .isVisible(true) .build() ); @@ -219,9 +180,7 @@ void getPublicSessions_success() throws Exception { .andExpect(status().isOk()) .andExpect(jsonPath("$.length()").value(2)) .andExpect(jsonPath("$[0].title").value("정규 세션 1")) - .andExpect(jsonPath("$[0].participantCount").value(10)) - .andExpect(jsonPath("$[1].title").value("정규 세션 2")) - .andExpect(jsonPath("$[1].participantCount").value(5)); + .andExpect(jsonPath("$[1].title").value("정규 세션 2")); } @Test @@ -233,11 +192,10 @@ void getActivateSessions_success() throws Exception { AttendanceSessionResponse.builder() .attendanceSessionId(UUID.randomUUID()) .title("활성 세션") - .code("111111") - .status(SessionStatus.OPEN) - .checkInAvailable(true) - .remainingSeconds(900L) - .participantCount(8) + .defaultStartTime(LocalTime.of(10, 0)) + .defaultAvailableMinutes(30) + .rewardPoints(10) + .isVisible(true) .build() ); @@ -247,44 +205,7 @@ void getActivateSessions_success() throws Exception { mockMvc.perform(get("/api/attendance/sessions/active")) .andExpect(status().isOk()) .andExpect(jsonPath("$.length()").value(1)) - .andExpect(jsonPath("$[0].title").value("활성 세션")) - .andExpect(jsonPath("$[0].status").value("OPEN")) - .andExpect(jsonPath("$[0].checkInAvailable").value(true)) - .andExpect(jsonPath("$[0].remainingSeconds").value(900)) - .andExpect(jsonPath("$[0].participantCount").value(8)); - } - - @Test - @DisplayName("태그별 세션 목록 조회 성공") - @WithMockUser - void getSessionByTag_success() throws Exception { - //given - String tag = "금융IT"; - List responses = Arrays.asList( - AttendanceSessionResponse.builder() - .attendanceSessionId(UUID.randomUUID()) - .title("금융IT 정규 세션 1") - .tag(tag) - .code("111111") - .participantCount(16) - .build(), - AttendanceSessionResponse.builder() - .attendanceSessionId(UUID.randomUUID()) - .title("금융IT 정규 세션 2") - .tag(tag) - .code("222222") - .participantCount(14) - .build() - ); - - when(attendanceSessionService.getSessionsByTag(tag)).thenReturn(responses); - - //then - mockMvc.perform(get("/api/attendance/sessions/tag/{tag}", tag)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.length()").value(2)) - .andExpect(jsonPath("$[0].tag").value(tag)) - .andExpect(jsonPath("$[1].tag").value(tag)); + .andExpect(jsonPath("$[0].title").value("활성 세션")); } @Test @@ -305,14 +226,10 @@ void updateSession_success() throws Exception { AttendanceSessionResponse response = AttendanceSessionResponse.builder() .attendanceSessionId(sessionId) .title("수정된 제목") - .tag("수정된 태그") - .startsAt(request.getStartsAt()) - .windowSeconds(3600) - .code("123456") + .defaultStartTime(LocalTime.of(10, 0)) + .defaultAvailableMinutes(60) .rewardPoints(10) - .visibility(SessionVisibility.PRIVATE) - .status(SessionStatus.UPCOMING) - .participantCount(0) + .isVisible(false) .build(); when(attendanceSessionService.updateSession(eq(sessionId), any(AttendanceSessionRequest.class))).thenReturn(response); @@ -323,9 +240,8 @@ void updateSession_success() throws Exception { .content(objectMapper.writeValueAsString(request))) .andExpect(status().isOk()) .andExpect(jsonPath("$.title").value("수정된 제목")) - .andExpect(jsonPath("$.tag").value("수정된 태그")) .andExpect(jsonPath("$.rewardPoints").value(10)) - .andExpect(jsonPath("$.visibility").value("PRIVATE")); + .andExpect(jsonPath("$.isVisible").value(false)); } @Test @@ -367,37 +283,6 @@ void deleteSession_success() throws Exception { .andExpect(status().isNoContent()); } - @Test - @DisplayName("상태별 세션 목록 조회 성공 (관리자)") - @WithMockUser(roles = "PRESIDENT") - void getSessionByStatus_success() throws Exception { - //given - SessionStatus status = SessionStatus.OPEN; - List responses = Arrays.asList( - AttendanceSessionResponse.builder() - .attendanceSessionId(UUID.randomUUID()) - .title("진행중인 세션 1") - .status(status) - .participantCount(8) - .build(), - AttendanceSessionResponse.builder() - .attendanceSessionId(UUID.randomUUID()) - .title("진행중인 세션 2") - .status(status) - .participantCount(14) - .build() - ); - - when(attendanceSessionService.getSessionsByStatus(status)).thenReturn(responses); - - //then - mockMvc.perform(get("/api/attendance/sessions/status/{status}", status)) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.length()").value(2)) - .andExpect(jsonPath("$[0].status").value("OPEN")) - .andExpect(jsonPath("$[1].status").value("OPEN")); - } - @Test @DisplayName("관리자 전용 기능 접근 실패: 권한 없음") @WithMockUser(roles = "TEAM_MEMBER") @@ -421,9 +306,5 @@ void adminOnlyEndpoints_fail_noPermission() throws Exception { // 세션 삭제 mockMvc.perform(delete("/api/attendance/sessions/{sessionId}", sessionId)) .andExpect(status().isForbidden()); - - // 상태별 조회 - mockMvc.perform(get("/api/attendance/sessions/status/OPEN")) - .andExpect(status().isForbidden()); } }