[FEAT] 작가가 program을 만드는 api 개발 (#281)#282
Conversation
📝 WalkthroughWalkthrough프로그램 도메인(엔티티·값객체·컨버터), 리포지토리·스펙, 애플리케이션 서비스와 REST 컨트롤러를 추가하고, UserId 파라미터 리졸버, JPA 감사·Clock 빈, 빌드 설정(IDEA·검증·메타모델·ArchUnit) 및 관련 단위/통합 테스트들을 도입했습니다. Changes
Sequence DiagramsequenceDiagram
participant Client
participant Controller as ProgramController
participant Resolver as UserIdArgumentResolver
participant Service as ProgramService
participant Repository as ProgramRepository
participant Database as Database
Client->>Controller: POST /reservation/program\n(Authorization header, body)
Controller->>Resolver: resolve `@UserId` (X-Passport header)
Resolver->>Controller: Long photographerId
Controller->>Controller: `@Valid` 요청 검증
Controller->>Service: createProgram(photographerId, request)
Service->>Service: Program.create(...) (VO 검증)
Service->>Repository: save(program)
Repository->>Database: INSERT program (converters 적용)
Database-->>Repository: persisted entity (id)
Repository-->>Service: saved Program
Service-->>Controller: ProgramResponse(id)
Controller-->>Client: 201 Created (ResultResponse<ProgramResponse>)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Test Results (reservation)239 tests 239 ✅ 7s ⏱️ Results for commit 3ca13d8. ♻️ This comment has been updated with latest results. |
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Fix all issues with AI agents
In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/Program.java`:
- Around line 140-142: The delete() method sets deletedAt using
LocalDateTime.now(), which makes tests time-dependent; modify Program.delete to
accept a Clock parameter (e.g., delete(Clock clock)) and set deletedAt =
LocalDateTime.now(clock), and add a no-arg overload delete() that delegates to
delete(Clock.systemDefaultZone()) so existing callers remain unchanged; update
references to Program.delete in tests to pass a fixed Clock to allow
deterministic unit tests.
- Around line 164-179: The equals/hashCode use only the id field which breaks
collections when id is null; update Program.equals(Object) to treat transient
entities specially by returning this == o when either id is null (i.e., if id ==
null then fall back to instance equality) and otherwise compare ids, and update
Program.hashCode() to return System.identityHashCode(this) (or another stable
identity-based value) when id is null and Objects.hash(id) when id is non-null
so the hashCode remains stable before/after persistence.
In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/vo/Title.java`:
- Around line 26-35: The validate method in Title contains redundant length
check: after failing value.isBlank(), value.length() < MIN_LENGTH is always
false because MIN_LENGTH == 1; remove the redundant condition and simplify the
range check in Title.validate to only test value.length() > MAX_LENGTH (or test
value.length() < MIN_LENGTH when MIN_LENGTH > 1), keeping the null/isBlank check
and throwing DomainException with the same message; update any error message
logic to compute current length via value.length() and keep references to
validate, MIN_LENGTH and MAX_LENGTH intact.
In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramRepository.java`:
- Around line 14-22: The findById(Long id) declaration in ProgramRepository
duplicates JpaRepository's method; remove this redundant method declaration from
ProgramRepository and keep the Javadoc elsewhere if you want to document
behavior. If you need a special query that ignores deletion flags, add a clearly
named repository method (e.g., findByIdIgnoringDeleted or use
ProgramSpecification with a method like findOne(Specification<Program> spec))
and implement/compose it instead of shadowing JpaRepository::findById.
In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramSpecification.java`:
- Around line 21-23: Replace hard-coded field name strings in
ProgramSpecification methods (e.g., isActive(), and the methods using
"photographerId") with type-safe references: use the generated JPA metamodel
(Program_.deletedAt, Program_.photographerId) or introduce constants on the
Program entity (e.g., Program.Fields.DELETED_AT /
Program.Fields.PHOTOGRAPHER_ID) and use root.get(Program_.deletedAt) or
root.get(Program.Fields.DELETED_AT) instead of root.get("deletedAt"); ensure the
metamodel is generated (annotation processing enabled) or the new constants are
defined and imported before updating the other methods that currently use string
literals.
In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/program/application/ProgramServiceTest.java`:
- Around line 122-168: Update the tests in ProgramServiceTest that currently
only assert the exception type (the three tests calling
programService.createProgram) to also assert the exception message or error code
for stronger validation; catch or use assertion helpers (e.g., AssertJ's
assertThatThrownBy) to verify DomainException.getMessage() or a specific
errorCode property on the thrown DomainException matches the expected text/code
for empty title, negative price, and zero duration respectively so each test
asserts both type and precise failure reason.
In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/program/domain/ProgramTest.java`:
- Around line 127-137: The test 동일한_ID를_가진_프로그램은_같다() currently compares
program1 to itself and doesn't verify ID-based equality; update the test to
create two distinct Program instances via createDefaultProgram(), assign the
same ID to both (use the Program class's setter or reflection to set the private
id field if no setter exists), then assert that program1.equals(program2)
(assertThat(program1).isEqualTo(program2)); reference the ProgramTest method
동일한_ID를_가진_프로그램은_같다() and helper createDefaultProgram() when locating where to
change the test and use Program#id (or private id field) as the unique
identifier to set.
In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramSpecificationTest.java`:
- Around line 17-62: These tests only assert that
ProgramSpecification.isActive(), isDeleted(), and belongsTo(...) return non-null
Specifications; update them to verify actual predicate behavior by converting to
`@DataJpaTest` integration tests: persist sample Program entities (active/deleted
and with different owner IDs) via the repository, then call
repository.findAll(ProgramSpecification.isActive()),
findAll(ProgramSpecification.isDeleted()),
findAll(ProgramSpecification.belongsTo(1L)), and combinations like
isActive().and(belongsTo(1L)) and assert the returned lists contain the expected
entities; this exercises the real Criteria/Predicate logic rather than only null
checks.
In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/shared/presentation/web/resolver/UserIdArgumentResolverTest.java`:
- Around line 72-137: Add a new test in the resolveArgument_테스트 nested class to
cover an expired Passport: create a Passport with an expired expiredAt (e.g.,
Instant.now().minus(...) so expiredAt < now), sign it using
passportHandler.sign(...) and mock NativeWebRequest/HttpServletRequest to return
that signed token for PassportHandler.PASSPORT_KEY, then call
resolver.resolveArgument(...) and assert it throws PresentationException (and
optionally assert the appropriate PresentationErrorCode for expired passport).
Ensure you reference the existing symbols Passport, passportHandler.sign,
PassportHandler.PASSPORT_KEY, resolver.resolveArgument, and
PresentationException when implementing the test.
reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/Program.java
Outdated
Show resolved
Hide resolved
reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/Program.java
Show resolved
Hide resolved
reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/vo/Title.java
Show resolved
Hide resolved
...java/net/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramRepository.java
Outdated
Show resolved
Hide resolved
...a/net/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramSpecification.java
Show resolved
Hide resolved
...ion/src/test/java/net/catsnap/CatsnapReservation/program/application/ProgramServiceTest.java
Show resolved
Hide resolved
reservation/src/test/java/net/catsnap/CatsnapReservation/program/domain/ProgramTest.java
Outdated
Show resolved
Hide resolved
...t/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramSpecificationTest.java
Outdated
Show resolved
Hide resolved
.../catsnap/CatsnapReservation/shared/presentation/web/resolver/UserIdArgumentResolverTest.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
작가(Photographer)가 자신의 촬영 프로그램을 생성할 수 있는 API를 추가하고, Passport 기반 인증 사용자 ID 주입(@userid) 및 관련 테스트/아키텍처 검증을 도입합니다.
Changes:
- Program 생성 유스케이스(도메인/서비스/리포지토리/컨트롤러) 및 요청/응답 DTO 추가
- Passport 헤더에서 userId를 추출해 컨트롤러 파라미터로 주입하는
@UserId+ ArgumentResolver 추가 및 MVC 설정에 등록 - Program/VO/Converter/Controller/Service 관련 테스트 및 컨트롤러 인증 아키텍처(ArchUnit) 테스트 추가
Reviewed changes
Copilot reviewed 33 out of 33 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| reservation/build.gradle | Validation starter 및 ArchUnit 테스트 의존성 추가 |
| reservation/src/main/java/net/catsnap/CatsnapReservation/program/presentation/ProgramController.java | 프로그램 생성 REST API 엔드포인트 추가 |
| reservation/src/main/java/net/catsnap/CatsnapReservation/program/application/ProgramService.java | 프로그램 생성 유스케이스 구현 |
| reservation/src/main/java/net/catsnap/CatsnapReservation/program/application/dto/request/ProgramCreateRequest.java | 프로그램 생성 요청 DTO(+ validation) 추가 |
| reservation/src/main/java/net/catsnap/CatsnapReservation/program/application/dto/response/ProgramResponse.java | 프로그램 생성 응답 DTO 추가 |
| reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/Program.java | Program 애그리거트 및 소프트 삭제/수정 로직 추가 |
| reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/vo/Title.java | 제목 VO 및 제약 검증 추가 |
| reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/vo/Price.java | 가격 VO 및 제약 검증/무료 여부 로직 추가 |
| reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/vo/Duration.java | 소요시간 VO 및 표현/계산 로직 추가 |
| reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/vo/Description.java | 설명 VO 및 제약/empty 판정 로직 추가 |
| reservation/src/main/java/net/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramRepository.java | Program JPA Repository 추가 |
| reservation/src/main/java/net/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramSpecification.java | Program 조회용 Specification 유틸 추가 |
| reservation/src/main/java/net/catsnap/CatsnapReservation/program/infrastructure/converter/TitleConverter.java | Title VO JPA 컨버터 추가 |
| reservation/src/main/java/net/catsnap/CatsnapReservation/program/infrastructure/converter/PriceConverter.java | Price VO JPA 컨버터 추가 |
| reservation/src/main/java/net/catsnap/CatsnapReservation/program/infrastructure/converter/DurationConverter.java | Duration VO JPA 컨버터 추가 |
| reservation/src/main/java/net/catsnap/CatsnapReservation/program/infrastructure/converter/DescriptionConverter.java | Description VO JPA 컨버터 추가 |
| reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/web/resolver/UserId.java | 컨트롤러 파라미터 userId 주입용 어노테이션 추가 |
| reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/web/resolver/UserIdArgumentResolver.java | Passport 헤더에서 userId 추출하는 ArgumentResolver 추가 |
| reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/web/config/WebMvcConfig.java | ArgumentResolver 등록 로직 추가 |
| reservation/src/test/java/net/catsnap/CatsnapReservation/program/presentation/ProgramControllerTest.java | ProgramController 생성 API/검증/인가 테스트 추가 |
| reservation/src/test/java/net/catsnap/CatsnapReservation/program/application/ProgramServiceTest.java | ProgramService 생성 유스케이스 통합 테스트 추가 |
| reservation/src/test/java/net/catsnap/CatsnapReservation/program/domain/ProgramTest.java | Program 도메인 동작 테스트 추가 |
| reservation/src/test/java/net/catsnap/CatsnapReservation/program/domain/vo/TitleTest.java | Title VO 테스트 추가 |
| reservation/src/test/java/net/catsnap/CatsnapReservation/program/domain/vo/PriceTest.java | Price VO 테스트 추가 |
| reservation/src/test/java/net/catsnap/CatsnapReservation/program/domain/vo/DurationTest.java | Duration VO 테스트 추가 |
| reservation/src/test/java/net/catsnap/CatsnapReservation/program/domain/vo/DescriptionTest.java | Description VO 테스트 추가 |
| reservation/src/test/java/net/catsnap/CatsnapReservation/program/infrastructure/converter/TitleConverterTest.java | TitleConverter 테스트 추가 |
| reservation/src/test/java/net/catsnap/CatsnapReservation/program/infrastructure/converter/PriceConverterTest.java | PriceConverter 테스트 추가 |
| reservation/src/test/java/net/catsnap/CatsnapReservation/program/infrastructure/converter/DurationConverterTest.java | DurationConverter 테스트 추가 |
| reservation/src/test/java/net/catsnap/CatsnapReservation/program/infrastructure/converter/DescriptionConverterTest.java | DescriptionConverter 테스트 추가 |
| reservation/src/test/java/net/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramSpecificationTest.java | ProgramSpecification 테스트 추가 |
| reservation/src/test/java/net/catsnap/CatsnapReservation/shared/presentation/web/resolver/UserIdArgumentResolverTest.java | UserIdArgumentResolver 단위 테스트 추가 |
| reservation/src/test/java/net/catsnap/CatsnapReservation/architecture/ControllerAuthenticationArchitectureTest.java | 모든 컨트롤러 메서드 인증 어노테이션 강제 ArchUnit 테스트 추가 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/Program.java
Show resolved
Hide resolved
.../net/catsnap/CatsnapReservation/shared/presentation/web/resolver/UserIdArgumentResolver.java
Show resolved
Hide resolved
...on/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/web/resolver/UserId.java
Show resolved
Hide resolved
...java/net/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramRepository.java
Outdated
Show resolved
Hide resolved
...t/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramSpecificationTest.java
Outdated
Show resolved
Hide resolved
reservation/src/test/java/net/catsnap/CatsnapReservation/program/domain/ProgramTest.java
Outdated
Show resolved
Hide resolved
...java/net/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramRepository.java
Outdated
Show resolved
Hide resolved
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In `@reservation/build.gradle`:
- Around line 76-77: Replace the hardcoded dependency coordinate
com.tngtech.archunit:archunit-junit5:1.4.1 with a single managed version: add an
entry like archunitVersion=1.4.1 to gradle.properties (or create a
libs.versions.toml alias such as libs.archunit.junit5) and update the dependency
declaration in both build files to use the property (e.g.,
"com.tngtech.archunit:archunit-junit5:${archunitVersion}") or the
version-catalog alias (libs.archunit.junit5) so the version is maintained in one
place.
In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/Program.java`:
- Around line 125-135: The update() method on Program must guard against
updating soft-deleted programs: at the start of Program.update(...) check the
program's deletion indicator (e.g., a boolean flag like isDeleted(), or a
timestamp field like deletedAt != null) and if the program is deleted throw an
appropriate exception (IllegalStateException or your domain-specific exception)
instead of applying new Title/Description/Price/Duration; keep the validation
before creating/applying Title, Description, Price, and Duration to prevent
modifications to soft-deleted entities.
- Around line 143-148: Program.delete(LocalDateTime) currently overwrites an
existing deletedAt; add duplicate-delete protection by checking the instance
field before assignment: keep the null-check for the incoming deletedAt, then if
this.deletedAt != null either make the operation idempotent by returning
immediately or throw a clear exception (e.g. IllegalStateException("이미
삭제되었습니다.")) — update the delete method in class Program and related tests to
reflect the chosen behavior.
In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/shared/infrastructure/ClockConfig.java`:
- Around line 27-28: The ClockConfig.clock() currently returns
Clock.systemDefaultZone(); change it to return Clock.systemUTC() so the
application uses a stable UTC clock instead of JVM default timezone; update the
method in ClockConfig (replace Clock.systemDefaultZone() with Clock.systemUTC())
and ensure any places that format/display times perform explicit timezone
conversion for user-facing output.
In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/program/domain/ProgramTest.java`:
- Around line 154-156: The createDefaultProgram() method in ProgramTest
duplicates fixture logic; replace its usage with ProgramFixture.createDefault()
to avoid duplicated defaults — update ProgramTest to call
ProgramFixture.createDefault() wherever createDefaultProgram() is used and
remove the private createDefaultProgram() helper to centralize default Program
creation in ProgramFixture.
In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramRepositoryTest.java`:
- Around line 16-21: Replace the inline auditing annotation in the
ProgramRepositoryTest class by removing `@EnableJpaAuditing` and importing the
centralized config: add `@Import`(JpaAuditingConfig.class) to the
ProgramRepositoryTest declaration so the test uses the shared JpaAuditingConfig
instead of enabling JPA auditing locally.
reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/Program.java
Show resolved
Hide resolved
reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/Program.java
Show resolved
Hide resolved
reservation/src/main/java/net/catsnap/CatsnapReservation/shared/infrastructure/ClockConfig.java
Show resolved
Hide resolved
reservation/src/test/java/net/catsnap/CatsnapReservation/program/domain/ProgramTest.java
Outdated
Show resolved
Hide resolved
.../net/catsnap/CatsnapReservation/program/infrastructure/repository/ProgramRepositoryTest.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/program/domain/Program.java`:
- Around line 165-167: The isOwnedBy method on Program currently calls
this.photographerId.equals(photographerId); make it defensive against a null
parameter by changing the implementation in Program.isOwnedBy to safely compare
photographer IDs (for example, return Objects.equals(this.photographerId,
photographerId) or explicitly if (photographerId == null) return false; return
this.photographerId.equals(photographerId)); ensure you add the
java.util.Objects import if using Objects.equals and update the method body
accordingly.
In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/program/domain/ProgramTest.java`:
- Around line 81-94: Add a new unit test that verifies Program.update(...)
throws when called on a deleted program: create a Program via
ProgramFixture.createDefaultDeleted(), then call program.update("새 제목", "새 설명",
200000L, 120) and assert that it throws IllegalStateException (use
assertThatThrownBy or equivalent). Name the test e.g. 삭제된_프로그램_수정_시_예외가_발생한다 and
place it alongside the existing 성공 테스트 to cover the deletion guard in
Program.update.
📌 관련 이슈
✨ PR 세부 내용
작가가 program을 만드는 api 개발했습니다.
Summary by CodeRabbit
새로운 기능
테스트