[#3] 인증 / 회원 코드 코틀린 마이그레이션 (#4)

<!-- 제목 양식 -->
<!-- [이슈번호] 작업 요약 (예시: [#10] Gitea 템플릿 생성) -->

## 📝 관련 이슈 및 PR

**PR과 관련된 이슈 번호**
- #3

##  작업 내용
<!-- 어떤 작업을 했는지 알려주세요! -->
### 0. 공통
- 패키지 구조 수정(web, business, infrastructure 구조)
- Swagger-UI 어노테이션을 별도의 인터페이스를 만들어 컨트롤러에서 분리
- 결합도 높은 클래스 통합(ex: Member 엔티티와 Role enum 등)

### 1. 회원 도메인
- 기능 자체가 적어서 변화된 내용이 크게 없음. 패키지 구조 수정과 클래스 통합 정도의 과정이 대부분이었음.

### 2. 인증 도메인
- 전체적으로 코드 중복이 많아, 확장함수 및 클래스 통합으로 중복 코드를 상당히 많이 제거하였음.
- JwtHandler와 Interceptor에서 모두 이뤄지던 null 예외 처리를 JwtHandler에서만 처리하도록 수정

## 🧪 테스트
<!-- 어떤 테스트를 생각했고 진행했는지 알려주세요! -->
- 모든 테스트는 Kotest 기반으로 수정 & 로그인 및 토큰 처리가 필요한 API 테스트는 mocking을 활용하도록 수정
- 향후 테스트도 꼭 필요하다고 느껴지는 테스트가 아니라면 DB 사용보다는 mocking을 활용할 예정

## 📚 참고 자료 및 기타
<!-- 참고한 자료, 또는 논의할 사항이 있다면 알려주세요! -->

Reviewed-on: #4
Co-authored-by: pricelees <priceelees@gmail.com>
Co-committed-by: pricelees <priceelees@gmail.com>
This commit is contained in:
이상진 2025-07-13 12:18:50 +00:00 committed by 이상진
parent 7121d36523
commit 22e6ad4f71
65 changed files with 1127 additions and 1292 deletions

View File

@ -0,0 +1,43 @@
package roomescape.member.business
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import roomescape.member.infrastructure.persistence.Member
import roomescape.member.infrastructure.persistence.MemberRepository
import roomescape.member.web.MembersResponse
import roomescape.member.web.toResponse
import roomescape.system.exception.ErrorType
import roomescape.system.exception.RoomEscapeException
@Service
@Transactional(readOnly = true)
class MemberService(
private val memberRepository: MemberRepository
) {
fun readAllMembers(): MembersResponse = MembersResponse(
memberRepository.findAll()
.map { it.toResponse() }
.toList()
)
fun findById(memberId: Long): Member = memberRepository.findByIdOrNull(memberId)
?: throw RoomEscapeException(
ErrorType.MEMBER_NOT_FOUND,
String.format("[memberId: %d]", memberId),
HttpStatus.BAD_REQUEST
)
fun findMemberByEmailAndPassword(email: String, password: String): Member =
memberRepository.findByEmailAndPassword(email, password)
?: throw RoomEscapeException(
ErrorType.MEMBER_NOT_FOUND,
String.format("[email: %s, password: %s]", email, password),
HttpStatus.BAD_REQUEST
)
fun existsById(memberId: Long): Boolean = memberRepository.existsById(memberId)
}

View File

@ -1,37 +0,0 @@
package roomescape.member.controller;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import roomescape.member.dto.MembersResponse;
import roomescape.member.service.MemberService;
import roomescape.system.auth.annotation.Admin;
import roomescape.system.dto.response.RoomEscapeApiResponse;
@RestController
@Tag(name = "2. 회원 API", description = "회원 정보를 관리할 때 사용합니다.")
public class MemberController {
private final MemberService memberService;
public MemberController(MemberService memberService) {
this.memberService = memberService;
}
@Admin
@GetMapping("/members")
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "모든 회원 조회", tags = "관리자 로그인이 필요한 API")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)
})
public RoomEscapeApiResponse<MembersResponse> getAllMembers() {
return RoomEscapeApiResponse.success(memberService.findAllMembers());
}
}

View File

@ -1,98 +0,0 @@
package roomescape.member.domain;
import org.springframework.http.HttpStatus;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import roomescape.system.exception.ErrorType;
import roomescape.system.exception.RoomEscapeException;
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private String password;
@Enumerated(value = EnumType.STRING)
private Role role;
protected Member() {
}
public Member(
String name,
String email,
String password,
Role role
) {
this(null, name, email, password, role);
}
public Member(
Long id,
String name,
String email,
String password,
Role role
) {
this.id = id;
this.name = name;
this.email = email;
this.password = password;
this.role = role;
validateRole();
}
private void validateRole() {
if (role == null) {
throw new RoomEscapeException(ErrorType.REQUEST_DATA_BLANK, String.format("[values: %s]", this),
HttpStatus.BAD_REQUEST);
}
}
public boolean isAdmin() {
return this.role == Role.ADMIN;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public String getEmail() {
return email;
}
public String getPassword() {
return password;
}
public Role getRole() {
return role;
}
@Override
public String toString() {
return "Member{" +
"id=" + id +
", name=" + name +
", email=" + email +
", password=" + password +
", role=" + role +
'}';
}
}

View File

@ -1,6 +0,0 @@
package roomescape.member.domain;
public enum Role {
MEMBER,
ADMIN
}

View File

@ -1,12 +0,0 @@
package roomescape.member.domain.repository;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import roomescape.member.domain.Member;
public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByEmailAndPassword(String email, String password);
}

View File

@ -1,14 +0,0 @@
package roomescape.member.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import roomescape.member.domain.Member;
@Schema(name = "회원 조회 응답", description = "회원 정보 조회 응답시 사용됩니다.")
public record MemberResponse(
@Schema(description = "회원 번호. 회원을 식별할 때 사용합니다.") Long id,
@Schema(description = "회원의 이름") String name
) {
public static MemberResponse fromEntity(Member member) {
return new MemberResponse(member.getId(), member.getName());
}
}

View File

@ -1,11 +0,0 @@
package roomescape.member.dto;
import java.util.List;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(name = "회원 목록 조회 응답", description = "모든 회원의 정보 조회 응답시 사용됩니다.")
public record MembersResponse(
@Schema(description = "모든 회원의 ID 및 이름") List<MemberResponse> members
) {
}

View File

@ -0,0 +1,23 @@
package roomescape.member.infrastructure.persistence
import jakarta.persistence.*
@Entity
class Member(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
var name: String,
var email: String,
var password: String,
@Enumerated(value = EnumType.STRING)
var role: Role
) {
fun isAdmin(): Boolean = role == Role.ADMIN
}
enum class Role {
MEMBER,
ADMIN,
}

View File

@ -0,0 +1,7 @@
package roomescape.member.infrastructure.persistence
import org.springframework.data.jpa.repository.JpaRepository
interface MemberRepository : JpaRepository<Member, Long> {
fun findByEmailAndPassword(email: String, password: String): Member?
}

View File

@ -1,47 +0,0 @@
package roomescape.member.service;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import roomescape.member.domain.Member;
import roomescape.member.domain.repository.MemberRepository;
import roomescape.member.dto.MemberResponse;
import roomescape.member.dto.MembersResponse;
import roomescape.system.exception.ErrorType;
import roomescape.system.exception.RoomEscapeException;
@Service
public class MemberService {
private final MemberRepository memberRepository;
public MemberService(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Transactional(readOnly = true)
public MembersResponse findAllMembers() {
List<MemberResponse> response = memberRepository.findAll().stream()
.map(MemberResponse::fromEntity)
.toList();
return new MembersResponse(response);
}
@Transactional(readOnly = true)
public Member findMemberById(Long memberId) {
return memberRepository.findById(memberId)
.orElseThrow(() -> new RoomEscapeException(ErrorType.MEMBER_NOT_FOUND,
String.format("[memberId: %d]", memberId), HttpStatus.BAD_REQUEST));
}
@Transactional(readOnly = true)
public Member findMemberByEmailAndPassword(String email, String password) {
return memberRepository.findByEmailAndPassword(email, password)
.orElseThrow(() -> new RoomEscapeException(ErrorType.MEMBER_NOT_FOUND,
String.format("[email: %s, password: %s]", email, password), HttpStatus.BAD_REQUEST));
}
}

View File

@ -0,0 +1,21 @@
package roomescape.member.web
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ResponseStatus
import roomescape.system.auth.web.support.Admin
import roomescape.system.dto.response.RoomEscapeApiResponse
@Tag(name = "2. 회원 API", description = "회원 정보를 관리할 때 사용합니다.")
interface MemberAPI {
@Admin
@Operation(summary = "모든 회원 조회", tags = ["관리자 로그인이 필요한 API"])
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
@ResponseStatus(HttpStatus.OK)
fun readAllMembers(): RoomEscapeApiResponse<MembersResponse>
}

View File

@ -0,0 +1,48 @@
package roomescape.member.web
import io.swagger.v3.oas.annotations.media.Schema
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import roomescape.member.business.MemberService
import roomescape.member.infrastructure.persistence.Member
import roomescape.system.dto.response.RoomEscapeApiResponse
@RestController
class MemberController(
private val memberService: MemberService
) : MemberAPI {
@GetMapping("/members")
override fun readAllMembers(): RoomEscapeApiResponse<MembersResponse> {
val result: MembersResponse = memberService.readAllMembers()
return RoomEscapeApiResponse.success(result)
}
}
@Schema(name = "회원 조회 응답", description = "회원 정보 조회 응답시 사용됩니다.")
data class MemberResponse(
@field:Schema(description = "회원의 고유 번호")
val id: Long,
@field:Schema(description = "회원의 이름")
val name: String
) {
companion object {
@JvmStatic
fun fromEntity(member: Member): MemberResponse {
return MemberResponse(member.id!!, member.name)
}
}
}
fun Member.toResponse(): MemberResponse = MemberResponse(
id = id!!,
name = name
)
@Schema(name = "회원 목록 조회 응답", description = "모든 회원의 정보 조회 응답시 사용됩니다.")
data class MembersResponse(
@field:Schema(description = "모든 회원의 ID 및 이름")
val members: List<MemberResponse>
)

View File

@ -37,9 +37,9 @@ import roomescape.reservation.dto.response.ReservationResponse;
import roomescape.reservation.dto.response.ReservationsResponse; import roomescape.reservation.dto.response.ReservationsResponse;
import roomescape.reservation.service.ReservationService; import roomescape.reservation.service.ReservationService;
import roomescape.reservation.service.ReservationWithPaymentService; import roomescape.reservation.service.ReservationWithPaymentService;
import roomescape.system.auth.annotation.Admin; import roomescape.system.auth.web.support.Admin;
import roomescape.system.auth.annotation.LoginRequired; import roomescape.system.auth.web.support.LoginRequired;
import roomescape.system.auth.annotation.MemberId; import roomescape.system.auth.web.support.MemberId;
import roomescape.system.dto.response.ErrorResponse; import roomescape.system.dto.response.ErrorResponse;
import roomescape.system.dto.response.RoomEscapeApiResponse; import roomescape.system.dto.response.RoomEscapeApiResponse;
import roomescape.system.exception.RoomEscapeException; import roomescape.system.exception.RoomEscapeException;

View File

@ -28,8 +28,8 @@ import roomescape.reservation.dto.response.ReservationTimeInfosResponse;
import roomescape.reservation.dto.response.ReservationTimeResponse; import roomescape.reservation.dto.response.ReservationTimeResponse;
import roomescape.reservation.dto.response.ReservationTimesResponse; import roomescape.reservation.dto.response.ReservationTimesResponse;
import roomescape.reservation.service.ReservationTimeService; import roomescape.reservation.service.ReservationTimeService;
import roomescape.system.auth.annotation.Admin; import roomescape.system.auth.web.support.Admin;
import roomescape.system.auth.annotation.LoginRequired; import roomescape.system.auth.web.support.LoginRequired;
import roomescape.system.dto.response.ErrorResponse; import roomescape.system.dto.response.ErrorResponse;
import roomescape.system.dto.response.RoomEscapeApiResponse; import roomescape.system.dto.response.RoomEscapeApiResponse;

View File

@ -15,7 +15,7 @@ import jakarta.persistence.GenerationType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne; import jakarta.persistence.ManyToOne;
import roomescape.member.domain.Member; import roomescape.member.infrastructure.persistence.Member;
import roomescape.system.exception.ErrorType; import roomescape.system.exception.ErrorType;
import roomescape.system.exception.RoomEscapeException; import roomescape.system.exception.RoomEscapeException;
import roomescape.theme.domain.Theme; import roomescape.theme.domain.Theme;

View File

@ -5,7 +5,7 @@ import java.time.LocalDate;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import roomescape.member.dto.MemberResponse; import roomescape.member.web.MemberResponse;
import roomescape.reservation.domain.Reservation; import roomescape.reservation.domain.Reservation;
import roomescape.reservation.domain.ReservationStatus; import roomescape.reservation.domain.ReservationStatus;
import roomescape.theme.dto.ThemeResponse; import roomescape.theme.dto.ThemeResponse;

View File

@ -9,8 +9,8 @@ import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import roomescape.member.domain.Member; import roomescape.member.business.MemberService;
import roomescape.member.service.MemberService; import roomescape.member.infrastructure.persistence.Member;
import roomescape.reservation.domain.Reservation; import roomescape.reservation.domain.Reservation;
import roomescape.reservation.domain.ReservationStatus; import roomescape.reservation.domain.ReservationStatus;
import roomescape.reservation.domain.ReservationTime; import roomescape.reservation.domain.ReservationTime;
@ -147,7 +147,7 @@ public class ReservationService {
ReservationStatus status) { ReservationStatus status) {
ReservationTime time = reservationTimeService.findTimeById(timeId); ReservationTime time = reservationTimeService.findTimeById(timeId);
Theme theme = themeService.findThemeById(themeId); Theme theme = themeService.findThemeById(themeId);
Member member = memberService.findMemberById(memberId); Member member = memberService.findById(memberId);
validateDateAndTime(date, time); validateDateAndTime(date, time);
return new Reservation(date, time, theme, member, status); return new Reservation(date, time, theme, member, status);
@ -213,7 +213,7 @@ public class ReservationService {
} }
private void validateIsMemberAdmin(Long memberId) { private void validateIsMemberAdmin(Long memberId) {
Member member = memberService.findMemberById(memberId); Member member = memberService.findById(memberId);
if (member.isAdmin()) { if (member.isAdmin()) {
return; return;
} }

View File

@ -1,11 +0,0 @@
package roomescape.system.auth.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Admin {
}

View File

@ -1,11 +0,0 @@
package roomescape.system.auth.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginRequired {
}

View File

@ -1,11 +0,0 @@
package roomescape.system.auth.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface MemberId {
}

View File

@ -1,102 +0,0 @@
package roomescape.system.auth.controller;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import roomescape.system.auth.annotation.LoginRequired;
import roomescape.system.auth.annotation.MemberId;
import roomescape.system.auth.dto.LoginCheckResponse;
import roomescape.system.auth.dto.LoginRequest;
import roomescape.system.auth.jwt.dto.TokenDto;
import roomescape.system.auth.service.AuthService;
import roomescape.system.dto.response.ErrorResponse;
import roomescape.system.dto.response.RoomEscapeApiResponse;
@RestController
@Tag(name = "1. 인증 / 인가 API", description = "로그인, 로그아웃 및 로그인 상태를 확인합니다")
public class AuthController {
private final AuthService authService;
public AuthController(AuthService authService) {
this.authService = authService;
}
@PostMapping("/login")
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "로그인")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "로그인 성공시 쿠키에 토큰 정보를 저장합니다."),
@ApiResponse(responseCode = "400", description = "존재하지 않는 회원이거나, 이메일 또는 비밀번호가 잘못 입력되었습니다.",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
public RoomEscapeApiResponse<Void> login(
@Valid @RequestBody LoginRequest loginRequest,
HttpServletResponse response
) {
TokenDto tokenInfo = authService.login(loginRequest);
addCookieToResponse(new Cookie("accessToken", tokenInfo.accessToken()), response);
return RoomEscapeApiResponse.success();
}
@GetMapping("/login/check")
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "로그인 상태 확인")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "로그인 상태이며, 로그인된 회원의 이름을 반환합니다."),
@ApiResponse(responseCode = "400", description = "쿠키에 있는 토큰 정보로 회원을 조회할 수 없습니다.",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
})
public RoomEscapeApiResponse<LoginCheckResponse> checkLogin(@MemberId @Parameter(hidden = true) Long memberId) {
LoginCheckResponse response = authService.checkLogin(memberId);
return RoomEscapeApiResponse.success(response);
}
@LoginRequired
@PostMapping("/logout")
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "로그아웃", tags = "로그인이 필요한 API")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "로그아웃 성공시 쿠키에 저장된 토큰 정보를 삭제합니다.")
})
public RoomEscapeApiResponse<Void> logout(
HttpServletRequest request,
HttpServletResponse response
) {
Cookie cookie = getTokenCookie(request);
cookie.setValue(null);
cookie.setMaxAge(0);
addCookieToResponse(cookie, response);
return RoomEscapeApiResponse.success();
}
private Cookie getTokenCookie(HttpServletRequest request) {
for (Cookie cookie : request.getCookies()) {
if (cookie.getName().equals("accessToken")) {
return cookie;
}
}
return new Cookie("accessToken", null);
}
private void addCookieToResponse(Cookie cookie, HttpServletResponse response) {
cookie.setHttpOnly(true);
response.addCookie(cookie);
}
}

View File

@ -1,9 +0,0 @@
package roomescape.system.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(name = "로그인 체크 응답", description = "로그인 상태 체크 응답시 사용됩니다.")
public record LoginCheckResponse(
@Schema(description = "로그인된 회원의 이름") String name
) {
}

View File

@ -1,17 +0,0 @@
package roomescape.system.auth.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
@Schema(name = "로그인 요청", description = "로그인 요청 시 사용됩니다.")
public record LoginRequest(
@NotBlank(message = "이메일은 null 또는 공백일 수 없습니다.")
@Email(message = "이메일 형식이 일치하지 않습니다. 예시: abc123@gmail.com)")
@Schema(description = "필수 값이며, 이메일 형식으로 입력해야 합니다.", example = "abc123@gmail.com")
String email,
@NotBlank(message = "비밀번호는 null 또는 공백일 수 없습니다.")
@Schema(description = "최소 1글자 이상 입력해야 합니다.")
String password
) {
}

View File

@ -0,0 +1,50 @@
package roomescape.system.auth.infrastructure.jwt
import io.jsonwebtoken.*
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Component
import roomescape.system.exception.ErrorType
import roomescape.system.exception.RoomEscapeException
import java.util.*
@Component
class JwtHandler(
@Value("\${security.jwt.token.secret-key}")
private val secretKey: String,
@Value("\${security.jwt.token.access.expire-length}")
private val accessTokenExpireTime: Long
) {
fun createToken(memberId: Long): String {
val date = Date()
val accessTokenExpiredAt = Date(date.time + accessTokenExpireTime)
return Jwts.builder()
.claim("memberId", memberId)
.setIssuedAt(date)
.setExpiration(accessTokenExpiredAt)
.signWith(SignatureAlgorithm.HS256, secretKey.toByteArray())
.compact()
}
fun getMemberIdFromToken(token: String?): Long {
try {
return Jwts.parser()
.setSigningKey(secretKey.toByteArray())
.parseClaimsJws(token)
.getBody()
.get("memberId", Number::class.java)
.toLong()
} catch (e: Exception) {
when (e) {
is ExpiredJwtException -> throw RoomEscapeException(ErrorType.EXPIRED_TOKEN, HttpStatus.UNAUTHORIZED)
is UnsupportedJwtException -> throw RoomEscapeException(ErrorType.UNSUPPORTED_TOKEN, HttpStatus.UNAUTHORIZED)
is MalformedJwtException -> throw RoomEscapeException(ErrorType.MALFORMED_TOKEN, HttpStatus.UNAUTHORIZED)
is SignatureException -> throw RoomEscapeException(ErrorType.INVALID_SIGNATURE_TOKEN, HttpStatus.UNAUTHORIZED)
is IllegalArgumentException -> throw RoomEscapeException(ErrorType.INVALID_TOKEN, HttpStatus.UNAUTHORIZED)
else -> throw RoomEscapeException(ErrorType.UNEXPECTED_ERROR, HttpStatus.INTERNAL_SERVER_ERROR)
}
}
}
}

View File

@ -1,86 +0,0 @@
package roomescape.system.auth.interceptor;
import java.util.Arrays;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import roomescape.member.domain.Member;
import roomescape.member.service.MemberService;
import roomescape.system.auth.annotation.Admin;
import roomescape.system.auth.jwt.JwtHandler;
import roomescape.system.exception.ErrorType;
import roomescape.system.exception.RoomEscapeException;
@Component
public class AdminInterceptor implements HandlerInterceptor {
private static final String ACCESS_TOKEN_COOKIE_NAME = "accessToken";
private final MemberService memberService;
private final JwtHandler jwtHandler;
public AdminInterceptor(MemberService memberService, JwtHandler jwtHandler) {
this.memberService = memberService;
this.jwtHandler = jwtHandler;
}
@Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler
)
throws Exception {
if (isHandlerIrrelevantWithAdmin(handler)) {
return true;
}
Member member;
try {
Cookie token = getToken(request);
Long memberId = jwtHandler.getMemberIdFromToken(token.getValue());
member = memberService.findMemberById(memberId);
} catch (RoomEscapeException e) {
response.sendRedirect("/login");
throw e;
}
if (member.isAdmin()) {
return true;
} else {
response.sendRedirect("/login");
throw new RoomEscapeException(ErrorType.PERMISSION_DOES_NOT_EXIST,
String.format("[memberId: %d, Role: %s]", member.getId(), member.getRole()), HttpStatus.FORBIDDEN);
}
}
private Cookie getToken(HttpServletRequest request) {
validateCookieHeader(request);
Cookie[] cookies = request.getCookies();
return Arrays.stream(cookies)
.filter(cookie -> cookie.getName().equals(ACCESS_TOKEN_COOKIE_NAME))
.findAny()
.orElseThrow(() -> new RoomEscapeException(ErrorType.INVALID_TOKEN, HttpStatus.UNAUTHORIZED));
}
private void validateCookieHeader(HttpServletRequest request) {
String cookieHeader = request.getHeader("Cookie");
if (cookieHeader == null) {
throw new RoomEscapeException(ErrorType.NOT_EXIST_COOKIE, HttpStatus.UNAUTHORIZED);
}
}
private boolean isHandlerIrrelevantWithAdmin(Object handler) {
if (!(handler instanceof HandlerMethod handlerMethod)) {
return true;
}
Admin adminAnnotation = handlerMethod.getMethodAnnotation(Admin.class);
return adminAnnotation == null;
}
}

View File

@ -1,79 +0,0 @@
package roomescape.system.auth.interceptor;
import java.util.Arrays;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import roomescape.member.domain.Member;
import roomescape.member.service.MemberService;
import roomescape.system.auth.annotation.LoginRequired;
import roomescape.system.auth.jwt.JwtHandler;
import roomescape.system.exception.ErrorType;
import roomescape.system.exception.RoomEscapeException;
@Component
public class LoginInterceptor implements HandlerInterceptor {
private static final String ACCESS_TOKEN_COOKIE_NAME = "accessToken";
private final MemberService memberService;
private final JwtHandler jwtHandler;
public LoginInterceptor(MemberService memberService, JwtHandler jwtHandler) {
this.memberService = memberService;
this.jwtHandler = jwtHandler;
}
@Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler
)
throws Exception {
if (isHandlerIrrelevantWithLoginRequired(handler)) {
return true;
}
Member member;
try {
Cookie token = getToken(request);
Long memberId = jwtHandler.getMemberIdFromToken(token.getValue());
member = memberService.findMemberById(memberId);
return member != null;
} catch (RoomEscapeException e) {
response.sendRedirect("/login");
throw new RoomEscapeException(ErrorType.LOGIN_REQUIRED, HttpStatus.FORBIDDEN);
}
}
private Cookie getToken(HttpServletRequest request) {
validateCookieHeader(request);
Cookie[] cookies = request.getCookies();
return Arrays.stream(cookies)
.filter(cookie -> cookie.getName().equals(ACCESS_TOKEN_COOKIE_NAME))
.findAny()
.orElseThrow(() -> new RoomEscapeException(ErrorType.INVALID_TOKEN, HttpStatus.UNAUTHORIZED));
}
private void validateCookieHeader(HttpServletRequest request) {
String cookieHeader = request.getHeader("Cookie");
if (cookieHeader == null) {
throw new RoomEscapeException(ErrorType.NOT_EXIST_COOKIE, HttpStatus.UNAUTHORIZED);
}
}
private boolean isHandlerIrrelevantWithLoginRequired(Object handler) {
if (!(handler instanceof HandlerMethod handlerMethod)) {
return true;
}
LoginRequired loginRequiredAnnotation = handlerMethod.getMethodAnnotation(LoginRequired.class);
return loginRequiredAnnotation == null;
}
}

View File

@ -1,65 +0,0 @@
package roomescape.system.auth.jwt;
import java.util.Date;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
import roomescape.system.auth.jwt.dto.TokenDto;
import roomescape.system.exception.ErrorType;
import roomescape.system.exception.RoomEscapeException;
@Component
public class JwtHandler {
@Value("${security.jwt.token.secret-key}")
private String secretKey;
@Value("${security.jwt.token.access.expire-length}")
private long accessTokenExpireTime;
public TokenDto createToken(Long memberId) {
Date date = new Date();
Date accessTokenExpiredAt = new Date(date.getTime() + accessTokenExpireTime);
String accessToken = Jwts.builder()
.claim("memberId", memberId)
.setIssuedAt(date)
.setExpiration(accessTokenExpiredAt)
.signWith(SignatureAlgorithm.HS256, secretKey.getBytes())
.compact();
return new TokenDto(accessToken);
}
public Long getMemberIdFromToken(String token) {
validateToken(token);
return Jwts.parser().setSigningKey(secretKey.getBytes()).parseClaimsJws(token)
.getBody()
.get("memberId", Long.class);
}
public void validateToken(String token) {
try {
Jwts.parser().setSigningKey(secretKey.getBytes()).parseClaimsJws(token);
} catch (ExpiredJwtException e) {
throw new RoomEscapeException(ErrorType.EXPIRED_TOKEN, HttpStatus.UNAUTHORIZED);
} catch (UnsupportedJwtException e) {
throw new RoomEscapeException(ErrorType.UNSUPPORTED_TOKEN, HttpStatus.UNAUTHORIZED);
} catch (MalformedJwtException e) {
throw new RoomEscapeException(ErrorType.MALFORMED_TOKEN, HttpStatus.UNAUTHORIZED);
} catch (SignatureException e) {
throw new RoomEscapeException(ErrorType.INVALID_SIGNATURE_TOKEN, HttpStatus.UNAUTHORIZED);
} catch (IllegalArgumentException e) {
throw new RoomEscapeException(ErrorType.ILLEGAL_TOKEN, HttpStatus.UNAUTHORIZED);
}
}
}

View File

@ -1,4 +0,0 @@
package roomescape.system.auth.jwt.dto;
public record TokenDto(String accessToken) {
}

View File

@ -1,53 +0,0 @@
package roomescape.system.auth.resolver;
import java.util.Arrays;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import roomescape.system.auth.annotation.MemberId;
import roomescape.system.auth.jwt.JwtHandler;
import roomescape.system.exception.ErrorType;
import roomescape.system.exception.RoomEscapeException;
@Component
public class MemberIdResolver implements HandlerMethodArgumentResolver {
private static final String ACCESS_TOKEN_COOKIE_NAME = "accessToken";
private final JwtHandler jwtHandler;
public MemberIdResolver(JwtHandler jwtHandler) {
this.jwtHandler = jwtHandler;
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(MemberId.class);
}
@Override
public Object resolveArgument(
MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory
) throws Exception {
Cookie[] cookies = webRequest.getNativeRequest(HttpServletRequest.class).getCookies();
if (cookies == null) {
throw new RoomEscapeException(ErrorType.NOT_EXIST_COOKIE, HttpStatus.UNAUTHORIZED);
}
return Arrays.stream(cookies)
.filter(cookie -> cookie.getName().equals(ACCESS_TOKEN_COOKIE_NAME))
.findAny()
.map(cookie -> jwtHandler.getMemberIdFromToken(cookie.getValue()))
.orElseThrow(() -> new RoomEscapeException(ErrorType.INVALID_TOKEN, HttpStatus.UNAUTHORIZED));
}
}

View File

@ -1,34 +0,0 @@
package roomescape.system.auth.service;
import org.springframework.stereotype.Service;
import roomescape.member.domain.Member;
import roomescape.member.service.MemberService;
import roomescape.system.auth.dto.LoginCheckResponse;
import roomescape.system.auth.dto.LoginRequest;
import roomescape.system.auth.jwt.JwtHandler;
import roomescape.system.auth.jwt.dto.TokenDto;
@Service
public class AuthService {
private final MemberService memberService;
private final JwtHandler jwtHandler;
public AuthService(MemberService memberService, JwtHandler jwtHandler) {
this.memberService = memberService;
this.jwtHandler = jwtHandler;
}
public TokenDto login(LoginRequest request) {
Member member = memberService.findMemberByEmailAndPassword(request.email(), request.password());
return jwtHandler.createToken(member.getId());
}
public LoginCheckResponse checkLogin(Long memberId) {
Member member = memberService.findMemberById(memberId);
return new LoginCheckResponse(member.getName());
}
}

View File

@ -0,0 +1,32 @@
package roomescape.system.auth.service
import org.springframework.stereotype.Service
import roomescape.member.business.MemberService
import roomescape.member.infrastructure.persistence.Member
import roomescape.system.auth.infrastructure.jwt.JwtHandler
import roomescape.system.auth.web.LoginCheckResponse
import roomescape.system.auth.web.LoginRequest
import roomescape.system.auth.web.TokenResponse
@Service
class AuthService(
private val memberService: MemberService,
private val jwtHandler: JwtHandler
) {
fun login(request: LoginRequest): TokenResponse {
val member: Member = memberService.findMemberByEmailAndPassword(
request.email,
request.password
)
val accessToken: String = jwtHandler.createToken(member.id!!)
return TokenResponse(accessToken)
}
fun checkLogin(memberId: Long): LoginCheckResponse {
val member = memberService.findById(memberId)
return LoginCheckResponse(member.name)
}
}

View File

@ -0,0 +1,67 @@
package roomescape.system.auth.web
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import io.swagger.v3.oas.annotations.tags.Tag
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import jakarta.validation.Valid
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.ResponseStatus
import roomescape.system.auth.web.support.LoginRequired
import roomescape.system.auth.web.support.MemberId
import roomescape.system.dto.response.ErrorResponse
import roomescape.system.dto.response.RoomEscapeApiResponse
@Tag(name = "1. 인증 / 인가 API", description = "로그인, 로그아웃 및 로그인 상태를 확인합니다")
interface AuthAPI {
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "로그인")
@ApiResponses(
ApiResponse(
responseCode = "200",
description = "로그인 성공시 쿠키에 토큰 정보를 저장합니다."
),
ApiResponse(
responseCode = "400",
description = "존재하지 않는 회원이거나, 이메일 또는 비밀번호가 잘못 입력되었습니다.",
content = [Content(schema = Schema(implementation = ErrorResponse::class))]
)
)
fun login(
@Valid @RequestBody loginRequest: LoginRequest,
response: HttpServletResponse
): RoomEscapeApiResponse<Void>
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "로그인 상태 확인")
@ApiResponses(
ApiResponse(
responseCode = "200",
description = "로그인 상태이며, 로그인된 회원의 이름을 반환합니다."
),
ApiResponse(
responseCode = "400",
description = "쿠키에 있는 토큰 정보로 회원을 조회할 수 없습니다.",
content = [Content(schema = Schema(implementation = ErrorResponse::class))]
),
ApiResponse(
responseCode = "401",
description = "토큰 정보가 없거나, 만료되었습니다.",
content = [Content(schema = Schema(implementation = ErrorResponse::class))]
)
)
fun checkLogin(@MemberId @Parameter(hidden = true) memberId: Long): RoomEscapeApiResponse<LoginCheckResponse>
@LoginRequired
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "로그아웃", tags = ["로그인이 필요한 API"])
@ApiResponses(ApiResponse(responseCode = "200", description = "로그아웃 성공시 쿠키에 저장된 토큰 정보를 삭제합니다."))
fun logout(request: HttpServletRequest, response: HttpServletResponse): RoomEscapeApiResponse<Void>
}

View File

@ -0,0 +1,54 @@
package roomescape.system.auth.web
import io.swagger.v3.oas.annotations.Parameter
import jakarta.servlet.http.Cookie
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import jakarta.validation.Valid
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
import roomescape.system.auth.service.AuthService
import roomescape.system.auth.web.support.*
import roomescape.system.dto.response.RoomEscapeApiResponse
@RestController
class AuthController(
private val authService: AuthService
) : AuthAPI {
@PostMapping("/login")
override fun login(
@Valid @RequestBody loginRequest: LoginRequest,
response: HttpServletResponse
): RoomEscapeApiResponse<Void> {
val accessToken: TokenResponse = authService.login(loginRequest)
val cookie: Cookie = accessToken.toCookie()
response.addAccessTokenCookie(cookie)
return RoomEscapeApiResponse.success()
}
@GetMapping("/login/check")
override fun checkLogin(
@MemberId @Parameter(hidden = true) memberId: Long
): RoomEscapeApiResponse<LoginCheckResponse> {
val response = authService.checkLogin(memberId)
return RoomEscapeApiResponse.success(response)
}
@PostMapping("/logout")
override fun logout(
request: HttpServletRequest,
response: HttpServletResponse
): RoomEscapeApiResponse<Void> {
val cookie: Cookie = request.accessTokenCookie()
cookie.expire()
response.addAccessTokenCookie(cookie)
return RoomEscapeApiResponse.success()
}
}

View File

@ -0,0 +1,30 @@
package roomescape.system.auth.web
import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.Email
import jakarta.validation.constraints.NotBlank
@JvmRecord
data class TokenResponse(
val accessToken: String
)
@Schema(name = "로그인 체크 응답", description = "로그인 상태 체크 응답시 사용됩니다.")
@JvmRecord
data class LoginCheckResponse(
@field:Schema(description = "로그인된 회원의 이름")
val name: String
)
@Schema(name = "로그인 요청", description = "로그인 요청 시 사용됩니다.")
@JvmRecord
data class LoginRequest(
@Email(message = "이메일 형식이 일치하지 않습니다. 예시: abc123@gmail.com")
@field:Schema(description = "필수 값이며, 이메일 형식으로 입력해야 합니다.", example = "abc123@gmail.com")
val email: String,
@NotBlank(message = "비밀번호는 공백일 수 없습니다.")
@field:Schema(description = "최소 1글자 이상 입력해야 합니다.")
val password: String
)

View File

@ -0,0 +1,13 @@
package roomescape.system.auth.web.support
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class Admin
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class LoginRequired
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
annotation class MemberId

View File

@ -0,0 +1,90 @@
package roomescape.system.auth.web.support
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpStatus
import org.springframework.stereotype.Component
import org.springframework.web.method.HandlerMethod
import org.springframework.web.servlet.HandlerInterceptor
import roomescape.member.business.MemberService
import roomescape.member.infrastructure.persistence.Member
import roomescape.system.auth.infrastructure.jwt.JwtHandler
import roomescape.system.exception.ErrorType
import roomescape.system.exception.RoomEscapeException
private fun Any.isIrrelevantWith(annotationType: Class<out Annotation>): Boolean {
if (this !is HandlerMethod) {
return true
}
return !this.hasMethodAnnotation(annotationType)
}
@Component
class LoginInterceptor(
private val memberService: MemberService,
private val jwtHandler: JwtHandler
) : HandlerInterceptor {
@Throws(Exception::class)
override fun preHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any
): Boolean {
if (handler.isIrrelevantWith(LoginRequired::class.java)) {
return true
}
try {
val token: String? = request.accessTokenCookie().value
val memberId: Long = jwtHandler.getMemberIdFromToken(token)
return memberService.existsById(memberId)
} catch (e: RoomEscapeException) {
response.sendRedirect("/login")
throw RoomEscapeException(ErrorType.LOGIN_REQUIRED, HttpStatus.FORBIDDEN)
}
}
}
@Component
class AdminInterceptor(
private val memberService: MemberService,
private val jwtHandler: JwtHandler
) : HandlerInterceptor {
@Throws(Exception::class)
override fun preHandle(
request: HttpServletRequest,
response: HttpServletResponse,
handler: Any
): Boolean {
if (handler.isIrrelevantWith(Admin::class.java)) {
return true
}
val member: Member?
try {
val token: String? = request.accessTokenCookie().value
val memberId: Long = jwtHandler.getMemberIdFromToken(token)
member = memberService.findById(memberId)
} catch (e: RoomEscapeException) {
response.sendRedirect("/login")
throw e
}
with(member) {
if (this.isAdmin()) {
return true
}
response.sendRedirect("/login")
throw RoomEscapeException(
ErrorType.PERMISSION_DOES_NOT_EXIST,
String.format("[memberId: %d, Role: %s]", this.id, this.role),
HttpStatus.FORBIDDEN
)
}
}
}

View File

@ -0,0 +1,27 @@
package roomescape.system.auth.web.support
import jakarta.servlet.http.Cookie
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import roomescape.system.auth.web.TokenResponse
const val ACCESS_TOKEN_COOKIE_NAME = "accessToken"
fun Cookie.expire(): Unit {
this.value = ""
this.maxAge = 0
}
fun TokenResponse.toCookie(): Cookie = Cookie(ACCESS_TOKEN_COOKIE_NAME, this.accessToken)
.also { it.maxAge = 1800000 }
fun HttpServletRequest.accessTokenCookie(): Cookie = this.cookies
?.firstOrNull { it.name == ACCESS_TOKEN_COOKIE_NAME }
?: Cookie(ACCESS_TOKEN_COOKIE_NAME, "")
fun HttpServletResponse.addAccessTokenCookie(cookie: Cookie) {
cookie.isHttpOnly = true
cookie.secure = true
cookie.path = "/"
this.addCookie(cookie)
}

View File

@ -0,0 +1,33 @@
package roomescape.system.auth.web.support
import jakarta.servlet.http.HttpServletRequest
import org.springframework.core.MethodParameter
import org.springframework.stereotype.Component
import org.springframework.web.bind.support.WebDataBinderFactory
import org.springframework.web.context.request.NativeWebRequest
import org.springframework.web.method.support.HandlerMethodArgumentResolver
import org.springframework.web.method.support.ModelAndViewContainer
import roomescape.system.auth.infrastructure.jwt.JwtHandler
@Component
class MemberIdResolver(
private val jwtHandler: JwtHandler
) : HandlerMethodArgumentResolver {
override fun supportsParameter(parameter: MethodParameter): Boolean {
return parameter.hasParameterAnnotation(MemberId::class.java)
}
@Throws(Exception::class)
override fun resolveArgument(
parameter: MethodParameter,
mavContainer: ModelAndViewContainer?,
webRequest: NativeWebRequest,
binderFactory: WebDataBinderFactory?
): Any {
val request: HttpServletRequest = webRequest.nativeRequest as HttpServletRequest
val token: String = request.accessTokenCookie().value
return jwtHandler.getMemberIdFromToken(token)
}
}

View File

@ -7,9 +7,9 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import roomescape.system.auth.interceptor.AdminInterceptor; import roomescape.system.auth.web.support.AdminInterceptor;
import roomescape.system.auth.interceptor.LoginInterceptor; import roomescape.system.auth.web.support.LoginInterceptor;
import roomescape.system.auth.resolver.MemberIdResolver; import roomescape.system.auth.web.support.MemberIdResolver;
@Configuration @Configuration
public class WebMvcConfig implements WebMvcConfigurer { public class WebMvcConfig implements WebMvcConfigurer {

View File

@ -43,6 +43,7 @@ public enum ErrorType {
// 500 Internal Server Error, // 500 Internal Server Error,
INTERNAL_SERVER_ERROR("서버 내부에서 에러가 발생하였습니다."), INTERNAL_SERVER_ERROR("서버 내부에서 에러가 발생하였습니다."),
UNEXPECTED_ERROR("예상치 못한 에러가 발생하였습니다. 잠시 후 다시 시도해주세요."),
// Payment Error // Payment Error
PAYMENT_ERROR("결제(취소)에 실패했습니다. 결제(취소) 정보를 확인해주세요."), PAYMENT_ERROR("결제(취소)에 실패했습니다. 결제(취소) 정보를 확인해주세요."),

View File

@ -21,8 +21,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import roomescape.system.auth.annotation.Admin; import roomescape.system.auth.web.support.Admin;
import roomescape.system.auth.annotation.LoginRequired; import roomescape.system.auth.web.support.LoginRequired;
import roomescape.system.dto.response.ErrorResponse; import roomescape.system.dto.response.ErrorResponse;
import roomescape.system.dto.response.RoomEscapeApiResponse; import roomescape.system.dto.response.RoomEscapeApiResponse;
import roomescape.theme.dto.ThemeRequest; import roomescape.theme.dto.ThemeRequest;

View File

@ -4,8 +4,8 @@ import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RequestMapping
import roomescape.system.auth.annotation.Admin import roomescape.system.auth.web.support.Admin
import roomescape.system.auth.annotation.LoginRequired import roomescape.system.auth.web.support.LoginRequired
@Controller @Controller
class AuthPageController { class AuthPageController {

View File

@ -1,7 +1,9 @@
package roomescape.common package roomescape.common
import roomescape.member.domain.Member import roomescape.member.infrastructure.persistence.Member
import roomescape.member.domain.Role import roomescape.member.infrastructure.persistence.Role
import roomescape.system.auth.infrastructure.jwt.JwtHandler
import roomescape.system.auth.web.LoginRequest
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong
object MemberFixture { object MemberFixture {
@ -16,5 +18,24 @@ object MemberFixture {
): Member = Member(id, name, "$account@email.com", password, role) ): Member = Member(id, name, "$account@email.com", password, role)
fun admin(): Member = create(account = "admin", role = Role.ADMIN) fun admin(): Member = create(account = "admin", role = Role.ADMIN)
fun adminLoginRequest(): LoginRequest = LoginRequest(
email = admin().email,
password = admin().password
)
fun user(): Member = create(account = "user", role = Role.MEMBER) fun user(): Member = create(account = "user", role = Role.MEMBER)
fun userLoginRequest(): LoginRequest = LoginRequest(
email = user().email,
password = user().password
)
}
object JwtFixture {
const val SECRET_KEY: String = "daijawligagaf@LIJ$@U)9nagnalkkgalijaddljfi"
const val EXPIRATION_TIME: Long = 1000 * 60 * 60
fun create(
secretKey: String = SECRET_KEY,
expirationTime: Long = EXPIRATION_TIME
): JwtHandler = JwtHandler(secretKey, expirationTime)
} }

View File

@ -0,0 +1,93 @@
package roomescape.common
import com.ninjasquad.springmockk.MockkBean
import io.kotest.core.spec.style.BehaviorSpec
import io.mockk.every
import io.restassured.http.ContentType
import io.restassured.module.kotlin.extensions.Given
import io.restassured.module.kotlin.extensions.Then
import io.restassured.module.kotlin.extensions.When
import io.restassured.response.ValidatableResponse
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.web.server.LocalServerPort
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpStatus
import roomescape.member.infrastructure.persistence.Member
import roomescape.member.infrastructure.persistence.MemberRepository
import roomescape.system.auth.infrastructure.jwt.JwtHandler
import roomescape.system.exception.ErrorType
import roomescape.system.exception.RoomEscapeException
const val NOT_LOGGED_IN_USERID: Long = 0;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@NoSqlInitialize
class RoomescapeApiTest(
@LocalServerPort val port: Int? = 9090,
) : BehaviorSpec() {
@MockkBean
lateinit var memberRepository: MemberRepository
@MockkBean
lateinit var jwtHandler: JwtHandler
val admin: Member = MemberFixture.admin()
val user: Member = MemberFixture.user()
fun runGetTest(endpoint: String, token: String? = "token", assert: ValidatableResponse.() -> Unit): ValidatableResponse {
return Given {
port(port!!)
header("Cookie", "accessToken=$token")
} When {
get(endpoint)
} Then assert
}
fun runPostTest(
endpoint: String,
token: String? = "token",
body: Any? = null,
assert: ValidatableResponse.() -> Unit
): ValidatableResponse {
return Given {
port(port!!)
contentType(ContentType.JSON)
body?.let { body(it) }
header("Cookie", "accessToken=$token")
} When {
post(endpoint)
} Then assert
}
fun setUpAdmin() {
every {
jwtHandler.getMemberIdFromToken(any())
} returns admin.id!!
every { memberRepository.existsById(admin.id!!) } returns true
every { memberRepository.findByIdOrNull(admin.id!!) } returns admin
}
fun setUpUser() {
every {
jwtHandler.getMemberIdFromToken(any())
} returns user.id!!
every { memberRepository.existsById(user.id!!) } returns true
every { memberRepository.findByIdOrNull(user.id!!) } returns user
}
fun setUpNotLoggedIn() {
every {
jwtHandler.getMemberIdFromToken(any())
} returns NOT_LOGGED_IN_USERID
every { memberRepository.existsById(NOT_LOGGED_IN_USERID) } throws RoomEscapeException(
ErrorType.LOGIN_REQUIRED,
HttpStatus.FORBIDDEN
)
every { memberRepository.findByIdOrNull(NOT_LOGGED_IN_USERID) } returns null
}
}

View File

@ -1,118 +0,0 @@
package roomescape.global.auth.jwt;
import static roomescape.system.exception.ErrorType.*;
import java.util.Date;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.context.jdbc.Sql;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.restassured.RestAssured;
import roomescape.system.auth.jwt.JwtHandler;
import roomescape.system.exception.ErrorType;
import roomescape.system.exception.RoomEscapeException;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Sql(scripts = "/truncate.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
class JwtHandlerTest {
@Autowired
private JwtHandler jwtHandler;
@Value("${security.jwt.token.secret-key}")
private String secretKey;
@LocalServerPort
private int port;
@BeforeEach
void setUp() {
RestAssured.port = port;
}
@Test
@DisplayName("토큰이 만료되면 401 Unauthorized 를 발생시킨다.")
void jwtExpired() {
//given
Date date = new Date();
Date expiredAt = new Date(date.getTime() - 1);
String accessToken = Jwts.builder()
.claim("memberId", 1L)
.setIssuedAt(date)
.setExpiration(expiredAt)
.signWith(SignatureAlgorithm.HS256, secretKey.getBytes())
.compact();
// when & then
Assertions.assertThatThrownBy(() -> jwtHandler.getMemberIdFromToken(accessToken))
.isInstanceOf(RoomEscapeException.class)
.hasMessage(EXPIRED_TOKEN.getDescription());
}
@Test
@DisplayName("지원하지 않는 토큰이면 401 Unauthorized 를 발생시킨다.")
void jwtMalformed() {
// given
Date date = new Date();
Date expiredAt = new Date(date.getTime() + 100000);
String accessToken = Jwts.builder()
.claim("memberId", 1L)
.setIssuedAt(date)
.setExpiration(expiredAt)
.signWith(SignatureAlgorithm.HS256, secretKey.getBytes())
.compact();
String[] splitAccessToken = accessToken.split("\\.");
String unsupportedAccessToken = splitAccessToken[0] + "." + splitAccessToken[1];
// when & then
Assertions.assertThatThrownBy(() -> jwtHandler.getMemberIdFromToken(unsupportedAccessToken))
.isInstanceOf(RoomEscapeException.class)
.hasMessage(ErrorType.MALFORMED_TOKEN.getDescription());
}
@Test
@DisplayName("토큰의 Signature 가 잘못되었다면 401 Unauthorized 를 발생시킨다.")
void jwtInvalidSignature() {
// given
Date date = new Date();
Date expiredAt = new Date(date.getTime() + 100000);
String invalidSecretKey = secretKey.substring(1);
String accessToken = Jwts.builder()
.claim("memberId", 1L)
.setIssuedAt(date)
.setExpiration(expiredAt)
.signWith(SignatureAlgorithm.HS256, invalidSecretKey.getBytes()) // 기존은 HS256 알고리즘
.compact();
// when & then
Assertions.assertThatThrownBy(() -> jwtHandler.getMemberIdFromToken(accessToken))
.isInstanceOf(RoomEscapeException.class)
.hasMessage(ErrorType.INVALID_SIGNATURE_TOKEN.getDescription());
}
@Test
@DisplayName("토큰이 공백값이라면 401 Unauthorized 를 발생시킨다.")
void jwtIllegal() {
// given
String accessToken = "";
// when & then
Assertions.assertThatThrownBy(() -> jwtHandler.getMemberIdFromToken(accessToken))
.isInstanceOf(RoomEscapeException.class)
.hasMessage(ErrorType.ILLEGAL_TOKEN.getDescription());
}
}

View File

@ -1,72 +0,0 @@
package roomescape.member.controller;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.context.jdbc.Sql;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.http.Header;
import roomescape.member.domain.Member;
import roomescape.member.domain.Role;
import roomescape.member.domain.repository.MemberRepository;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Sql(scripts = "/truncate.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
class MemberControllerTest {
@Autowired
private MemberRepository memberRepository;
@LocalServerPort
private int port;
@BeforeEach
void setUp() {
RestAssured.port = port;
}
@Test
@DisplayName("/members 로 GET 요청을 보내면 회원 정보와 200 OK 를 받는다.")
void getAdminPage() {
// given
String accessTokenCookie = getAdminAccessTokenCookieByLogin("admin@admin.com", "12341234");
memberRepository.save(new Member("이름1", "test@test.com", "password", Role.MEMBER));
memberRepository.save(new Member("이름2", "test@test.com", "password", Role.MEMBER));
memberRepository.save(new Member("이름3", "test@test.com", "password", Role.MEMBER));
memberRepository.save(new Member("이름4", "test@test.com", "password", Role.MEMBER));
// when & then
RestAssured.given().log().all()
.port(port)
.header(new Header("Cookie", accessTokenCookie))
.when().get("/members")
.then().log().all()
.statusCode(200);
}
private String getAdminAccessTokenCookieByLogin(final String email, final String password) {
memberRepository.save(new Member("이름", email, password, Role.ADMIN));
Map<String, String> loginParams = Map.of(
"email", email,
"password", password
);
String accessToken = RestAssured.given().log().all()
.contentType(ContentType.JSON)
.port(port)
.body(loginParams)
.when().post("/login")
.then().log().all().extract().cookie("accessToken");
return "accessToken=" + accessToken;
}
}

View File

@ -0,0 +1,66 @@
package roomescape.member.controller
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import io.kotest.assertions.assertSoftly
import io.kotest.matchers.collections.shouldContainAll
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.restassured.module.kotlin.extensions.Extract
import org.hamcrest.Matchers.containsString
import roomescape.common.MemberFixture
import roomescape.common.RoomescapeApiTest
import roomescape.member.web.MembersResponse
class MemberControllerTest : RoomescapeApiTest() {
init {
given("GET /members 요청을") {
val endpoint = "/members"
every { memberRepository.findAll() } returns listOf(
MemberFixture.create(name = "name1"),
MemberFixture.create(name = "name2"),
MemberFixture.create(name = "name3"),
)
`when`("관리자가 보내면") {
setUpAdmin()
then("성공한다.") {
val result: Any = runGetTest(endpoint) {
statusCode(200)
} Extract {
path("data")
}
val response: MembersResponse = jacksonObjectMapper().convertValue(result, MembersResponse::class.java)
assertSoftly(response.members) {
it.size shouldBe 3
it.map { m -> m.name } shouldContainAll listOf("name1", "name2", "name3")
}
}
}
`when`("관리자가 아니면 로그인 페이지로 이동한다.") {
then("비회원") {
setUpNotLoggedIn()
runGetTest(endpoint) {
statusCode(200)
body(containsString("<title>Login</title>"))
}
}
then("일반 회원") {
setUpUser()
runGetTest(endpoint) {
statusCode(200)
body(containsString("<title>Login</title>"))
}
}
}
}
}
}

View File

@ -1,26 +0,0 @@
package roomescape.member.domain;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import roomescape.system.exception.RoomEscapeException;
class MemberTest {
@Test
@DisplayName("Member 객체를 생성할 때 Role은 반드시 입력되어야 한다.")
void createMemberWithoutRole() {
// given
String name = "name";
String email = "email";
String password = "password";
// when
Role role = null;
// then
Assertions.assertThatThrownBy(() -> new Member(name, email, password, null))
.isInstanceOf(RoomEscapeException.class);
}
}

View File

@ -13,8 +13,8 @@ import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.NullAndEmptySource; import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.junit.jupiter.params.provider.NullSource; import org.junit.jupiter.params.provider.NullSource;
import roomescape.member.domain.Member; import roomescape.member.infrastructure.persistence.Member;
import roomescape.member.domain.Role; import roomescape.member.infrastructure.persistence.Role;
import roomescape.reservation.domain.Reservation; import roomescape.reservation.domain.Reservation;
import roomescape.reservation.domain.ReservationStatus; import roomescape.reservation.domain.ReservationStatus;
import roomescape.reservation.domain.ReservationTime; import roomescape.reservation.domain.ReservationTime;
@ -30,7 +30,7 @@ class PaymentTest {
LocalDate now = LocalDate.now(); LocalDate now = LocalDate.now();
ReservationTime reservationTime = new ReservationTime(LocalTime.now()); ReservationTime reservationTime = new ReservationTime(LocalTime.now());
Theme theme = new Theme("name", "desc", "thumb"); Theme theme = new Theme("name", "desc", "thumb");
Member member = new Member("name", "email", "password", Role.MEMBER); Member member = new Member(null, "name", "email", "password", Role.MEMBER);
reservation = new Reservation(now, reservationTime, theme, member, ReservationStatus.CONFIRMED); reservation = new Reservation(now, reservationTime, theme, member, ReservationStatus.CONFIRMED);
} }

View File

@ -13,9 +13,9 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
import roomescape.member.domain.Member; import roomescape.member.infrastructure.persistence.Member;
import roomescape.member.domain.Role; import roomescape.member.infrastructure.persistence.MemberRepository;
import roomescape.member.domain.repository.MemberRepository; import roomescape.member.infrastructure.persistence.Role;
import roomescape.payment.domain.repository.CanceledPaymentRepository; import roomescape.payment.domain.repository.CanceledPaymentRepository;
import roomescape.payment.dto.request.PaymentCancelRequest; import roomescape.payment.dto.request.PaymentCancelRequest;
import roomescape.payment.dto.response.PaymentResponse; import roomescape.payment.dto.response.PaymentResponse;
@ -54,7 +54,7 @@ class PaymentServiceTest {
LocalDateTime localDateTime = LocalDateTime.now().plusHours(1L); LocalDateTime localDateTime = LocalDateTime.now().plusHours(1L);
LocalDate date = localDateTime.toLocalDate(); LocalDate date = localDateTime.toLocalDate();
ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime())); ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime()));
Member member = memberRepository.save(new Member("member", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "member", "email@email.com", "password", Role.MEMBER));
Theme theme = themeRepository.save(new Theme("name", "desc", "thumbnail")); Theme theme = themeRepository.save(new Theme("name", "desc", "thumbnail"));
Reservation reservation = reservationRepository.save(new Reservation(date, time, theme, member, Reservation reservation = reservationRepository.save(new Reservation(date, time, theme, member,
ReservationStatus.CONFIRMED)); ReservationStatus.CONFIRMED));
@ -75,7 +75,7 @@ class PaymentServiceTest {
LocalDateTime localDateTime = LocalDateTime.now().plusHours(1L); LocalDateTime localDateTime = LocalDateTime.now().plusHours(1L);
LocalDate date = localDateTime.toLocalDate(); LocalDate date = localDateTime.toLocalDate();
ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime())); ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime()));
Member member = memberRepository.save(new Member("member", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "member", "email@email.com", "password", Role.MEMBER));
Theme theme = themeRepository.save(new Theme("name", "desc", "thumbnail")); Theme theme = themeRepository.save(new Theme("name", "desc", "thumbnail"));
Reservation reservation = reservationRepository.save(new Reservation(date, time, theme, member, Reservation reservation = reservationRepository.save(new Reservation(date, time, theme, member,
ReservationStatus.CONFIRMED)); ReservationStatus.CONFIRMED));
@ -111,7 +111,7 @@ class PaymentServiceTest {
LocalDateTime localDateTime = LocalDateTime.now().plusHours(1L); LocalDateTime localDateTime = LocalDateTime.now().plusHours(1L);
LocalDate date = localDateTime.toLocalDate(); LocalDate date = localDateTime.toLocalDate();
ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime())); ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime()));
Member member = memberRepository.save(new Member("member", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "member", "email@email.com", "password", Role.MEMBER));
Theme theme = themeRepository.save(new Theme("name", "desc", "thumbnail")); Theme theme = themeRepository.save(new Theme("name", "desc", "thumbnail"));
Reservation reservation = reservationRepository.save(new Reservation(date, time, theme, member, Reservation reservation = reservationRepository.save(new Reservation(date, time, theme, member,
ReservationStatus.CONFIRMED)); ReservationStatus.CONFIRMED));

View File

@ -2,7 +2,6 @@ package roomescape.reservation.controller;
import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.*;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
@ -33,9 +32,9 @@ import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import io.restassured.http.Header; import io.restassured.http.Header;
import roomescape.member.domain.Member; import roomescape.member.infrastructure.persistence.Member;
import roomescape.member.domain.Role; import roomescape.member.infrastructure.persistence.MemberRepository;
import roomescape.member.domain.repository.MemberRepository; import roomescape.member.infrastructure.persistence.Role;
import roomescape.payment.client.TossPaymentClient; import roomescape.payment.client.TossPaymentClient;
import roomescape.payment.domain.CanceledPayment; import roomescape.payment.domain.CanceledPayment;
import roomescape.payment.domain.Payment; import roomescape.payment.domain.Payment;
@ -124,12 +123,12 @@ public class ReservationControllerTest {
@DisplayName("대기중인 예약을 취소한다.") @DisplayName("대기중인 예약을 취소한다.")
void cancelWaiting() { void cancelWaiting() {
// given // given
Member member = memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
String accessTokenCookie = getAccessTokenCookieByLogin("email@email.com", "password"); String accessTokenCookie = getAccessTokenCookieByLogin("email@email.com", "password");
ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 30))); ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 30)));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL"));
Member member1 = memberRepository.save(new Member("name1", "email1r@email.com", "password", Role.MEMBER)); Member member1 = memberRepository.save(new Member(null, "name1", "email1r@email.com", "password", Role.MEMBER));
// when // when
reservationRepository.save(new Reservation(LocalDate.now().plusDays(1), reservationTime, theme, member1, reservationRepository.save(new Reservation(LocalDate.now().plusDays(1), reservationTime, theme, member1,
@ -151,12 +150,14 @@ public class ReservationControllerTest {
@DisplayName("회원은 자신이 아닌 다른 회원의 예약을 취소할 수 없다.") @DisplayName("회원은 자신이 아닌 다른 회원의 예약을 취소할 수 없다.")
void cantCancelOtherMembersWaiting() { void cantCancelOtherMembersWaiting() {
// given // given
Member confirmedMember = memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)); Member confirmedMember = memberRepository.save(
new Member(null, "name", "email@email.com", "password", Role.MEMBER));
String accessTokenCookie = getAccessTokenCookieByLogin("email@email.com", "password"); String accessTokenCookie = getAccessTokenCookieByLogin("email@email.com", "password");
ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 30))); ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 30)));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL"));
Member waitingMember = memberRepository.save(new Member("name1", "email1r@email.com", "password", Role.MEMBER)); Member waitingMember = memberRepository.save(
new Member(null, "name1", "email1r@email.com", "password", Role.MEMBER));
// when // when
reservationRepository.save(new Reservation(LocalDate.now().plusDays(1), reservationTime, theme, confirmedMember, reservationRepository.save(new Reservation(LocalDate.now().plusDays(1), reservationTime, theme, confirmedMember,
@ -182,7 +183,7 @@ public class ReservationControllerTest {
ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 30))); ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 30)));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
// when // when
reservationRepository.save( reservationRepository.save(
@ -206,7 +207,7 @@ public class ReservationControllerTest {
@DisplayName("예약 취소는 관리자만 할 수 있다.") @DisplayName("예약 취소는 관리자만 할 수 있다.")
void canRemoveMyReservation() { void canRemoveMyReservation() {
// given // given
Member member = memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
String accessTokenCookie = getAccessTokenCookieByLogin(member.getEmail(), member.getPassword()); String accessTokenCookie = getAccessTokenCookieByLogin(member.getEmail(), member.getPassword());
ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 30))); ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 30)));
@ -231,8 +232,10 @@ public class ReservationControllerTest {
ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 30))); ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 30)));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL"));
Member confirmedMember = memberRepository.save(new Member("name1", "email@email.com", "password", Role.MEMBER)); Member confirmedMember = memberRepository.save(
Member waitingMember = memberRepository.save(new Member("name1", "email1@email.com", "password", Role.MEMBER)); new Member(null, "name1", "email@email.com", "password", Role.MEMBER));
Member waitingMember = memberRepository.save(
new Member(null, "name1", "email1@email.com", "password", Role.MEMBER));
reservationRepository.save( reservationRepository.save(
new Reservation(LocalDate.now(), reservationTime, theme, confirmedMember, ReservationStatus.CONFIRMED)); new Reservation(LocalDate.now(), reservationTime, theme, confirmedMember, ReservationStatus.CONFIRMED));
@ -252,12 +255,13 @@ public class ReservationControllerTest {
@DisplayName("본인의 예약이 아니더라도 관리자 권한이 있으면 예약 정보를 삭제할 수 있다.") @DisplayName("본인의 예약이 아니더라도 관리자 권한이 있으면 예약 정보를 삭제할 수 있다.")
void readReservationsSizeAfterPostAndDelete() { void readReservationsSizeAfterPostAndDelete() {
// given // given
Member member = memberRepository.save(new Member("name", "admin@admin.com", "password", Role.ADMIN)); Member member = memberRepository.save(new Member(null, "name", "admin@admin.com", "password", Role.ADMIN));
String accessTokenCookie = getAccessTokenCookieByLogin(member.getEmail(), member.getPassword()); String accessTokenCookie = getAccessTokenCookieByLogin(member.getEmail(), member.getPassword());
ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 30))); ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 30)));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL"));
Member anotherMember = memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)); Member anotherMember = memberRepository.save(
new Member(null, "name", "email@email.com", "password", Role.MEMBER));
Reservation reservation = reservationRepository.save( Reservation reservation = reservationRepository.save(
new Reservation(LocalDate.now(), reservationTime, theme, anotherMember, ReservationStatus.CONFIRMED)); new Reservation(LocalDate.now(), reservationTime, theme, anotherMember, ReservationStatus.CONFIRMED));
@ -341,7 +345,7 @@ public class ReservationControllerTest {
ReservationTime time1 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(18, 30))); ReservationTime time1 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(18, 30)));
ReservationTime time2 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(19, 30))); ReservationTime time2 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(19, 30)));
Member member = memberRepository.save(new Member("name", "email@email.com", "password", Role.ADMIN)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.ADMIN));
String accessToken = getAccessTokenCookieByLogin("email@email.com", "password"); String accessToken = getAccessTokenCookieByLogin("email@email.com", "password");
// when : 예약은 2개, 예약 대기는 1개 조회되어야 한다. // when : 예약은 2개, 예약 대기는 1개 조회되어야 한다.
@ -371,7 +375,7 @@ public class ReservationControllerTest {
// when // when
Reservation saved = reservationRepository.save(new Reservation(date, time, theme, Reservation saved = reservationRepository.save(new Reservation(date, time, theme,
memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)), memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER)),
ReservationStatus.CONFIRMED_PAYMENT_REQUIRED)); ReservationStatus.CONFIRMED_PAYMENT_REQUIRED));
// then // then
@ -391,7 +395,7 @@ public class ReservationControllerTest {
LocalDate date = LocalDate.now().plusDays(1); LocalDate date = LocalDate.now().plusDays(1);
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL"));
ReservationTime time = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 30))); ReservationTime time = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 30)));
Member member = memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
Reservation saved = reservationRepository.save( Reservation saved = reservationRepository.save(
new Reservation(date, time, theme, member, ReservationStatus.CONFIRMED)); new Reservation(date, time, theme, member, ReservationStatus.CONFIRMED));
@ -421,7 +425,7 @@ public class ReservationControllerTest {
LocalDate date = localDateTime.toLocalDate(); LocalDate date = localDateTime.toLocalDate();
ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime())); ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime()));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
String accessToken = getAccessTokenCookieByLogin(member.getEmail(), member.getPassword()); String accessToken = getAccessTokenCookieByLogin(member.getEmail(), member.getPassword());
// when : 이전 날짜의 예약을 추가하여 결제 승인 이후 DB 저장 과정에서 예외를 발생시킨다. // when : 이전 날짜의 예약을 추가하여 결제 승인 이후 DB 저장 과정에서 예외를 발생시킨다.
@ -514,8 +518,8 @@ public class ReservationControllerTest {
LocalDate date = localDateTime.toLocalDate(); LocalDate date = localDateTime.toLocalDate();
ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime())); ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime()));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
Member member1 = memberRepository.save(new Member("name1", "email1@email.com", "password", Role.MEMBER)); Member member1 = memberRepository.save(new Member(null, "name1", "email1@email.com", "password", Role.MEMBER));
String accessToken = getAccessTokenCookieByLogin(member.getEmail(), member.getPassword()); String accessToken = getAccessTokenCookieByLogin(member.getEmail(), member.getPassword());
reservationRepository.save(new Reservation(date, time, theme, member1, ReservationStatus.CONFIRMED)); reservationRepository.save(new Reservation(date, time, theme, member1, ReservationStatus.CONFIRMED));
@ -540,7 +544,7 @@ public class ReservationControllerTest {
LocalDate date = localDateTime.toLocalDate(); LocalDate date = localDateTime.toLocalDate();
ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime())); ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime()));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
String accessToken = getAdminAccessTokenCookieByLogin("admin@email.com", "password"); String accessToken = getAdminAccessTokenCookieByLogin("admin@email.com", "password");
Reservation waiting = reservationRepository.save( Reservation waiting = reservationRepository.save(
@ -585,7 +589,7 @@ public class ReservationControllerTest {
LocalDate date = localDateTime.toLocalDate(); LocalDate date = localDateTime.toLocalDate();
ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime())); ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime()));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
String adminAccessToken = getAdminAccessTokenCookieByLogin("admin@email.com", "password"); String adminAccessToken = getAdminAccessTokenCookieByLogin("admin@email.com", "password");
@ -601,7 +605,7 @@ public class ReservationControllerTest {
} }
private String getAdminAccessTokenCookieByLogin(final String email, final String password) { private String getAdminAccessTokenCookieByLogin(final String email, final String password) {
memberRepository.save(new Member("이름", email, password, Role.ADMIN)); memberRepository.save(new Member(null, "이름", email, password, Role.ADMIN));
Map<String, String> loginParams = Map.of( Map<String, String> loginParams = Map.of(
"email", email, "email", email,

View File

@ -21,9 +21,9 @@ import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import io.restassured.http.Header; import io.restassured.http.Header;
import roomescape.member.domain.Member; import roomescape.member.infrastructure.persistence.Member;
import roomescape.member.domain.Role; import roomescape.member.infrastructure.persistence.MemberRepository;
import roomescape.member.domain.repository.MemberRepository; import roomescape.member.infrastructure.persistence.Role;
import roomescape.reservation.domain.Reservation; import roomescape.reservation.domain.Reservation;
import roomescape.reservation.domain.ReservationStatus; import roomescape.reservation.domain.ReservationStatus;
import roomescape.reservation.domain.ReservationTime; import roomescape.reservation.domain.ReservationTime;
@ -199,7 +199,7 @@ public class ReservationTimeControllerTest {
} }
private String getAdminAccessTokenCookieByLogin(String email, String password) { private String getAdminAccessTokenCookieByLogin(String email, String password) {
memberRepository.save(new Member("이름", email, password, Role.ADMIN)); memberRepository.save(new Member(null, "이름", email, password, Role.ADMIN));
Map<String, String> loginParams = Map.of( Map<String, String> loginParams = Map.of(
"email", email, "email", email,
@ -225,7 +225,7 @@ public class ReservationTimeControllerTest {
ReservationTime reservationTime2 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 30))); ReservationTime reservationTime2 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 30)));
ReservationTime reservationTime3 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(18, 30))); ReservationTime reservationTime3 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(18, 30)));
Theme theme = themeRepository.save(new Theme("테마명1", "설명", "썸네일URL")); Theme theme = themeRepository.save(new Theme("테마명1", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
reservationRepository.save( reservationRepository.save(
new Reservation(today.plusDays(1), reservationTime1, theme, member, ReservationStatus.CONFIRMED)); new Reservation(today.plusDays(1), reservationTime1, theme, member, ReservationStatus.CONFIRMED));

View File

@ -10,8 +10,8 @@ import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import roomescape.member.domain.Member; import roomescape.member.infrastructure.persistence.Member;
import roomescape.member.domain.Role; import roomescape.member.infrastructure.persistence.Role;
import roomescape.system.exception.RoomEscapeException; import roomescape.system.exception.RoomEscapeException;
import roomescape.theme.domain.Theme; import roomescape.theme.domain.Theme;
@ -34,17 +34,17 @@ public class ReservationTest {
Arguments.of(null, Arguments.of(null,
new ReservationTime(LocalTime.now().plusHours(1)), new ReservationTime(LocalTime.now().plusHours(1)),
new Theme("테마명", "설명", "썸네일URI"), new Theme("테마명", "설명", "썸네일URI"),
new Member("name", "email@email.com", "password", Role.MEMBER)), new Member(null, "name", "email@email.com", "password", Role.MEMBER)),
Arguments.of( Arguments.of(
LocalDate.now(), LocalDate.now(),
null, null,
new Theme("테마명", "설명", "썸네일URI"), new Theme("테마명", "설명", "썸네일URI"),
new Member("name", "email@email.com", "password", Role.MEMBER)), new Member(null, "name", "email@email.com", "password", Role.MEMBER)),
Arguments.of( Arguments.of(
LocalDate.now(), LocalDate.now(),
new ReservationTime(LocalTime.now().plusHours(1)), new ReservationTime(LocalTime.now().plusHours(1)),
null, null,
new Member("name", "email@email.com", "password", Role.MEMBER)), new Member(null, "name", "email@email.com", "password", Role.MEMBER)),
Arguments.of( Arguments.of(
LocalDate.now(), LocalDate.now(),
new ReservationTime(LocalTime.now().plusHours(1)), new ReservationTime(LocalTime.now().plusHours(1)),

View File

@ -13,9 +13,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.domain.Specification;
import roomescape.member.domain.Member; import roomescape.member.infrastructure.persistence.Member;
import roomescape.member.domain.Role; import roomescape.member.infrastructure.persistence.MemberRepository;
import roomescape.member.domain.repository.MemberRepository; import roomescape.member.infrastructure.persistence.Role;
import roomescape.reservation.domain.Reservation; import roomescape.reservation.domain.Reservation;
import roomescape.reservation.domain.ReservationStatus; import roomescape.reservation.domain.ReservationStatus;
import roomescape.reservation.domain.ReservationTime; import roomescape.reservation.domain.ReservationTime;
@ -50,7 +50,7 @@ class ReservationSearchSpecificationTest {
@BeforeEach @BeforeEach
void setUp() { void setUp() {
LocalDateTime dateTime = LocalDateTime.now(); LocalDateTime dateTime = LocalDateTime.now();
Member member = memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
ReservationTime time = timeRepository.save(new ReservationTime(dateTime.toLocalTime())); ReservationTime time = timeRepository.save(new ReservationTime(dateTime.toLocalTime()));
Theme theme = themeRepository.save(new Theme("name", "description", "thumbnail")); Theme theme = themeRepository.save(new Theme("name", "description", "thumbnail"));

View File

@ -14,10 +14,10 @@ import org.springframework.context.annotation.Import;
import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
import roomescape.member.domain.Member; import roomescape.member.business.MemberService;
import roomescape.member.domain.Role; import roomescape.member.infrastructure.persistence.Member;
import roomescape.member.domain.repository.MemberRepository; import roomescape.member.infrastructure.persistence.MemberRepository;
import roomescape.member.service.MemberService; import roomescape.member.infrastructure.persistence.Role;
import roomescape.reservation.domain.Reservation; import roomescape.reservation.domain.Reservation;
import roomescape.reservation.domain.ReservationStatus; import roomescape.reservation.domain.ReservationStatus;
import roomescape.reservation.domain.ReservationTime; import roomescape.reservation.domain.ReservationTime;
@ -53,8 +53,8 @@ class ReservationServiceTest {
// given // given
ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(12, 30))); ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(12, 30)));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL"));
Member member1 = memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)); Member member1 = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
Member member2 = memberRepository.save(new Member("name2", "email2@email.com", "password", Role.MEMBER)); Member member2 = memberRepository.save(new Member(null, "name2", "email2@email.com", "password", Role.MEMBER));
LocalDate date = LocalDate.now().plusDays(1L); LocalDate date = LocalDate.now().plusDays(1L);
// when // when
@ -75,7 +75,7 @@ class ReservationServiceTest {
// given // given
ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(12, 30))); ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(12, 30)));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
LocalDate date = LocalDate.now().plusDays(1L); LocalDate date = LocalDate.now().plusDays(1L);
// when // when
@ -95,8 +95,8 @@ class ReservationServiceTest {
// given // given
ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(12, 30))); ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(12, 30)));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
Member member1 = memberRepository.save(new Member("name1", "email1@email.com", "password", Role.MEMBER)); Member member1 = memberRepository.save(new Member(null, "name1", "email1@email.com", "password", Role.MEMBER));
LocalDate date = LocalDate.now().plusDays(1L); LocalDate date = LocalDate.now().plusDays(1L);
// when // when
@ -119,7 +119,7 @@ class ReservationServiceTest {
// given // given
ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(12, 30))); ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(12, 30)));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
LocalDate beforeDate = LocalDate.now().minusDays(1L); LocalDate beforeDate = LocalDate.now().minusDays(1L);
// when & then // when & then
@ -136,7 +136,7 @@ class ReservationServiceTest {
LocalDateTime beforeTime = LocalDateTime.now().minusHours(1L).withNano(0); LocalDateTime beforeTime = LocalDateTime.now().minusHours(1L).withNano(0);
ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(beforeTime.toLocalTime())); ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(beforeTime.toLocalTime()));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
// when & then // when & then
assertThatThrownBy(() -> reservationService.addReservation( assertThatThrownBy(() -> reservationService.addReservation(
@ -180,9 +180,9 @@ class ReservationServiceTest {
// given // given
ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(12, 30))); ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(12, 30)));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL"));
Member admin = memberRepository.save(new Member("admin", "admin@email.com", "password", Role.ADMIN)); Member admin = memberRepository.save(new Member(null, "admin", "admin@email.com", "password", Role.ADMIN));
Member member = memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
Member member1 = memberRepository.save(new Member("name1", "email1@email.com", "password", Role.MEMBER)); Member member1 = memberRepository.save(new Member(null, "name1", "email1@email.com", "password", Role.MEMBER));
reservationService.addReservation( reservationService.addReservation(
new ReservationRequest(LocalDate.now().plusDays(1L), reservationTime.getId(), theme.getId(), new ReservationRequest(LocalDate.now().plusDays(1L), reservationTime.getId(), theme.getId(),
@ -203,8 +203,8 @@ class ReservationServiceTest {
// given // given
ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(12, 30))); ReservationTime reservationTime = reservationTimeRepository.save(new ReservationTime(LocalTime.of(12, 30)));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL"));
Member admin = memberRepository.save(new Member("admin", "admin@email.com", "password", Role.ADMIN)); Member admin = memberRepository.save(new Member(null, "admin", "admin@email.com", "password", Role.ADMIN));
Member member = memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
// when // when
ReservationResponse waiting = reservationService.addWaiting( ReservationResponse waiting = reservationService.addWaiting(

View File

@ -13,9 +13,9 @@ import org.springframework.context.annotation.Import;
import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
import roomescape.member.domain.Member; import roomescape.member.infrastructure.persistence.Member;
import roomescape.member.domain.Role; import roomescape.member.infrastructure.persistence.MemberRepository;
import roomescape.member.domain.repository.MemberRepository; import roomescape.member.infrastructure.persistence.Role;
import roomescape.reservation.domain.Reservation; import roomescape.reservation.domain.Reservation;
import roomescape.reservation.domain.ReservationStatus; import roomescape.reservation.domain.ReservationStatus;
import roomescape.reservation.domain.ReservationTime; import roomescape.reservation.domain.ReservationTime;
@ -75,7 +75,7 @@ class ReservationTimeServiceTest {
ReservationTime reservationTime = reservationTimeRepository.save( ReservationTime reservationTime = reservationTimeRepository.save(
new ReservationTime(localDateTime.toLocalTime())); new ReservationTime(localDateTime.toLocalTime()));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member("name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
// when // when
reservationRepository.save(new Reservation(localDateTime.toLocalDate(), reservationTime, theme, member, reservationRepository.save(new Reservation(localDateTime.toLocalDate(), reservationTime, theme, member,

View File

@ -13,9 +13,9 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
import roomescape.member.domain.Member; import roomescape.member.infrastructure.persistence.Member;
import roomescape.member.domain.Role; import roomescape.member.infrastructure.persistence.MemberRepository;
import roomescape.member.domain.repository.MemberRepository; import roomescape.member.infrastructure.persistence.Role;
import roomescape.payment.domain.repository.CanceledPaymentRepository; import roomescape.payment.domain.repository.CanceledPaymentRepository;
import roomescape.payment.domain.repository.PaymentRepository; import roomescape.payment.domain.repository.PaymentRepository;
import roomescape.payment.dto.request.PaymentCancelRequest; import roomescape.payment.dto.request.PaymentCancelRequest;
@ -57,7 +57,7 @@ class ReservationWithPaymentServiceTest {
LocalDateTime localDateTime = LocalDateTime.now().plusDays(1L).withNano(0); LocalDateTime localDateTime = LocalDateTime.now().plusDays(1L).withNano(0);
LocalDate date = localDateTime.toLocalDate(); LocalDate date = localDateTime.toLocalDate();
ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime())); ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime()));
Member member = memberRepository.save(new Member("member", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "member", "email@email.com", "password", Role.MEMBER));
Theme theme = themeRepository.save(new Theme("name", "desc", "thumbnail")); Theme theme = themeRepository.save(new Theme("name", "desc", "thumbnail"));
ReservationRequest reservationRequest = new ReservationRequest(date, time.getId(), theme.getId(), "payment-key", ReservationRequest reservationRequest = new ReservationRequest(date, time.getId(), theme.getId(), "payment-key",
"order-id", 10000L, "NORMAL"); "order-id", 10000L, "NORMAL");
@ -92,7 +92,7 @@ class ReservationWithPaymentServiceTest {
LocalDateTime localDateTime = LocalDateTime.now().plusDays(1L).withNano(0); LocalDateTime localDateTime = LocalDateTime.now().plusDays(1L).withNano(0);
LocalDate date = localDateTime.toLocalDate(); LocalDate date = localDateTime.toLocalDate();
ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime())); ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime()));
Member member = memberRepository.save(new Member("member", "admin@email.com", "password", Role.ADMIN)); Member member = memberRepository.save(new Member(null, "member", "admin@email.com", "password", Role.ADMIN));
Theme theme = themeRepository.save(new Theme("name", "desc", "thumbnail")); Theme theme = themeRepository.save(new Theme("name", "desc", "thumbnail"));
ReservationRequest reservationRequest = new ReservationRequest(date, time.getId(), theme.getId(), "payment-key", ReservationRequest reservationRequest = new ReservationRequest(date, time.getId(), theme.getId(), "payment-key",
"order-id", 10000L, "NORMAL"); "order-id", 10000L, "NORMAL");
@ -119,7 +119,7 @@ class ReservationWithPaymentServiceTest {
LocalDateTime localDateTime = LocalDateTime.now().plusHours(1L); LocalDateTime localDateTime = LocalDateTime.now().plusHours(1L);
LocalDate date = localDateTime.toLocalDate(); LocalDate date = localDateTime.toLocalDate();
ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime())); ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime()));
Member member = memberRepository.save(new Member("member", "admin@email.com", "password", Role.ADMIN)); Member member = memberRepository.save(new Member(null, "member", "admin@email.com", "password", Role.ADMIN));
Theme theme = themeRepository.save(new Theme("name", "desc", "thumbnail")); Theme theme = themeRepository.save(new Theme("name", "desc", "thumbnail"));
Reservation saved = reservationRepository.save( Reservation saved = reservationRepository.save(
@ -140,7 +140,7 @@ class ReservationWithPaymentServiceTest {
LocalDateTime localDateTime = LocalDateTime.now().plusDays(1L).withNano(0); LocalDateTime localDateTime = LocalDateTime.now().plusDays(1L).withNano(0);
LocalDate date = localDateTime.toLocalDate(); LocalDate date = localDateTime.toLocalDate();
ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime())); ReservationTime time = reservationTimeRepository.save(new ReservationTime(localDateTime.toLocalTime()));
Member member = memberRepository.save(new Member("member", "admin@email.com", "password", Role.ADMIN)); Member member = memberRepository.save(new Member(null, "member", "admin@email.com", "password", Role.ADMIN));
Theme theme = themeRepository.save(new Theme("name", "desc", "thumbnail")); Theme theme = themeRepository.save(new Theme("name", "desc", "thumbnail"));
ReservationRequest reservationRequest = new ReservationRequest(date, time.getId(), theme.getId(), "payment-key", ReservationRequest reservationRequest = new ReservationRequest(date, time.getId(), theme.getId(), "payment-key",
"order-id", 10000L, "NORMAL"); "order-id", 10000L, "NORMAL");

View File

@ -0,0 +1,83 @@
package roomescape.system.auth.business
import io.kotest.assertions.assertSoftly
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockk
import org.springframework.data.repository.findByIdOrNull
import roomescape.common.JwtFixture
import roomescape.common.MemberFixture
import roomescape.member.business.MemberService
import roomescape.member.infrastructure.persistence.Member
import roomescape.member.infrastructure.persistence.MemberRepository
import roomescape.system.auth.infrastructure.jwt.JwtHandler
import roomescape.system.auth.service.AuthService
import roomescape.system.exception.ErrorType
import roomescape.system.exception.RoomEscapeException
class AuthServiceTest : BehaviorSpec({
val memberRepository: MemberRepository = mockk()
val memberService: MemberService = MemberService(memberRepository)
val jwtHandler: JwtHandler = JwtFixture.create()
val authService = AuthService(memberService, jwtHandler)
val user: Member = MemberFixture.user()
Given("로그인 요청을 받으면") {
When("이메일과 비밀번호로 회원을 찾고") {
val request = MemberFixture.userLoginRequest()
Then("회원이 있다면 JWT 토큰을 생성한 뒤 반환한다.") {
every {
memberRepository.findByEmailAndPassword(request.email, request.password)
} returns user
val accessToken: String = authService.login(request).accessToken
accessToken.isNotBlank() shouldBe true
jwtHandler.getMemberIdFromToken(accessToken) shouldBe user.id
}
Then("회원이 없다면 예외를 던진다.") {
every {
memberRepository.findByEmailAndPassword(request.email, request.password)
} returns null
val exception = shouldThrow<RoomEscapeException> {
authService.login(request)
}
exception.errorType shouldBe ErrorType.MEMBER_NOT_FOUND
}
}
}
Given("로그인 확인 요청을 받으면") {
When("회원 ID로 회원을 찾고") {
val userId: Long = user.id!!
Then("회원이 있다면 회원의 이름을 반환한다.") {
every { memberRepository.findByIdOrNull(userId) } returns user
val response = authService.checkLogin(userId)
assertSoftly(response) {
this.name shouldBe user.name
}
}
Then("회원이 없다면 예외를 던진다.") {
every { memberRepository.findByIdOrNull(userId) } returns null
val exception = shouldThrow<RoomEscapeException> {
authService.checkLogin(userId)
}
exception.errorType shouldBe ErrorType.MEMBER_NOT_FOUND
}
}
}
})

View File

@ -1,118 +0,0 @@
package roomescape.system.auth.controller;
import static org.assertj.core.api.Assertions.*;
import static org.hamcrest.Matchers.*;
import java.util.Map;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.test.context.jdbc.Sql;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import roomescape.member.domain.Member;
import roomescape.member.domain.Role;
import roomescape.member.domain.repository.MemberRepository;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Sql(scripts = "/truncate.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
class AuthControllerTest {
@Autowired
private MemberRepository memberRepository;
@LocalServerPort
private int port;
@Test
@DisplayName("로그인에 성공하면 JWT accessToken을 응답 받는다.")
void getJwtAccessTokenWhenlogin() {
// given
String email = "test@email.com";
String password = "12341234";
memberRepository.save(new Member("이름", email, password, Role.MEMBER));
Map<String, String> loginParams = Map.of(
"email", email,
"password", password
);
// when
Map<String, String> cookies = RestAssured.given().log().all()
.contentType(ContentType.JSON)
.port(port)
.body(loginParams)
.when().post("/login")
.then().log().all().extract().cookies();
// then
assertThat(cookies.get("accessToken")).isNotNull();
}
@Test
@DisplayName("로그인 검증 시, 회원의 name을 응답 받는다.")
void checkLogin() {
// given
String email = "test@test.com";
String password = "12341234";
String accessTokenCookie = getAccessTokenCookieByLogin(email, password);
// when & then
RestAssured.given().log().all()
.contentType(ContentType.JSON)
.port(port)
.header("cookie", accessTokenCookie)
.when().get("/login/check")
.then()
.body("data.name", is("이름"));
}
@Test
@DisplayName("로그인 없이 검증요청을 보내면 401 Unauthorized 를 응답한다.")
void checkLoginFailByNotAuthorized() {
RestAssured.given().log().all()
.contentType(ContentType.JSON)
.port(port)
.when().get("/login/check")
.then()
.statusCode(401);
}
@Test
@DisplayName("로그아웃 요청 시, accessToken 쿠키가 삭제된다.")
void checkLogout() {
// given
String accessToken = getAccessTokenCookieByLogin("email@email.com", "password");
// when & then
RestAssured.given().log().all()
.port(port)
.header("cookie", accessToken)
.when().post("/logout")
.then()
.statusCode(200)
.cookie("accessToken", "");
}
private String getAccessTokenCookieByLogin(final String email, final String password) {
memberRepository.save(new Member("이름", email, password, Role.ADMIN));
Map<String, String> loginParams = Map.of(
"email", email,
"password", password
);
String accessToken = RestAssured.given().log().all()
.contentType(ContentType.JSON)
.port(port)
.body(loginParams)
.when().post("/login")
.then().log().all().extract().cookie("accessToken");
return "accessToken=" + accessToken;
}
}

View File

@ -0,0 +1,61 @@
package roomescape.system.auth.infrastructure.jwt
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import roomescape.common.JwtFixture
import roomescape.system.exception.ErrorType
import roomescape.system.exception.RoomEscapeException
import java.util.*
import kotlin.random.Random
class JwtHandlerTest : FunSpec({
context("JWT 토큰 조회") {
val memberId = Random.nextLong()
val jwtHandler: JwtHandler = JwtFixture.create()
test("토큰에서 멤버 ID를 올바르게 추출한다.") {
val token = jwtHandler.createToken(memberId)
val extractedMemberId = jwtHandler.getMemberIdFromToken(token)
extractedMemberId shouldBe memberId
}
test("만료된 토큰이면 예외를 던진다.") {
// given
val expirationTime = 0L
val shortExpirationTimeJwtHandler: JwtHandler = JwtFixture.create(expirationTime = expirationTime)
val token = shortExpirationTimeJwtHandler.createToken(memberId)
Thread.sleep(expirationTime) // 만료 시간 이후로 대기
// when & then
shouldThrow<RoomEscapeException> {
shortExpirationTimeJwtHandler.getMemberIdFromToken(token)
}.errorType shouldBe ErrorType.EXPIRED_TOKEN
}
test("토큰이 빈 값이면 예외를 던진다.") {
shouldThrow<RoomEscapeException> {
jwtHandler.getMemberIdFromToken("")
}.errorType shouldBe ErrorType.INVALID_TOKEN
}
test("시크릿 키가 잘못된 경우 예외를 던진다.") {
val now: Date = Date()
val invalidSignatureToken: String = Jwts.builder()
.claim("memberId", memberId)
.setIssuedAt(now)
.setExpiration(Date(now.time + JwtFixture.EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS256, JwtFixture.SECRET_KEY.substring(1).toByteArray())
.compact()
shouldThrow<RoomEscapeException> {
jwtHandler.getMemberIdFromToken(invalidSignatureToken)
}.errorType shouldBe ErrorType.INVALID_SIGNATURE_TOKEN
}
}
})

View File

@ -1,65 +0,0 @@
package roomescape.system.auth.service;
import static org.assertj.core.api.Assertions.*;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import roomescape.member.domain.Member;
import roomescape.member.domain.Role;
import roomescape.member.domain.repository.MemberRepository;
import roomescape.member.service.MemberService;
import roomescape.system.auth.dto.LoginRequest;
import roomescape.system.auth.jwt.JwtHandler;
import roomescape.system.auth.jwt.dto.TokenDto;
import roomescape.system.exception.RoomEscapeException;
@SpringBootTest
@Import({AuthService.class, JwtHandler.class, MemberService.class})
class AuthServiceTest {
@Autowired
private AuthService authService;
@Autowired
private MemberRepository memberRepository;
@Test
@DisplayName("로그인 성공시 JWT accessToken 을 반환한다.")
void loginSuccess() {
// given
Member member = memberRepository.save(new Member("이름", "test@test.com", "12341234", Role.MEMBER));
// when
TokenDto response = authService.login(new LoginRequest(member.getEmail(), member.getPassword()));
// then
assertThat(response.accessToken()).isNotNull();
}
@Test
@DisplayName("존재하지 않는 회원 email 또는 password로 로그인하면 예외가 발생한다.")
void loginFailByNotExistMemberInfo() {
// given
String notExistEmail = "invalid@test.com";
String notExistPassword = "invalid1234";
// when & then
Assertions.assertThatThrownBy(() -> authService.login(new LoginRequest(notExistEmail, notExistPassword)))
.isInstanceOf(RoomEscapeException.class);
}
@Test
@DisplayName("존재하지 않는 회원의 memberId로 로그인 여부를 체크하면 예외가 발생한다.")
void checkLoginFailByNotExistMemberInfo() {
// given
Long notExistMemberId = (long)(memberRepository.findAll().size() + 1);
// when & then
Assertions.assertThatThrownBy(() -> authService.checkLogin(notExistMemberId))
.isInstanceOf(RoomEscapeException.class);
}
}

View File

@ -0,0 +1,139 @@
package roomescape.system.auth.web
import io.mockk.every
import org.hamcrest.Matchers.containsString
import org.hamcrest.Matchers.`is`
import org.springframework.data.repository.findByIdOrNull
import roomescape.common.MemberFixture
import roomescape.common.RoomescapeApiTest
import roomescape.system.exception.ErrorType
class AuthControllerTest : RoomescapeApiTest() {
val userRequest: LoginRequest = MemberFixture.userLoginRequest()
init {
Given("로그인 요청을 보낼 때") {
val endpoint: String = "/login"
When("로그인에 성공하면") {
val expectedToken: String = "expectedToken"
every {
memberRepository.findByEmailAndPassword(userRequest.email, userRequest.password)
} returns user
every {
jwtHandler.createToken(user.id!!)
} returns expectedToken
Then("토큰을 쿠키에 담아 응답한다") {
runPostTest(endpoint, body = MemberFixture.userLoginRequest()) {
statusCode(200)
cookie("accessToken", expectedToken)
header("Set-Cookie", containsString("Max-Age=1800000"))
header("Set-Cookie", containsString("HttpOnly"))
header("Set-Cookie", containsString("Secure"))
}
}
}
When("회원을 찾지 못하면") {
every {
memberRepository.findByEmailAndPassword(userRequest.email, userRequest.password)
} returns null
Then("400 에러를 응답한다") {
runPostTest(endpoint, body = userRequest) {
log().all()
statusCode(400)
body("errorType", `is`(ErrorType.MEMBER_NOT_FOUND.name))
}
}
}
When("잘못된 요청을 보내면 400 에러를 응답한다.") {
Then("이메일 형식이 잘못된 경우") {
val invalidRequest: LoginRequest = userRequest.copy(email = "invalid")
runPostTest(endpoint, body = invalidRequest) {
log().all()
statusCode(400)
body("message", `is`("이메일 형식이 일치하지 않습니다. 예시: abc123@gmail.com"))
}
}
Then("비밀번호가 공백인 경우") {
val invalidRequest = userRequest.copy(password = " ")
runPostTest(endpoint, body = invalidRequest) {
log().all()
statusCode(400)
body("message", `is`("비밀번호는 공백일 수 없습니다."))
}
}
}
}
Given("로그인 상태를 확인할 때") {
val endpoint: String = "/login/check"
When("로그인된 회원의 ID로 요청하면") {
every { jwtHandler.getMemberIdFromToken(any()) } returns user.id!!
every { memberRepository.findByIdOrNull(user.id!!) } returns user
Then("회원의 이름을 응답한다") {
runGetTest(endpoint) {
statusCode(200)
body("data.name", `is`(user.name))
}
}
}
When("토큰은 있지만 회원을 찾을 수 없으면") {
val invalidMemberId: Long = -1L
every { jwtHandler.getMemberIdFromToken(any()) } returns invalidMemberId
every { memberRepository.findByIdOrNull(invalidMemberId) } returns null
Then("400 에러를 응답한다.") {
runGetTest(endpoint) {
statusCode(400)
body("errorType", `is`(ErrorType.MEMBER_NOT_FOUND.name))
}
}
}
}
Given("로그아웃 요청을 보낼 때") {
val endpoint: String = "/logout"
When("로그인 상태가 아니라면") {
setUpNotLoggedIn()
Then("로그인 페이지로 이동한다.") {
runPostTest(endpoint) {
log().all()
statusCode(302)
header("Location", containsString("/login"))
}
}
}
When("로그인 상태라면") {
setUpUser()
every { memberRepository.findByIdOrNull(user.id!!) } returns user
Then("토큰의 존재 여부와 무관하게 토큰을 만료시킨다.") {
runPostTest(endpoint) {
log().all()
statusCode(200)
cookie("accessToken", "")
header("Set-Cookie", containsString("Max-Age=0"))
}
}
}
}
}
}

View File

@ -18,9 +18,9 @@ import org.springframework.test.context.jdbc.Sql.ExecutionPhase;
import io.restassured.RestAssured; import io.restassured.RestAssured;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import io.restassured.http.Header; import io.restassured.http.Header;
import roomescape.member.domain.Member; import roomescape.member.infrastructure.persistence.Member;
import roomescape.member.domain.Role; import roomescape.member.infrastructure.persistence.MemberRepository;
import roomescape.member.domain.repository.MemberRepository; import roomescape.member.infrastructure.persistence.Role;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Sql(scripts = "/truncate.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = "/truncate.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
@ -165,7 +165,7 @@ class ThemeControllerTest {
} }
private String getAdminAccessTokenCookieByLogin(final String email, final String password) { private String getAdminAccessTokenCookieByLogin(final String email, final String password) {
memberRepository.save(new Member("이름", email, password, Role.ADMIN)); memberRepository.save(new Member(null, "이름", email, password, Role.ADMIN));
Map<String, String> loginParams = Map.of( Map<String, String> loginParams = Map.of(
"email", email, "email", email,

View File

@ -13,10 +13,10 @@ import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql;
import roomescape.member.domain.Member; import roomescape.member.business.MemberService;
import roomescape.member.domain.Role; import roomescape.member.infrastructure.persistence.Member;
import roomescape.member.domain.repository.MemberRepository; import roomescape.member.infrastructure.persistence.MemberRepository;
import roomescape.member.service.MemberService; import roomescape.member.infrastructure.persistence.Role;
import roomescape.reservation.dto.request.ReservationRequest; import roomescape.reservation.dto.request.ReservationRequest;
import roomescape.reservation.dto.request.ReservationTimeRequest; import roomescape.reservation.dto.request.ReservationTimeRequest;
import roomescape.reservation.dto.response.ReservationTimeResponse; import roomescape.reservation.dto.response.ReservationTimeResponse;
@ -152,7 +152,7 @@ class ThemeServiceTest {
ReservationTimeResponse time = reservationTimeService.addTime( ReservationTimeResponse time = reservationTimeService.addTime(
new ReservationTimeRequest(dateTime.toLocalTime())); new ReservationTimeRequest(dateTime.toLocalTime()));
Theme theme = themeRepository.save(new Theme("name", "description", "thumbnail")); Theme theme = themeRepository.save(new Theme("name", "description", "thumbnail"));
Member member = memberRepository.save(new Member("member", "password", "name", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "member", "password", "name", Role.MEMBER));
reservationService.addReservation( reservationService.addReservation(
new ReservationRequest(dateTime.toLocalDate(), time.id(), theme.getId(), "paymentKey", "orderId", 1000L, new ReservationRequest(dateTime.toLocalDate(), time.id(), theme.getId(), "paymentKey", "orderId", 1000L,
"NORMAL"), member.getId()); "NORMAL"), member.getId());

View File

@ -1,66 +1,34 @@
package roomescape.view.controller package roomescape.view.controller
import com.ninjasquad.springmockk.MockkBean
import io.kotest.core.spec.style.BehaviorSpec
import io.mockk.every
import io.restassured.module.kotlin.extensions.Given
import io.restassured.module.kotlin.extensions.Then
import io.restassured.module.kotlin.extensions.When
import io.restassured.response.ValidatableResponse
import org.hamcrest.Matchers.containsString import org.hamcrest.Matchers.containsString
import org.springframework.boot.test.context.SpringBootTest import roomescape.common.RoomescapeApiTest
import org.springframework.boot.test.web.server.LocalServerPort
import org.springframework.http.HttpStatus
import roomescape.common.MemberFixture
import roomescape.common.NoSqlInitialize
import roomescape.member.domain.Member
import roomescape.member.service.MemberService
import roomescape.system.auth.jwt.JwtHandler
import roomescape.system.exception.ErrorType
import roomescape.system.exception.RoomEscapeException
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@NoSqlInitialize
class PageControllerTest(
@LocalServerPort val port: Int,
) : BehaviorSpec() {
@MockkBean
private lateinit var jwtHandler: JwtHandler
@MockkBean
private lateinit var memberService: MemberService
private val admin: Member = MemberFixture.admin()
private val user: Member = MemberFixture.user()
class PageControllerTest() : RoomescapeApiTest() {
init { init {
listOf("/", "/login").forEach { listOf("/", "/login").forEach {
given("GET $it 요청은") { given("GET $it 요청은") {
`when`("로그인 및 권한 여부와 관계없이 성공한다.") { `when`("로그인 및 권한 여부와 관계없이 성공한다.") {
then("비회원") { then("비회원") {
runTest(it) { setUpNotLoggedIn()
runGetTest(it) {
statusCode(200) statusCode(200)
} }
} }
then("회원") { then("회원") {
every { setUpUser()
jwtHandler.getMemberIdFromToken(any())
} returns user.id
runTest(it) { runGetTest(it) {
statusCode(200) statusCode(200)
} }
} }
then("관리자") { then("관리자") {
every { setUpAdmin()
jwtHandler.getMemberIdFromToken(any())
} returns admin.id
runTest(it) { runGetTest(it) {
statusCode(200) statusCode(200)
} }
} }
@ -71,24 +39,20 @@ class PageControllerTest(
listOf("/admin", "/admin/reservation", "/admin/time", "/admin/theme", "/admin/waiting").forEach { listOf("/admin", "/admin/reservation", "/admin/time", "/admin/theme", "/admin/waiting").forEach {
given("GET $it 요청을") { given("GET $it 요청을") {
`when`("관리자가 보내면") { `when`("관리자가 보내면") {
every { setUpAdmin()
jwtHandler.getMemberIdFromToken(any())
} returns admin.id
then("성공한다.") { then("성공한다.") {
runTest(it) { runGetTest(it) {
statusCode(200) statusCode(200)
} }
} }
} }
`when`("회원이 보내면") { `when`("회원이 보내면") {
every { setUpUser()
jwtHandler.getMemberIdFromToken(any())
} returns user.id
then("로그인 페이지로 이동한다.") { then("로그인 페이지로 이동한다.") {
runTest(it) { runGetTest(it) {
statusCode(200) statusCode(200)
body(containsString("<title>Login</title>")) body(containsString("<title>Login</title>"))
} }
@ -101,20 +65,16 @@ class PageControllerTest(
given("GET $it 요청을") { given("GET $it 요청을") {
`when`("로그인 된 회원이 보내면 성공한다.") { `when`("로그인 된 회원이 보내면 성공한다.") {
then("회원") { then("회원") {
every { setUpUser()
jwtHandler.getMemberIdFromToken(any())
} returns user.id
runTest(it) { runGetTest(it) {
statusCode(200) statusCode(200)
} }
} }
then("관리자") { then("관리자") {
every { setUpAdmin()
jwtHandler.getMemberIdFromToken(any())
} returns admin.id
runTest(it) { runGetTest(it) {
statusCode(200) statusCode(200)
} }
} }
@ -122,11 +82,9 @@ class PageControllerTest(
`when`("로그인 없이 보내면") { `when`("로그인 없이 보내면") {
then("로그인 페이지로 이동한다.") { then("로그인 페이지로 이동한다.") {
every { setUpNotLoggedIn()
jwtHandler.getMemberIdFromToken(any())
} returns null
runTest(it) { runGetTest(it) {
statusCode(200) statusCode(200)
body(containsString("<title>Login</title>")) body(containsString("<title>Login</title>"))
} }
@ -135,27 +93,4 @@ class PageControllerTest(
} }
} }
} }
fun runTest(endpoint: String, assert: ValidatableResponse.() -> Unit) {
setUpMocks()
Given {
port(port)
header("Cookie", "accessToken=token")
} When {
get(endpoint)
} Then assert
}
private fun setUpMocks() {
every { memberService.findMemberById(admin.id) } returns admin
every { memberService.findMemberById(user.id) } returns user
every { memberService.findMemberById(null) } throws RoomEscapeException(
ErrorType.MEMBER_NOT_FOUND,
String.format("[memberId: %d]", null),
HttpStatus.BAD_REQUEST
)
}
} }