Skip to content

Comments

[FEAT] 예약 서버 인증 및 공통 응답 정의 (#279)#280

Merged
redblackblossom merged 10 commits intomainfrom
feat/#279/reservation_authorization
Jan 25, 2026
Merged

[FEAT] 예약 서버 인증 및 공통 응답 정의 (#279)#280
redblackblossom merged 10 commits intomainfrom
feat/#279/reservation_authorization

Conversation

@redblackblossom
Copy link
Contributor

@redblackblossom redblackblossom commented Jan 24, 2026

📌 관련 이슈

✨ PR 세부 내용

예약 서버 인증 및 공통 응답 정의했습니다.

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • API 응답/오류 형식 표준화 및 다계층 결과 코드 추가
    • 전역 예외 처리로 일관된 오류 응답 제공
    • 컨트롤러 수준의 역할 기반 인증·권한 인터셉터 및 패스포트 기반 인증 구성
    • 테스트용 패스포트 헬퍼 및 테스트 구성 추가
  • 테스트

    • 인증, 권한, 예외 처리, 응답 래퍼 등에 대한 포괄적 단위/통합 테스트 추가

✏️ Tip: You can customize this high-level summary in your review settings.

@redblackblossom redblackblossom self-assigned this Jan 24, 2026
@redblackblossom redblackblossom added the ✨feature 기능 추가 label Jan 24, 2026
@redblackblossom redblackblossom linked an issue Jan 24, 2026 that may be closed by this pull request
@coderabbitai
Copy link

coderabbitai bot commented Jan 24, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

reservation 모듈에 authorization-shared 의존성을 추가하고, ResultCode/ResultResponse 기반 표준 응답·에러 모델, Passport 기반 인증·인가 인터셉터(및 설정), 글로벌 예외 처리기와 관련 테스트/테스트 헬퍼를 추가했습니다.

Changes

Cohort / File(s) 변경 요약
빌드/설정
\reservation/build.gradle``
implementation project(':authorization-shared') 추가
앱 설정 및 MVC 구성
\reservation/src/main/java/.../presentation/web/config/PassportConfig.java`, `reservation/src/main/java/.../presentation/web/config/WebMvcConfig.java`, `reservation/src/test/resources/application.yml``
PassportHandler 빈 구성, 5개 인터셉터 등록, 테스트용 passport.secret-key 프로퍼티 추가
공통 응답/코드 계약
\reservation/src/main/java/.../ResultCode.java`, `reservation/src/main/java/.../presentation/response/ResultResponse.java``
ResultCode 인터페이스(HTTP 상태, 코드, 메시지) 추가, 제네릭 ResultResponse 팩토리 메서드 추가
도메인 에러 타입
\reservation/src/main/java/.../domain/error/DomainErrorCode.java`, `reservation/src/main/java/.../domain/error/DomainException.java``
DomainErrorCode(enum) 및 DomainException(RuntimeException 래핑) 추가
프레젠테이션 에러 타입 및 핸들러
\reservation/src/main/java/.../presentation/error/PresentationErrorCode.java`, `reservation/src/main/java/.../presentation/error/PresentationException.java`, `reservation/src/main/java/.../presentation/GlobalExceptionHandler.java``
PresentationErrorCode(enum), PresentationException, 전역 예외 처리기(다수의 handler 메서드) 추가
성공 코드
\reservation/src/main/java/.../presentation/success/PresentationSuccessCode.java``
성공 응답 코드 enum 추가(READ, CREATE, UPDATE, DELETE, NO_CONTENT)
인증/인가 인터셉터
\reservation/src/main/java/.../presentation/web/interceptor/AbstractAuthInterceptor.java`, `reservation/src/main/java/.../presentation/web/interceptor/*Interceptor.java``
추상 인증 인터셉터(공통 로직) 및 Admin/AnyUser/LoginUser/LoginPhotographer/LoginModel 인터셉터 추가(헤더 추출, Passport 파싱, 권한 검증, 예외 던짐)
테스트 유틸리티
\reservation/src/test/java/.../fixture/PassportTestHelper.java``
MockMvc용 X-Passport 헤더 생성·부착 헬퍼 추가
단위 테스트
\reservation/src/test/java/.../domain/error/DomainErrorCodeTest.java`, `reservation/src/test/java/.../domain/error/DomainExceptionTest.java`, `reservation/src/test/java/.../presentation/*Tests.java`, `reservation/src/test/java/.../presentation/web/interceptor/*InterceptorTest.java``
각 enum/예외/핸들러/인터셉터 동작 검증 테스트 추가 (다수의 시나리오 포함)

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant DispatcherServlet
    participant Interceptor as AbstractAuthInterceptor
    participant PassportHandler
    participant Controller
    participant GlobalExceptionHandler

    Client->>DispatcherServlet: HTTP 요청 (X-Passport 헤더)
    DispatcherServlet->>Interceptor: preHandle(request, response, handler)

    rect rgba(200, 150, 255, 0.5)
        Note over Interceptor: 핸들러에 대상 애노테이션 존재 여부 검사
        Interceptor->>Interceptor: 애노테이션 확인
    end

    rect rgba(150, 200, 255, 0.5)
        Note over Interceptor: Passport 검증 흐름
        Interceptor->>PassportHandler: parse(X-Passport)
        alt 파싱 실패 (Invalid/Expired)
            PassportHandler-->>Interceptor: Exception
            Interceptor->>GlobalExceptionHandler: PresentationException(요류코드) 발생
        else 파싱 성공
            PassportHandler-->>Interceptor: Passport
            Interceptor->>Interceptor: 권한 비교(allowedAuthorities)
            alt 권한 부족
                Interceptor->>GlobalExceptionHandler: PresentationException(FORBIDDEN) 발생
            else 권한 허용
                Interceptor-->>DispatcherServlet: true (요청 계속)
            end
        end
    end

    rect rgba(200, 255, 200, 0.5)
        Controller->>Controller: 비즈니스 처리
        Controller-->>Client: ResponseEntity<ResultResponse>
    end

    rect rgba(255, 150, 150, 0.5)
        Note over GlobalExceptionHandler: 예외 수신 및 ResultResponse 변환
        GlobalExceptionHandler-->>Client: ResponseEntity<ResultResponse> (상태, 코드, 메시지)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning PR 변경사항(인증 및 응답 처리)과 연결된 이슈 #278(작가 프로그램 설정 로직)의 목적이 명확히 다릅니다. PR의 변경사항이 연결된 이슈 #278의 목표(작가 프로그램 설정 로직 개발)와 일치하지 않습니다. 올바른 이슈를 링크하거나 이슈 내용을 확인하시기 바랍니다.
Docstring Coverage ⚠️ Warning Docstring coverage is 26.05% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목은 예약 서버의 인증 및 공통 응답 정의라는 주요 변경사항을 명확하게 설명하고 있습니다.
Description check ✅ Passed PR 설명은 필수 템플릿 섹션을 포함하고 있으나, 세부 내용 섹션이 간략하게만 작성되어 있습니다.
Out of Scope Changes check ✅ Passed PR의 모든 변경사항은 예약 서버의 인증 및 공통 응답 처리 구현에 집중되어 있으며 범위를 벗어나지 않습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Jan 24, 2026

Test Results (reservation)

131 tests   131 ✅  2s ⏱️
 21 suites    0 💤
 21 files      0 ❌

Results for commit 90fbac1.

♻️ This comment has been updated with latest results.

@codecov
Copy link

codecov bot commented Jan 24, 2026

Codecov Report

❌ Patch coverage is 91.05691% with 11 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ation/web/interceptor/AbstractAuthInterceptor.java 75.86% 4 Missing and 3 partials ⚠️
...on/shared/presentation/GlobalExceptionHandler.java 81.81% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR wires the reservation service into the shared authorization/Passport mechanism and introduces a unified ResultCode-based response and error-handling layer for the reservation API.

Changes:

  • Integrates the authorization-shared module and configures a PassportHandler bean plus HTTP interceptors for role-based access control using Passport headers.
  • Introduces a unified ResultCode abstraction with reservation-specific success and error code enums, plus a ResultResponse wrapper and global exception handler.
  • Adds comprehensive unit tests for interceptors, result/exception handling, error/success codes, and Passport test utilities.

Reviewed changes

Copilot reviewed 31 out of 31 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
reservation/build.gradle Adds dependency on the shared authorization module so reservation can use Passport and auth-related types.
reservation/src/test/resources/application.yml Provides a test-only passport.secret-key property so the PassportHandler bean can be instantiated in tests.
reservation/src/main/java/net/catsnap/CatsnapReservation/shared/ResultCode.java Defines a common interface for HTTP status, business code, and message; used by success/error enums and exceptions.
reservation/src/main/java/net/catsnap/CatsnapReservation/shared/domain/error/DomainErrorCode.java Adds domain-layer error codes (e.g., ED000 for domain constraint violations) implementing ResultCode.
reservation/src/main/java/net/catsnap/CatsnapReservation/shared/domain/error/DomainException.java Introduces a domain-layer base exception that carries a ResultCode and standardizes messages.
reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/error/PresentationErrorCode.java Adds presentation-layer error codes for HTTP/API and auth failures (EA0xx / EA1xx) implementing ResultCode.
reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/error/PresentationException.java Adds a presentation-layer base exception carrying a ResultCode for API-facing errors.
reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/success/PresentationSuccessCode.java Defines common success codes (SA0xx) for CRUD operations on the presentation layer.
reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/response/ResultResponse.java Introduces a generic response wrapper and static factories to build ResponseEntity from ResultCode and optional data.
reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/GlobalExceptionHandler.java Adds a @RestControllerAdvice that maps framework, domain, and presentation exceptions to ResultResponse using PresentationErrorCode.
reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/web/config/PassportConfig.java Configures a BinaryPassportHandler bean, injecting the Passport secret key from configuration.
reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/web/config/WebMvcConfig.java Registers all auth-related interceptors (admin, any user, specific roles) with Spring MVC.
reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/web/interceptor/AbstractAuthInterceptor.java Implements the shared preHandle logic: detect target annotations, parse Passport header, check allowed authorities, and map Passport exceptions to PresentationErrorCode.
reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/web/interceptor/AdminInterceptor.java Specializes AbstractAuthInterceptor for @Admin, allowing only ADMIN authority.
reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/web/interceptor/AnyUserInterceptor.java Specializes AbstractAuthInterceptor for @AnyUser, allowing all authorities but still requiring a valid Passport header.
reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/web/interceptor/LoginUserInterceptor.java Specializes AbstractAuthInterceptor for @LoginUser, allowing MODEL, PHOTOGRAPHER, ADMIN but not ANONYMOUS.
reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/web/interceptor/LoginPhotographerInterceptor.java Specializes AbstractAuthInterceptor for @LoginPhotographer, allowing PHOTOGRAPHER and ADMIN only.
reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/web/interceptor/LoginModelInterceptor.java Specializes AbstractAuthInterceptor for @LoginModel, allowing MODEL and ADMIN only.
reservation/src/test/java/net/catsnap/CatsnapReservation/shared/presentation/web/interceptor/AdminInterceptorTest.java Verifies @Admin interception: pass with ADMIN, UNAUTHORIZED for missing header, FORBIDDEN for non-admin, INVALID_PASSPORT mapping.
reservation/src/test/java/net/catsnap/CatsnapReservation/shared/presentation/web/interceptor/AnyUserInterceptorTest.java Tests @AnyUser interception: all authorities including ANONYMOUS are allowed, but missing headers yield UNAUTHORIZED.
reservation/src/test/java/net/catsnap/CatsnapReservation/shared/presentation/web/interceptor/LoginUserInterceptorTest.java Tests @LoginUser interception: MODEL/PHOTOGRAPHER/ADMIN allowed, ANONYMOUS -> FORBIDDEN, missing header -> UNAUTHORIZED, methods without annotation bypassed.
reservation/src/test/java/net/catsnap/CatsnapReservation/shared/presentation/web/interceptor/LoginPhotographerInterceptorTest.java Tests @LoginPhotographer interception: PHOTOGRAPHER/ADMIN allowed, MODEL -> FORBIDDEN, missing header -> UNAUTHORIZED, no-annotation bypass.
reservation/src/test/java/net/catsnap/CatsnapReservation/shared/presentation/web/interceptor/LoginModelInterceptorTest.java Tests @LoginModel interception: MODEL/ADMIN allowed, PHOTOGRAPHER -> FORBIDDEN, missing header -> UNAUTHORIZED, no-annotation bypass.
reservation/src/test/java/net/catsnap/CatsnapReservation/shared/presentation/success/PresentationSuccessCodeTest.java Ensures all PresentationSuccessCode values have codes starting with S, non-null HttpStatus, and non-blank messages.
reservation/src/test/java/net/catsnap/CatsnapReservation/shared/presentation/response/ResultResponseTest.java Verifies ResultResponse.of builds correct HTTP status, code, message, and data with and without payload.
reservation/src/test/java/net/catsnap/CatsnapReservation/shared/presentation/error/PresentationExceptionTest.java Tests all PresentationException constructors and messages, including specific authentication/authorization codes.
reservation/src/test/java/net/catsnap/CatsnapReservation/shared/presentation/error/PresentationErrorCodeTest.java Asserts all presentation error codes follow EA-prefix conventions and are grouped by API vs auth categories.
reservation/src/test/java/net/catsnap/CatsnapReservation/shared/presentation/GlobalExceptionHandlerTest.java Covers most exception handlers in GlobalExceptionHandler (framework, presentation, domain, generic) and their mapped codes/statuses.
reservation/src/test/java/net/catsnap/CatsnapReservation/shared/domain/error/DomainExceptionTest.java Tests all DomainException constructors and message behavior with DomainErrorCode.
reservation/src/test/java/net/catsnap/CatsnapReservation/shared/domain/error/DomainErrorCodeTest.java Verifies all DomainErrorCode values start with ED, have an HttpStatus, and non-blank messages.
reservation/src/test/java/net/catsnap/CatsnapReservation/shared/fixture/PassportTestHelper.java Adds a @TestComponent utility to attach signed or invalid Passport headers to MockMvc requests for different authorities.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 21

🤖 Fix all issues with AI agents
In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/shared/domain/error/DomainErrorCode.java`:
- Around line 9-26: Add specific domain error enum members to DomainErrorCode to
replace the single generic DOMAIN_CONSTRAINT_VIOLATION for reservation
scenarios: define DUPLICATE_RESERVATION, UNAVAILABLE_TIME_SLOT, and
ALREADY_CANCELLED_RESERVATION in the DomainErrorCode enum (use appropriate
HttpStatus values such as CONFLICT or BAD_REQUEST), assign unique codes like
"ED001"/"ED002"/"ED003" and clear messages (e.g. "예약이 중복되었습니다.", "해당 시간대에는 예약할 수
없습니다.", "이미 취소된 예약입니다."). Update any code paths that throw
DOMAIN_CONSTRAINT_VIOLATION in reservation-related logic to throw the new
specific enum values (refer to DomainErrorCode and the enum constant names) so
clients can handle errors more granularly.

In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/shared/domain/error/DomainException.java`:
- Around line 14-49: DomainException and PresentationException duplicate the
same ResultCode field and three constructors; extract a common superclass (e.g.,
ResultCodeException or AbstractDomainException) that holds private final
ResultCode resultCode and the three constructors (ResultCode), (ResultCode,
Throwable), and (ResultCode, String) delegating to super(...) and then have
DomainException and PresentationException extend that new base class (removing
their duplicate fields/constructors); update any code that constructs or catches
DomainException/PresentationException to reference the new superclass where
appropriate.
- Around line 45-48: The DomainException constructor currently appends
additionalMessage directly which yields "... - null" when null; update the
constructor (DomainException(ResultCode, String)) to only append the separator
and additionalMessage when additionalMessage is non-null/non-empty (or replace
null with an empty string), mirroring PresentationException behavior so the
resulting super(message) never contains "null".

In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/error/PresentationException.java`:
- Around line 46-49: The PresentationException constructor currently
concatenates resultCode.getMessage() with " - " + additionalMessage which yields
" - null" when additionalMessage is null; update the constructor
(PresentationException(ResultCode, String)) to defensively handle null/blank
additionalMessage by checking it and only appending the separator and
additionalMessage when non-null/non-empty (or use a default like empty string),
then assign this.resultCode as before to avoid NPEs and unintended "null" text
in the exception message.

In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/GlobalExceptionHandler.java`:
- Around line 79-91: The handler
GlobalExceptionHandler.handleMethodArgumentNotValidException computes a detailed
errorMessage but returns
ResultResponse.of(PresentationErrorCode.INVALID_REQUEST_BODY) without including
it; change the return to include the message (and field name if available) via
ResultResponse's API so the client sees the failure reason — e.g. extract
fieldName = fieldError != null ? fieldError.getField() : null and pass
errorMessage (and fieldName) into ResultResponse (for example
ResultResponse.of(PresentationErrorCode.INVALID_REQUEST_BODY, errorMessage) or
ResultResponse.of(..., fieldName, errorMessage) depending on the existing
ResultResponse factory/builder).
- Around line 29-37: The NoHandlerFoundException handler in
GlobalExceptionHandler (method handleNoHandlerFoundException) will never be
invoked unless Spring is configured to throw this exception; add the MVC/web
resource properties to the application configuration so Spring raises
NoHandlerFoundException and disables default resource mappings: set
spring.mvc.throw-exception-if-no-handler-found: true and
spring.web.resources.add-mappings: false in your application configuration file
(e.g., application.yml) so the existing handler can be reached.

In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/response/ResultResponse.java`:
- Around line 32-49: The of(ResultCode resultCode) overload currently delegates
to of(resultCode, null) which builds a body even for NO_CONTENT, but Spring
strips bodies for 204 so clients won't receive code/message; update handling in
ResultResponse.of (the generic static factory) to explicitly detect
PresentationSuccessCode.NO_CONTENT (or resultCode.getHttpStatus() ==
HttpStatus.NO_CONTENT) and either return
ResponseEntity.status(HttpStatus.NO_CONTENT).build() for a true no-body
response, or normalize the contract by mapping NO_CONTENT to a 200/201
resultCode before building the body; modify ResultResponse.of(ResultCode, T) or
add a new ofNoContent(ResultCode) method accordingly and adjust callers to use
the new behavior.

In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/web/config/WebMvcConfig.java`:
- Around line 21-33: The explicit constructor in WebMvcConfig duplicates
boilerplate for injecting AdminInterceptor, AnyUserInterceptor,
LoginUserInterceptor, LoginPhotographerInterceptor, and LoginModelInterceptor;
replace it by annotating the WebMvcConfig class with Lombok's
`@RequiredArgsConstructor`, make the corresponding fields final (e.g.,
adminInterceptor, anyUserInterceptor, loginUserInterceptor,
loginPhotographerInterceptor, loginModelInterceptor), remove the manual
constructor, and add the Lombok import (lombok.RequiredArgsConstructor) so
Spring will still perform constructor injection without the boilerplate.
- Around line 35-42: addInterceptors currently registers adminInterceptor,
anyUserInterceptor, loginUserInterceptor, loginPhotographerInterceptor, and
loginModelInterceptor globally which causes preHandle to run for all requests;
update each registry.addInterceptor(...) call in
addInterceptors(InterceptorRegistry registry) to exclude infrastructure paths by
chaining .excludePathPatterns("/", "/actuator/**", "/swagger-ui/**",
"/v3/api-docs/**") so health, actuator and swagger endpoints are omitted from
interceptor execution.

In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/web/interceptor/AbstractAuthInterceptor.java`:
- Around line 139-149: The caught exceptions thrown from passportHandler.parse
(PassportParsingException, InvalidPassportException, ExpiredPassportException)
should be chained into the new PresentationException so the original stack trace
is preserved; update the catch blocks in AbstractAuthInterceptor to pass the
caught exception (e) as the cause when constructing PresentationException with
PresentationErrorCode.INVALID_PASSPORT and
PresentationErrorCode.EXPIRED_PASSPORT, and likewise consider including a cause
when throwing PresentationException for the authority check failure (where you
throw new PresentationException(PresentationErrorCode.FORBIDDEN)) so all
PresentationException instances preserve original exception context.
- Around line 129-150: validateUserAuthority currently parses the Passport only
to check authority and drops it; persist the parsed Passport for downstream
reuse by storing the result of passportHandler.parse(signedPassport) as a
request attribute (e.g., request.setAttribute("passport", passport)) after
successful parsing and before authority checks so controllers can retrieve
Passport (type Passport) without re-parsing.

In
`@reservation/src/main/java/net/catsnap/CatsnapReservation/shared/presentation/web/interceptor/AdminInterceptor.java`:
- Line 4: The import of AbstractAuthInterceptor is redundant because
AdminInterceptor is in the same package; remove the unnecessary import line that
references AbstractAuthInterceptor so AdminInterceptor.java relies on
package-level visibility and avoids an unused import.

In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/shared/fixture/PassportTestHelper.java`:
- Around line 57-83: Add two no-argument-overload helper methods for
photographer and model: implement withPhotographer(MockHttpServletRequestBuilder
builder) and withModel(MockHttpServletRequestBuilder builder) that delegate to
withAuthority(builder, DEFAULT_ADMIN_USER_ID, CatsnapAuthority.PHOTOGRAPHER) and
withAuthority(builder, DEFAULT_ADMIN_USER_ID, CatsnapAuthority.MODEL)
respectively so tests can omit the userId similar to withAdmin.

In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/shared/presentation/error/PresentationErrorCodeTest.java`:
- Around line 39-59: The tests explicitly list PresentationErrorCode enum
members, which risks missing newly added codes; change the
API_요청_에러는_EA0XX_코드를_가진다 and 인증_인가_에러는_EA1XX_코드를_가진다 tests to verify prefixes
dynamically by iterating over PresentationErrorCode.values() and asserting codes
that belong to the respective category start with "EA0" or "EA1" (use each
enum's name or a maintained category predicate to decide membership); update the
tests to build the membership check from a set/list of names or a helper method
so adding new enum entries is caught automatically.

In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/shared/presentation/GlobalExceptionHandlerTest.java`:
- Around line 44-46: The test in GlobalExceptionHandlerTest calls
response.getBody() without null-check which can cause an NPE; update the
assertion to first assert the body is not null (e.g.,
assertThat(response.getBody()).isNotNull()) and then assert the code by
extracting ResultResponse::getCode (or perform an explicit null check and then
assertEquals on getCode) so the failure message is clearer when
response.getBody() is null; keep the existing HttpStatus assertion and assert
the code equals PresentationErrorCode.NOT_FOUND_API.getCode().

In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/shared/presentation/success/PresentationSuccessCodeTest.java`:
- Around line 15-37: Replace the three for-loop based tests in
PresentationSuccessCodeTest (methods 모두_SuccessCode는_S로_시작하는_코드를_가진다,
모두_SuccessCode는_HttpStatus를_가진다, 모두_SuccessCode는_메시지를_가진다) with JUnit 5
parameterized tests using `@ParameterizedTest` and
`@EnumSource`(PresentationSuccessCode.class) so each test receives a single
PresentationSuccessCode and failures report the specific enum constant; keep the
same assertions (getCode().startsWith("S"), getHttpStatus() != null,
getMessage() not blank) but change the test signatures to accept a
PresentationSuccessCode parameter for clearer failure diagnostics.

In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/shared/presentation/web/interceptor/AnyUserInterceptorTest.java`:
- Around line 57-119: Tests repeat Passport creation across
ANONYMOUS/MODEL/PHOTOGRAPHER/ADMIN cases; extract that logic into a private
helper like createPassport(Long userId, CatsnapAuthority authority) that builds
the Instant now/exp and returns new Passport(...), then replace each test's
repeated Instant/Passport block with a call to createPassport and keep the rest
(when(passportHandler.parse(...)), request.addHeader("X-Passport", ...),
createHandlerMethod("anyUserMethod"), and
assertTrue(anyUserInterceptor.preHandle(...))) unchanged.
- Around line 121-133: Add tests to AnyUserInterceptorTest covering
INVALID_PASSPORT and EXPIRED_PASSPORT flows: simulate the token-parsing failures
that cause AbstractAuthInterceptor.validateUserAuthority to throw
InvalidPassportException and ExpiredPassportException and assert that
anyUserInterceptor.preHandle(request, response, handler) throws a
PresentationException with PresentationErrorCode.UNAUTHORIZED (or the expected
code for each case). Use the same pattern as the existing
인증_헤더가_없으면_UNAUTHORIZED_예외가_발생한다() test, reusing
createHandlerMethod("anyUserMethod") and arranging the request/authorization
header to trigger
PassportParsingException/InvalidPassportException/ExpiredPassportException paths
(or mock the underlying passport parsing to throw those exceptions) and validate
the resulting PresentationException.getResultCode().

In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/shared/presentation/web/interceptor/LoginModelInterceptorTest.java`:
- Around line 89-122: Add three tests to LoginModelInterceptorTest: (1) test
ANONYMOUS 권한 is treated as forbidden: mock Passport with
CatsnapAuthority.ANONYMOUS and have passportHandler.parse(...) return it, set
X-Passport header and call loginModelInterceptor.preHandle(request, response,
handler) then assert a PresentationException with
PresentationErrorCode.FORBIDDEN; (2) test expired passport: mock
passportHandler.parse(...) to throw ExpiredPassportException, add X-Passport
header, call preHandle and assert a PresentationException with
PresentationErrorCode.EXPIRED_PASSPORT; (3) test invalid passport parsing: mock
passportHandler.parse(...) to throw PassportParsingException (or
InvalidPassportException), add X-Passport header, call preHandle and assert a
PresentationException with PresentationErrorCode.INVALID_PASSPORT; use the
existing createHandlerMethod("modelMethod") and request/response fixtures and
the same assertThatThrownBy pattern as other tests.

In
`@reservation/src/test/java/net/catsnap/CatsnapReservation/shared/presentation/web/interceptor/LoginUserInterceptorTest.java`:
- Around line 105-124: Add a unit test that verifies
loginUserInterceptor.preHandle throws ExpiredPassportException when
Passport.authority() detects an expired passport: create an expired Passport
instance (set issued and exp so exp < now), stub passportHandler.parse(...) to
return that expired Passport, add the "X-Passport" header and a HandlerMethod
via createHandlerMethod("loginUserMethod"), then assert that calling
loginUserInterceptor.preHandle(request, response, handler) results in an
ExpiredPassportException; reference Passport, Passport.authority(),
passportHandler.parse, loginUserInterceptor.preHandle, and
ExpiredPassportException when implementing the test method.

In `@reservation/src/test/resources/application.yml`:
- Around line 21-22: Fix the minor typo in the passport secret value: replace
the misspelled "secreykey" substring in the passport.secret-key value (e.g.,
"itstestsecreykey123123123...") with "secretkey" so the value reads
"itstestsecretkey..."; update the value in the application.yml entry for
passport.secret-key to correct the spelling.

@redblackblossom redblackblossom merged commit 0b2e35c into main Jan 25, 2026
10 checks passed
@redblackblossom redblackblossom deleted the feat/#279/reservation_authorization branch January 25, 2026 05:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨feature 기능 추가

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] reservation 서버 authorization 설정 [FEAT] 작가가 프로그램을 설정하는 로직 개발

1 participant