Files
sodalive-backend-spring-boot/docs/20260626_현재진행중인라이브조회_API/plan-task.md

30 KiB

현재 진행 중인 라이브 조회 API Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development 또는 superpowers:executing-plans로 task 단위 구현을 진행한다. 각 단계는 체크박스(- [ ])로 진행 상태를 갱신한다.

Goal: 인증 회원이 GET /api/v2/home/on-air-lives로 현재 진행 중인 라이브를 20개씩 페이징 조회한다.

Architecture: 공개 API controller/facade/response DTO는 kr.co.vividnext.sodalive.v2.api.home.live 조립 계층에 둔다. 도메인 조회는 기존 kr.co.vividnext.sodalive.v2.recommendationHomeRecommendationQueryServiceHomeRecommendationQueryPort.findLiveRecommendations(...)를 확장 재사용한다. 기존 추천 탭 공개 응답 DTO는 변경하지 않고, 신규 endpoint에서만 title, price, beginDateTimeUtc를 포함한 응답 DTO로 조립한다.

Tech Stack: Kotlin, Spring Boot 2.7.14, Java 17, Spring MVC, Spring Security, Spring Data JPA, QueryDSL, JUnit 5, MockMvc, Gradle Wrapper


0. 확정 사항

  • API endpoint: GET /api/v2/home/on-air-lives
  • 인증 정책: 인증 회원만 조회 가능
  • 비회원/anonymous 요청: 기존 인증 필요 API와 동일하게 인증 오류 반환
  • 응답 wrapper: ApiResponse.ok(...)
  • query parameter: page만 받음, 기본값 0
  • page size: 항상 20개 고정, 클라이언트가 size를 지정하지 않음
  • page 응답: items, page, size, hasNext
  • hasNext 판정: 내부에서 PAGE_SIZE + 1개 조회 후 응답에는 최대 20개만 노출
  • 현재 진행 중인 라이브 조건: live_room.is_active = true, channel_name is not null, channel_name <> ''
  • 정렬: live_room.begin_date_time desc, live_room.id desc
  • 방송자 조건: member.is_active = true
  • 차단 정책: 요청 회원과 크리에이터의 양방향 활성 차단 관계 제외
  • 성인 라이브 정책: MemberContentPreferenceService.canViewAdultContent(member) 결과 반영
  • 시작 시간 응답: LiveRoom.beginDateTime을 기존 UTC ISO 문자열 변환 패턴으로 변환해 beginDateTimeUtc로 응답
  • 프로필 이미지: 기존 홈 추천 패턴과 동일하게 CDN URL 변환, 없으면 기본 프로필 이미지 URL
  • 기존 공개 API 스키마 유지:
    • GET /api/v2/home/recommendations
    • GET /api/v2/home/recommendations/lives

1. 파일 구조 계획

신규 API 조립 계층

  • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/adapter/in/web/HomeOnAirLiveController.kt
  • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/application/HomeOnAirLiveFacade.kt
  • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/dto/HomeOnAirLiveResponse.kt
  • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/adapter/in/web/HomeOnAirLiveControllerTest.kt
  • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/application/HomeOnAirLiveFacadeTest.kt
  • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/dto/HomeOnAirLiveResponseTest.kt

기존 도메인 조회 계층 확장

  • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/port/out/HomeRecommendationQueryPort.kt
  • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt
  • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/HomeRecommendationQueryService.kt
  • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt
  • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/HomeRecommendationQueryServiceTest.kt

기존 설정 수정

  • Modify: src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt
  • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/adapter/in/web/HomeOnAirLiveControllerTest.kt

문서

  • Keep: docs/20260626_현재진행중인라이브조회_API/prd.md
  • Modify: docs/20260626_현재진행중인라이브조회_API/plan-task.md

2. Response data class 초안

src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/dto/HomeOnAirLiveResponse.kt에 아래 DTO를 추가한다.

package kr.co.vividnext.sodalive.v2.api.home.live.dto

data class HomeOnAirLivePageResponse(
    val items: List<HomeOnAirLiveResponse>,
    val page: Int,
    val size: Int,
    val hasNext: Boolean
)

data class HomeOnAirLiveResponse(
    val roomId: Long,
    val creatorNickname: String,
    val creatorProfileImage: String,
    val title: String,
    val price: Int,
    val beginDateTimeUtc: String
)

src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/port/out/HomeRecommendationQueryPort.kt의 기존 record는 아래처럼 확장한다.

package kr.co.vividnext.sodalive.v2.recommendation.port.out

import java.time.LocalDateTime

data class HomeLiveRecommendationRecord(
    val liveRoomId: Long,
    val creatorNickname: String,
    val creatorProfileImage: String?,
    val title: String,
    val price: Int,
    val beginDateTime: LocalDateTime
)

기존 HomeRecommendationFacade.toItem()title, price를 무시하고 기존 HomeLiveItem 필드만 매핑해 기존 API 응답 스키마를 유지한다.


Phase 1: 도메인 조회 record 확장

  • Task 1.1: 라이브 추천 record에 title/price/beginDateTime 포함

    • Files:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/port/out/HomeRecommendationQueryPort.kt
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt
      • Test: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt
    • RED: DefaultHomeRecommendationQueryRepositoryTestshouldReturnLiveTitlePriceAndBeginDateTimeForOnAirLiveQuery 테스트를 추가한다. fixture는 LiveRoom(title = "paid live", price = 30, beginDateTime = LocalDateTime.of(2026, 6, 26, 12, 30), channelName = "channel")를 저장하고, findLiveRecommendations(offset = 0, limit = 1, memberId = viewer.id, includeAdultLives = true) 결과의 title == "paid live", price == 30, beginDateTime == LocalDateTime.of(2026, 6, 26, 12, 30)을 검증한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest
    • GREEN: HomeLiveRecommendationRecordtitle, price, beginDateTime을 추가하고, QueryDSL projection에 liveRoom.title, liveRoom.price, liveRoom.beginDateTime을 추가한다.
    • REFACTOR: 기존 HomeRecommendationFacade.toItem()과 기존 테스트 컴파일 오류를 수정하되 HomeLiveItem 공개 필드는 추가하지 않는다.
    • 기대 결과: repository 테스트가 PASS이고 기존 추천 탭 응답 DTO에는 title, price, beginDateTimeUtc가 추가되지 않는다.
  • Task 1.2: 기존 라이브 조회 조건 회귀 테스트 보강

    • Files:
      • Modify: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepositoryTest.kt
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt
    • RED: 기존 shouldFindPagedLiveRecommendationsWithAdultFilter 테스트를 확장하거나 별도 shouldApplyOnAirLiveVisibilityPolicy 테스트를 추가한다. 활성 방송자/비활성 방송자, channelName = null, 빈 channelName, isActive = false, 성인 라이브, 양방향 차단 라이브를 fixture로 만들고 조건에 맞는 라이브만 최신순으로 반환되는지 검증한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest
    • GREEN: 기존 조회 조건이 부족하면 member.isActive.isTrue, liveRoom.channelName.isNotNull, liveRoom.channelName.isNotEmpty, includeAdultLiveCondition(...), notBlockedCreatorCondition(...)을 보강한다.
    • REFACTOR: 중복 조건은 기존 private condition 함수로 유지하고 신규 abstraction은 추가하지 않는다.
    • 기대 결과: 진행 중 라이브 조회 정책이 PRD의 노출 조건과 일치한다.
  • Task 1.3: HomeRecommendationQueryService 위임 계약 유지

    • Files:
      • Modify: src/test/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/HomeRecommendationQueryServiceTest.kt
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/application/HomeRecommendationQueryService.kt
    • RED: shouldDelegateLiveRecommendationQueryWithPagingAndAdultFlag 테스트를 추가한다. mock HomeRecommendationQueryPortHomeLiveRecommendationRecord(liveRoomId = 1L, creatorNickname = "creator", creatorProfileImage = "profile.png", title = "live", price = 10, beginDateTime = LocalDateTime.of(2026, 6, 26, 12, 30))을 반환하도록 하고, service가 offset, limit, memberId, includeAdultLives를 그대로 port에 전달하는지 검증한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryServiceTest
    • GREEN: 컴파일 오류가 있으면 record 생성부와 import를 갱신한다. service 메서드 시그니처는 기존 findLiveRecommendations(offset, limit, memberId, includeAdultLives)를 유지한다.
    • REFACTOR: service에는 신규 API 전용 page 조립 로직을 넣지 않는다.
    • 기대 결과: 도메인 조회 계층은 API DTO에 의존하지 않고 기존 port record만 반환한다.

Phase 2: 신규 API 조립 계층

  • Task 2.1: 신규 응답 DTO와 직렬화 테스트 추가

    • Files:
      • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/dto/HomeOnAirLiveResponse.kt
      • Create: src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/dto/HomeOnAirLiveResponseTest.kt
    • RED: shouldSerializeOnAirLivePageResponse 테스트를 작성한다. HomeOnAirLivePageResponse(items = listOf(HomeOnAirLiveResponse(...)), page = 0, size = 20, hasNext = true)를 Jackson으로 직렬화하고 items[0].roomId, creatorNickname, creatorProfileImage, title, price, beginDateTimeUtc, page, size, hasNext 필드가 존재하는지 검증한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.live.dto.HomeOnAirLiveResponseTest
    • GREEN: PRD의 Response data class와 동일한 DTO를 추가한다.
    • REFACTOR: DTO에는 도메인 조회나 CDN 변환 로직을 넣지 않는다.
    • 기대 결과: 공개 응답 필드명이 PRD와 일치한다.
  • Task 2.2: HomeOnAirLiveFacade 작성

    • Files:
      • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/application/HomeOnAirLiveFacade.kt
      • Create: src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/application/HomeOnAirLiveFacadeTest.kt
    • RED: shouldReturnFixedSizePageAndHasNext 테스트를 작성한다. mock HomeRecommendationQueryService가 21개 record를 반환하게 하고, facade가 page = 0, size = 20, hasNext = true, items.size = 20을 반환하는지 검증한다. offset = 0, limit = 21, memberId = member.id, includeAdultLives = true 호출도 검증한다.
    • RED: shouldUseDefaultProfileImageWhenCreatorProfileImageIsBlank 테스트를 작성한다. creatorProfileImage = null인 record가 https://cdn.test/profile/default-profile.png로 매핑되는지 검증한다.
    • RED: shouldMapBeginDateTimeToUtcIsoString 테스트를 작성한다. record의 beginDateTime = LocalDateTime.of(2026, 6, 26, 12, 30)가 응답 beginDateTimeUtc = "2026-06-26T12:30:00Z"로 변환되는지 검증한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.live.application.HomeOnAirLiveFacadeTest
    • GREEN: HomeOnAirLiveFacade@Component로 추가한다. 생성자에는 HomeRecommendationQueryService, MemberContentPreferenceService, @Value("\${cloud.aws.cloud-front.host}") cloudFrontHost를 주입한다.
    • GREEN: getOnAirLives(member: Member, page: Int): HomeOnAirLivePageResponse를 구현하고, 내부 상수는 PAGE_SIZE = 20, MAX_PAGE = 10_000으로 둔다.
    • GREEN: page.coerceIn(0, MAX_PAGE)로 page를 보정하고, offset = normalizedPage * PAGE_SIZE, limit = PAGE_SIZE + 1로 조회한다.
    • REFACTOR: CDN URL 변환은 기존 홈 추천의 profileImageUrl(cloudFrontHost, path) 의미와 동일하게 유지한다. 시작 시간 UTC 문자열 변환은 기존 toUtcIso 의미와 동일하게 유지한다. 해당 helper들이 package-private이라 재사용이 어렵다면 facade 내부 private 함수로 최소 복제한다.
    • 기대 결과: facade가 page 조립, 성인 노출 플래그 계산, DTO 매핑만 담당한다.
  • Task 2.3: HomeOnAirLiveController 작성

    • Files:
      • Create: src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/adapter/in/web/HomeOnAirLiveController.kt
      • Create: src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/adapter/in/web/HomeOnAirLiveControllerTest.kt
    • RED: shouldRejectAnonymousRequest 테스트를 작성한다. GET /api/v2/home/on-air-lives를 인증 없이 호출하면 401 Unauthorized가 반환되는지 검증한다.
    • RED: shouldPassAuthenticatedMemberAndPageToFacade 테스트를 작성한다. with(user(MemberAdapter(member)))GET /api/v2/home/on-air-lives?page=2를 호출하고 facade가 member와 page 2를 받으며 $.data.size == 20 응답을 반환하는지 검증한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.live.adapter.in.web.HomeOnAirLiveControllerTest
    • GREEN: @RestController, @RequestMapping("/api/v2/home/on-air-lives") controller를 추가한다. @GetMapping 메서드는 @RequestParam(defaultValue = "0") page: Int@AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") member: Member?를 받는다.
    • GREEN: member ?: throw SodaException(messageKey = "common.error.bad_credentials")로 인증 회원을 요구하고, ApiResponse.ok(homeOnAirLiveFacade.getOnAirLives(member, page))를 반환한다.
    • REFACTOR: controller에는 조회 조건/응답 매핑 로직을 넣지 않는다.
    • 기대 결과: 신규 endpoint는 인증 회원만 접근 가능하고 기존 ApiResponse.ok(...) wrapper를 따른다.

Phase 3: 보안 설정과 회귀 검증

  • Task 3.1: SecurityConfig에 인증 필요 endpoint 등록

    • Files:
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/configs/SecurityConfig.kt
      • Modify: src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/adapter/in/web/HomeOnAirLiveControllerTest.kt
    • RED: HomeOnAirLiveControllerTest.shouldRejectAnonymousRequestSecurityConfig 적용 상태에서 401을 기대하도록 유지한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.live.adapter.in.web.HomeOnAirLiveControllerTest
    • GREEN: SecurityConfigGET /api/v2/home/on-air-livesauthenticated() 경로로 추가한다. permitAll에는 추가하지 않는다.
    • REFACTOR: 기존 /api/v2/home/recommendations permitAll과 /api/v2/home/recommendations/** authenticated 정책을 변경하지 않는다.
    • 기대 결과: 현재 진행 중인 라이브 신규 API는 인증 필수이고, 기존 추천 탭 통합 조회와 전체보기 API의 기존 보안 정책은 변경되지 않는다.
  • Task 3.2: 기존 추천 탭 응답 스키마 회귀 테스트

    • Files:
      • Modify: src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/dto/recommendation/HomeRecommendationResponseTest.kt
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/application/HomeRecommendationFacade.kt
    • RED: shouldKeepHomeLiveItemSchemaWithoutTitlePriceAndBeginDateTimeUtc 테스트를 추가한다. HomeLiveItem(roomId = 1L, creatorNickname = "creator", creatorProfileImage = "https://cdn.test/profile.png")를 직렬화하고 title, price, beginDateTimeUtc 필드가 없음을 검증한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.dto.recommendation.HomeRecommendationResponseTest
    • GREEN: HomeRecommendationFacade의 기존 HomeLiveRecommendationRecord.toItem() 매핑은 roomId, creatorNickname, creatorProfileImage만 사용하도록 유지한다.
    • REFACTOR: 신규 API DTO와 기존 추천 탭 DTO import가 섞이지 않도록 패키지를 명확히 유지한다.
    • 기대 결과: 기존 GET /api/v2/home/recommendations/lives 응답 스키마는 변경되지 않는다.
  • Task 3.3: End-to-end 조회 검증

    • Files:
      • Create: src/test/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/adapter/in/web/HomeOnAirLiveEndToEndTest.kt
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/adapter/in/web/HomeOnAirLiveController.kt
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/api/home/live/application/HomeOnAirLiveFacade.kt
      • Modify: src/main/kotlin/kr/co/vividnext/sodalive/v2/recommendation/adapter/out/persistence/DefaultHomeRecommendationQueryRepository.kt
    • RED: shouldReturnAuthenticatedOnAirLivesWithTitlePriceAndBeginDateTimeUtc 통합 테스트를 작성한다. 인증 회원, 활성 크리에이터, 진행 중 라이브 2개를 저장하고 GET /api/v2/home/on-air-lives?page=0 호출 결과에서 최신순, roomId, creatorNickname, creatorProfileImage, title, price, beginDateTimeUtc, page = 0, size = 20, hasNext = false를 검증한다.
    • RED: shouldExcludeAdultLiveWhenViewerCannotViewAdultContent 통합 테스트를 작성한다. 성인 콘텐츠 노출 불가 회원 기준으로 성인 라이브가 제외되는지 검증한다.
    • 실패 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.live.adapter.in.web.HomeOnAirLiveEndToEndTest
    • GREEN: controller, facade, query repository 연결을 보강해 통합 테스트를 통과시킨다.
    • REFACTOR: 테스트 fixture helper는 해당 테스트 클래스 내부 private 함수로 두고, 공용 테스트 유틸은 만들지 않는다.
    • 기대 결과: 실제 Spring MVC, Security, JPA/QueryDSL 경로로 신규 API 요구사항이 검증된다.

Phase 4: 최종 검증과 문서 기록

  • Task 4.1: 단일/회귀 테스트 실행 및 기록

    • Files:
      • Modify: docs/20260626_현재진행중인라이브조회_API/plan-task.md
    • RED: 신규/수정 테스트가 모두 구현된 상태에서 아래 명령을 실행한다.
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.live.adapter.in.web.HomeOnAirLiveControllerTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.live.application.HomeOnAirLiveFacadeTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.live.dto.HomeOnAirLiveResponseTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest
      • ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryServiceTest
    • 실패 확인: 실패가 있으면 해당 task로 돌아가 원인을 수정한다.
    • GREEN: 신규 API 관련 단일 테스트가 모두 PASS인지 확인한다.
    • REFACTOR: ./gradlew ktlintCheck를 실행해 포맷 위반을 확인한다.
    • 회귀 확인: ./gradlew test를 실행해 전체 테스트 회귀를 확인한다.
    • 기대 결과: 단일 테스트, ktlint, 전체 테스트 결과를 이 task 아래에 한국어로 누적 기록한다.
    • 검증 기록:
      • 무엇을: 신규 API 관련 controller/facade/DTO/repository/query service 단일 테스트, 신규 API E2E 테스트, ktlint, 전체 회귀 테스트를 실행했다.
      • 왜: Phase 1~3 구현 결과가 신규 endpoint 계약과 기존 추천 도메인 회귀 범위를 유지하는지 최종 확인하기 위해서다.
      • 어떻게: ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.live.adapter.in.web.HomeOnAirLiveControllerTest, ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.live.application.HomeOnAirLiveFacadeTest, ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.live.dto.HomeOnAirLiveResponseTest, ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest, ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryServiceTest, ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.live.adapter.in.web.HomeOnAirLiveEndToEndTest, ./gradlew ktlintCheck, ./gradlew test를 순차 실행했다.
      • 결과: 단일 테스트 6개 명령과 ktlintCheck는 모두 BUILD SUCCESSFUL로 통과했다. ./gradlew test는 1029개 테스트 중 1개 실패로 종료했고, 실패 테스트는 AudioContentServiceTest > 업로드 완료 시 예약 공개 콘텐츠는 생성 시점에 최근 소식을 발행하지 않는다이며 AudioContentServiceTest.kt:422의 Mockito interaction 검증 실패다. 신규 API 관련 단일/E2E 검증은 모두 통과했으므로 전체 회귀 실패는 기존 하단 검증 기록과 같은 범위 외 잔여 실패로 기록한다.
  • Task 4.2: 문서 동기화 확인

    • Files:
      • Keep: docs/20260626_현재진행중인라이브조회_API/prd.md
      • Modify: docs/20260626_현재진행중인라이브조회_API/plan-task.md
    • RED: 구현 중 endpoint, response field, 인증 정책, page size가 바뀌었는지 확인한다.
    • 실패 확인: PRD와 구현이 다르면 구현 전에 PRD와 plan-task를 먼저 갱신한다.
    • GREEN: 변경 사항이 없으면 문서 경로와 검증 결과만 유지한다.
    • REFACTOR: ./gradlew tasks --all을 실행해 문서 유지보수 규칙의 명령 유효성을 확인한다.
    • 기대 결과: PRD와 plan-task가 같은 endpoint, response data class, 인증 정책, 페이징 정책을 설명한다.
    • 검증 기록:
      • 무엇을: PRD와 plan-task의 endpoint, response field, 인증 정책, page size 설명이 구현/테스트 대상과 같은지 확인했다.
      • 왜: Phase 4에서 최종 문서 계약이 실제 신규 API 구현과 어긋나지 않도록 하기 위해서다.
      • 어떻게: docs/20260626_현재진행중인라이브조회_API/prd.md, 이 문서의 확정 사항/실행 명령, HomeOnAirLiveControllerTest, HomeOnAirLiveFacadeTest, HomeOnAirLiveResponseTest, HomeOnAirLiveEndToEndTest의 검증 범위를 대조하고 ./gradlew tasks --all을 실행했다.
      • 결과: PRD와 plan-task 모두 GET /api/v2/home/on-air-lives, 인증 회원 전용, page size 20, items/page/size/hasNext, roomId/creatorNickname/creatorProfileImage/title/price/beginDateTimeUtc 응답 필드를 동일하게 설명한다. ./gradlew tasks --allBUILD SUCCESSFUL로 통과했다.

4. 실행 명령

  • 컨트롤러 단일 테스트: ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.live.adapter.in.web.HomeOnAirLiveControllerTest
  • facade 단일 테스트: ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.live.application.HomeOnAirLiveFacadeTest
  • DTO 단일 테스트: ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.live.dto.HomeOnAirLiveResponseTest
  • repository 단일 테스트: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest
  • query service 단일 테스트: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryServiceTest
  • 신규 API E2E 테스트: ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.live.adapter.in.web.HomeOnAirLiveEndToEndTest
  • 포맷 검증: ./gradlew ktlintCheck
  • 전체 회귀 테스트: ./gradlew test
  • Gradle 명령 유효성 확인: ./gradlew tasks --all

5. 검증 기록

  • 문서 작성 시점에는 구현을 진행하지 않았으므로 테스트 실행 기록은 없다.
  • 2026-06-26 문서 작성 후 명령 유효성 확인을 위해 ./gradlew tasks --all을 실행했고 BUILD SUCCESSFUL을 확인했다.
  • 2026-06-26 beginDateTimeUtc 응답 필드 문서 보강 후 명령 유효성 확인을 위해 ./gradlew tasks --all을 실행했고 BUILD SUCCESSFUL을 확인했다.
  • 2026-06-26 Phase 1/2 RED 확인: 신규 테스트 추가 후 HomeLiveRecommendationRecord.title/price/beginDateTime, HomeOnAirLiveResponse, HomeOnAirLiveFacade, HomeOnAirLiveController 미구현으로 :compileTestKotlin FAILED를 확인했다.
  • 2026-06-26 Phase 1/2 GREEN 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.recommendation.adapter.out.persistence.DefaultHomeRecommendationQueryRepositoryTest --tests kr.co.vividnext.sodalive.v2.recommendation.application.HomeRecommendationQueryServiceTest --tests kr.co.vividnext.sodalive.v2.api.home.live.dto.HomeOnAirLiveResponseTest --tests kr.co.vividnext.sodalive.v2.api.home.live.application.HomeOnAirLiveFacadeTest --tests kr.co.vividnext.sodalive.v2.api.home.live.adapter.in.web.HomeOnAirLiveControllerTest를 실행했고 BUILD SUCCESSFUL을 확인했다.
  • 2026-06-26 Phase 1/2 포맷 검증: ./gradlew ktlintCheck를 실행했고 BUILD SUCCESSFUL을 확인했다.
  • 2026-06-26 전체 회귀 확인: ./gradlew test는 1026개 테스트 중 1개 실패로 종료했다. 실패 테스트는 kr.co.vividnext.sodalive.content.AudioContentServiceTest.shouldNotPublishNewsWhenUploadCompleteKeepsScheduledContentInactive이며, 동일 테스트 단독 재실행도 같은 HomeFollowingNewsPublishService mock interaction 검증 실패를 재현했다. 이번 Phase 1/2 변경 파일은 v2/recommendation, v2/api/home/live, 문서에 한정되어 해당 실패는 범위 외 잔여 실패로 기록한다.
  • 2026-06-27 Phase 3 RED 확인: HomeOnAirLiveEndToEndTest 신규 추가 후 ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.live.adapter.in.web.HomeOnAirLiveEndToEndTest를 실행했고, $.data.items.length()가 기대값 2가 아닌 3으로 실패하는 것을 확인했다. 실패 원인은 신규 E2E 테스트 메서드 간 H2 fixture 공유로 확인했다.
  • 2026-06-27 Phase 3 GREEN 확인: SecurityConfigGET /api/v2/home/on-air-lives 인증 matcher를 명시하고 E2E 테스트 격리를 보강한 뒤 ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.live.adapter.in.web.HomeOnAirLiveControllerTest, ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.live.adapter.in.web.HomeOnAirLiveEndToEndTest, ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.dto.recommendation.HomeRecommendationResponseTest를 각각 실행했고 모두 BUILD SUCCESSFUL을 확인했다.
  • 2026-06-27 Phase 3 포맷 검증: ./gradlew ktlintCheck를 실행했고 BUILD SUCCESSFUL을 확인했다.
  • 2026-06-27 Phase 3 회귀 묶음 확인: ./gradlew test --tests kr.co.vividnext.sodalive.v2.api.home.live.adapter.in.web.HomeOnAirLiveControllerTest --tests kr.co.vividnext.sodalive.v2.api.home.dto.recommendation.HomeRecommendationResponseTest --tests kr.co.vividnext.sodalive.v2.api.home.live.adapter.in.web.HomeOnAirLiveEndToEndTest를 실행했고 BUILD SUCCESSFUL을 확인했다.
  • 2026-06-27 Phase 3 코드 리뷰 보강: HomeRecommendationResponseTest.shouldKeepHomeLiveItemSchemaWithoutTitlePriceAndBeginDateTimeUtc에 테스트 스타일 규칙에 맞는 @DisplayName을 추가했다. 이후 ./gradlew --no-daemon test --tests kr.co.vividnext.sodalive.v2.api.home.live.adapter.in.web.HomeOnAirLiveControllerTest --tests kr.co.vividnext.sodalive.v2.api.home.dto.recommendation.HomeRecommendationResponseTest --tests kr.co.vividnext.sodalive.v2.api.home.live.adapter.in.web.HomeOnAirLiveEndToEndTest./gradlew --no-daemon ktlintCheck를 실행했고 모두 BUILD SUCCESSFUL을 확인했다.
  • 2026-06-27 Phase 4 단일/E2E/포맷 검증: HomeOnAirLiveControllerTest, HomeOnAirLiveFacadeTest, HomeOnAirLiveResponseTest, DefaultHomeRecommendationQueryRepositoryTest, HomeRecommendationQueryServiceTest, HomeOnAirLiveEndToEndTest를 각각 ./gradlew test --tests ...로 실행했고 모두 BUILD SUCCESSFUL을 확인했다. 이어서 ./gradlew ktlintCheckBUILD SUCCESSFUL을 확인했다.
  • 2026-06-27 Phase 4 전체 회귀 확인: ./gradlew test는 1029개 테스트 중 1개 실패로 종료했다. 실패 테스트는 AudioContentServiceTest > 업로드 완료 시 예약 공개 콘텐츠는 생성 시점에 최근 소식을 발행하지 않는다이며 AudioContentServiceTest.kt:422의 Mockito interaction 검증 실패다. 신규 API 관련 단일/E2E 테스트는 모두 통과했고, 실패 위치가 content.AudioContentServiceTest로 이번 Phase 4 문서 기록 범위 및 신규 v2/api/home/live, v2/recommendation 변경 범위 밖이므로 잔여 실패로 기록한다.
  • 2026-06-27 Phase 4 문서 동기화 확인: PRD와 plan-task가 GET /api/v2/home/on-air-lives, 인증 회원 전용, page size 20, items/page/size/hasNext, roomId/creatorNickname/creatorProfileImage/title/price/beginDateTimeUtc 응답 필드를 동일하게 설명하는지 확인했다. 문서 유지보수 규칙 확인을 위해 ./gradlew tasks --all을 실행했고 BUILD SUCCESSFUL을 확인했다.
  • 2026-06-27 Phase 4 코드 리뷰 보강: HomeOnAirLiveFacadeTest, HomeOnAirLiveResponseTest에 테스트 스타일 규칙에 맞는 @DisplayName을 추가했다. 이후 HomeOnAirLiveControllerTest, HomeOnAirLiveFacadeTest, HomeOnAirLiveResponseTest, DefaultHomeRecommendationQueryRepositoryTest, HomeRecommendationQueryServiceTest, HomeOnAirLiveEndToEndTest를 각각 ./gradlew test --tests ...로 재실행했고 모두 BUILD SUCCESSFUL을 확인했다. ./gradlew ktlintCheck./gradlew tasks --allBUILD SUCCESSFUL을 확인했다.
  • 2026-06-27 Phase 4 전체 회귀 재확인: ./gradlew test는 1029개 테스트 중 1개 실패로 종료했다. 실패 테스트는 기존 기록과 동일하게 AudioContentServiceTest > 업로드 완료 시 예약 공개 콘텐츠는 생성 시점에 최근 소식을 발행하지 않는다이며 AudioContentServiceTest.kt:422의 Mockito interaction 검증 실패다. 신규 API 관련 단일/E2E 테스트는 모두 통과했으므로 범위 외 잔여 실패로 유지한다.