[#13] Theme 도메인 코드 코틀린 마이그레이션 #15

Merged
pricelees merged 17 commits from refactor/#13 into main 2025-07-17 16:37:27 +00:00
35 changed files with 1095 additions and 764 deletions

View File

@ -69,9 +69,9 @@ class AdminInterceptor(
val token: String? = request.accessTokenCookie().value val token: String? = request.accessTokenCookie().value
val memberId: Long = jwtHandler.getMemberIdFromToken(token) val memberId: Long = jwtHandler.getMemberIdFromToken(token)
member = memberService.findById(memberId) member = memberService.findById(memberId)
} catch (e: RoomescapeException) { } catch (_: RoomescapeException) {
response.sendRedirect("/login") response.sendRedirect("/login")
throw e throw RoomescapeException(ErrorType.LOGIN_REQUIRED, HttpStatus.FORBIDDEN)
} }
with(member) { with(member) {

View File

@ -8,11 +8,4 @@ import roomescape.common.exception.ErrorType
data class RoomescapeErrorResponse( data class RoomescapeErrorResponse(
val errorType: ErrorType, val errorType: ErrorType,
val message: String val message: String
) { )
companion object {
@JvmStatic
fun of(errorType: ErrorType, message: String? = null): RoomescapeErrorResponse =
RoomescapeErrorResponse(errorType, message ?: errorType.description)
}
}

View File

@ -18,7 +18,7 @@ import jakarta.persistence.ManyToOne;
import roomescape.common.exception.ErrorType; import roomescape.common.exception.ErrorType;
import roomescape.common.exception.RoomescapeException; import roomescape.common.exception.RoomescapeException;
import roomescape.member.infrastructure.persistence.Member; import roomescape.member.infrastructure.persistence.Member;
import roomescape.theme.domain.Theme; import roomescape.theme.infrastructure.persistence.ThemeEntity;
@Entity @Entity
public class Reservation { public class Reservation {
@ -35,7 +35,7 @@ public class Reservation {
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "theme_id", nullable = false) @JoinColumn(name = "theme_id", nullable = false)
private Theme theme; private ThemeEntity theme;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false) @JoinColumn(name = "member_id", nullable = false)
@ -50,7 +50,7 @@ public class Reservation {
public Reservation( public Reservation(
LocalDate date, LocalDate date,
ReservationTime reservationTime, ReservationTime reservationTime,
Theme theme, ThemeEntity theme,
Member member, Member member,
ReservationStatus status ReservationStatus status
) { ) {
@ -61,7 +61,7 @@ public class Reservation {
Long id, Long id,
LocalDate date, LocalDate date,
ReservationTime reservationTime, ReservationTime reservationTime,
Theme theme, ThemeEntity theme,
Member member, Member member,
ReservationStatus status ReservationStatus status
) { ) {
@ -74,7 +74,7 @@ public class Reservation {
this.reservationStatus = status; this.reservationStatus = status;
} }
private void validateIsNull(LocalDate date, ReservationTime reservationTime, Theme theme, Member member, private void validateIsNull(LocalDate date, ReservationTime reservationTime, ThemeEntity theme, Member member,
ReservationStatus reservationStatus) { ReservationStatus reservationStatus) {
if (date == null || reservationTime == null || theme == null || member == null || reservationStatus == null) { if (date == null || reservationTime == null || theme == null || member == null || reservationStatus == null) {
throw new RoomescapeException(ErrorType.REQUEST_DATA_BLANK, String.format("[values: %s]", this), throw new RoomescapeException(ErrorType.REQUEST_DATA_BLANK, String.format("[values: %s]", this),
@ -98,7 +98,7 @@ public class Reservation {
return reservationTime; return reservationTime;
} }
public Theme getTheme() { public ThemeEntity getTheme() {
return theme; return theme;
} }

View File

@ -8,7 +8,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import roomescape.member.web.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.web.ThemeResponse;
@Schema(name = "예약 정보", description = "예약 저장 및 조회 응답에 사용됩니다.") @Schema(name = "예약 정보", description = "예약 저장 및 조회 응답에 사용됩니다.")
public record ReservationResponse( public record ReservationResponse(

View File

@ -24,8 +24,8 @@ import roomescape.reservation.dto.request.WaitingRequest;
import roomescape.reservation.dto.response.MyReservationsResponse; import roomescape.reservation.dto.response.MyReservationsResponse;
import roomescape.reservation.dto.response.ReservationResponse; import roomescape.reservation.dto.response.ReservationResponse;
import roomescape.reservation.dto.response.ReservationsResponse; import roomescape.reservation.dto.response.ReservationsResponse;
import roomescape.theme.domain.Theme; import roomescape.theme.infrastructure.persistence.ThemeEntity;
import roomescape.theme.service.ThemeService; import roomescape.theme.business.ThemeService;
@Service @Service
@Transactional @Transactional
@ -146,7 +146,7 @@ public class ReservationService {
private Reservation getReservationForSave(Long timeId, Long themeId, LocalDate date, Long memberId, private Reservation getReservationForSave(Long timeId, Long themeId, LocalDate date, Long memberId,
ReservationStatus status) { ReservationStatus status) {
ReservationTime time = reservationTimeService.findTimeById(timeId); ReservationTime time = reservationTimeService.findTimeById(timeId);
Theme theme = themeService.findThemeById(themeId); ThemeEntity theme = themeService.findThemeById(themeId);
Member member = memberService.findById(memberId); Member member = memberService.findById(memberId);
validateDateAndTime(date, time); validateDateAndTime(date, time);

View File

@ -0,0 +1,74 @@
package roomescape.theme.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.common.exception.ErrorType
import roomescape.common.exception.RoomescapeException
import roomescape.theme.infrastructure.persistence.ThemeEntity
import roomescape.theme.infrastructure.persistence.ThemeRepository
import roomescape.theme.web.ThemeRequest
import roomescape.theme.web.ThemeResponse
import roomescape.theme.web.ThemesResponse
import roomescape.theme.web.toResponse
import java.time.LocalDate
@Service
class ThemeService(
private val themeRepository: ThemeRepository
) {
@Transactional(readOnly = true)
fun findThemeById(id: Long): ThemeEntity = themeRepository.findByIdOrNull(id)
?: throw RoomescapeException(
ErrorType.THEME_NOT_FOUND,
"[themeId: $id]",
HttpStatus.BAD_REQUEST
)
@Transactional(readOnly = true)
fun findAllThemes(): ThemesResponse = themeRepository.findAll()
.toResponse()
@Transactional(readOnly = true)
fun getMostReservedThemesByCount(count: Int): ThemesResponse {
val today = LocalDate.now()
val startDate = today.minusDays(7)
val endDate = today.minusDays(1)
return themeRepository.findTopNThemeBetweenStartDateAndEndDate(startDate, endDate, count)
.toResponse()
}
@Transactional
fun save(request: ThemeRequest): ThemeResponse {
if (themeRepository.existsByName(request.name)) {
throw RoomescapeException(
ErrorType.THEME_DUPLICATED,
"[name: ${request.name}]",
HttpStatus.CONFLICT
)
}
return ThemeEntity(
name = request.name,
description = request.description,
thumbnail = request.thumbnail
).also {
themeRepository.save(it)
}.toResponse()
}
@Transactional
fun deleteById(id: Long) {
if (themeRepository.isReservedTheme(id)) {
throw RoomescapeException(
ErrorType.THEME_IS_USED_CONFLICT,
"[themeId: %d]",
HttpStatus.CONFLICT
)
}
themeRepository.deleteById(id)
}
}

View File

@ -1,101 +0,0 @@
package roomescape.theme.controller;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
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.HttpServletResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import roomescape.auth.web.support.Admin;
import roomescape.auth.web.support.LoginRequired;
import roomescape.common.dto.response.RoomescapeApiResponse;
import roomescape.common.dto.response.RoomescapeErrorResponse;
import roomescape.theme.dto.ThemeRequest;
import roomescape.theme.dto.ThemeResponse;
import roomescape.theme.dto.ThemesResponse;
import roomescape.theme.service.ThemeService;
@RestController
@Tag(name = "5. 테마 API", description = "테마를 조회 / 추가 / 삭제할 때 사용합니다.")
public class ThemeController {
private final ThemeService themeService;
public ThemeController(ThemeService themeService) {
this.themeService = themeService;
}
@LoginRequired
@GetMapping("/themes")
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "모든 테마 조회", description = "모든 테마를 조회합니다.", tags = "로그인이 필요한 API")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)
})
public RoomescapeApiResponse<ThemesResponse> getAllThemes() {
return RoomescapeApiResponse.success(themeService.findAllThemes());
}
@GetMapping("/themes/most-reserved-last-week")
@ResponseStatus(HttpStatus.OK)
@Operation(summary = "가장 많이 예약된 테마 조회")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true)
})
public RoomescapeApiResponse<ThemesResponse> getMostReservedThemes(
@RequestParam(defaultValue = "10") @Parameter(description = "최대로 조회할 테마 갯수") int count
) {
return RoomescapeApiResponse.success(themeService.getMostReservedThemesByCount(count));
}
@Admin
@PostMapping("/themes")
@ResponseStatus(HttpStatus.CREATED)
@Operation(summary = "테마 추가", tags = "관리자 로그인이 필요한 API")
@ApiResponses({
@ApiResponse(responseCode = "201", description = "성공", useReturnTypeSchema = true),
@ApiResponse(responseCode = "409", description = "같은 이름의 테마를 추가할 수 없습니다.",
content = @Content(schema = @Schema(implementation = RoomescapeErrorResponse.class)))
})
public RoomescapeApiResponse<ThemeResponse> saveTheme(
@Valid @RequestBody ThemeRequest request,
HttpServletResponse response
) {
ThemeResponse themeResponse = themeService.addTheme(request);
response.setHeader(HttpHeaders.LOCATION, "/themes/" + themeResponse.id());
return RoomescapeApiResponse.success(themeResponse);
}
@Admin
@DeleteMapping("/themes/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
@Operation(summary = "테마 삭제", tags = "관리자 로그인이 필요한 API")
@ApiResponses({
@ApiResponse(responseCode = "204", description = "성공", useReturnTypeSchema = true),
@ApiResponse(responseCode = "409", description = "예약된 테마는 삭제할 수 없습니다.",
content = @Content(schema = @Schema(implementation = RoomescapeErrorResponse.class)))
})
public RoomescapeApiResponse<Void> removeTheme(
@NotNull(message = "themeId는 null일 수 없습니다.") @PathVariable Long id
) {
themeService.removeThemeById(id);
return RoomescapeApiResponse.success();
}
}

View File

@ -0,0 +1,51 @@
package roomescape.theme.docs
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
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.validation.Valid
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestParam
import roomescape.auth.web.support.Admin
import roomescape.auth.web.support.LoginRequired
import roomescape.common.dto.response.CommonApiResponse
import roomescape.theme.web.ThemeRequest
import roomescape.theme.web.ThemeResponse
import roomescape.theme.web.ThemesResponse
@Tag(name = "5. 테마 API", description = "테마를 조회 / 추가 / 삭제할 때 사용합니다.")
interface ThemeAPI {
@LoginRequired
@Operation(summary = "모든 테마 조회", description = "모든 테마를 조회합니다.", tags = ["로그인이 필요한 API"])
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
fun getAllThemes(): ResponseEntity<CommonApiResponse<ThemesResponse>>
@Operation(summary = "가장 많이 예약된 테마 조회")
@ApiResponses(ApiResponse(responseCode = "200", description = "성공", useReturnTypeSchema = true))
fun getMostReservedThemes(
@RequestParam(defaultValue = "10") @Parameter(description = "최대로 조회할 테마 갯수") count: Int
): ResponseEntity<CommonApiResponse<ThemesResponse>>
@Admin
@Operation(summary = "테마 추가", tags = ["관리자 로그인이 필요한 API"])
@ApiResponses(
ApiResponse(responseCode = "201", description = "성공", useReturnTypeSchema = true),
)
fun saveTheme(
@Valid @RequestBody request: ThemeRequest,
): ResponseEntity<CommonApiResponse<ThemeResponse>>
@Admin
@Operation(summary = "테마 삭제", tags = ["관리자 로그인이 필요한 API"])
@ApiResponses(
ApiResponse(responseCode = "204", description = "성공", useReturnTypeSchema = true),
)
fun removeTheme(
@PathVariable id: Long
): ResponseEntity<CommonApiResponse<Unit>>
}

View File

@ -1,65 +0,0 @@
package roomescape.theme.domain;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class Theme {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
private String thumbnail;
protected Theme() {
}
public Theme(String name, String description, String thumbnail) {
this(null, name, description, thumbnail);
}
public Theme(
Long id,
String name,
String description,
String thumbnail
) {
this.id = id;
this.name = name;
this.description = description;
this.thumbnail = thumbnail;
}
public Long getId() {
return id;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public String getThumbnail() {
return thumbnail;
}
@Override
public String toString() {
return "Theme{" +
"id=" + id +
", name=" + name +
", description=" + description +
", thumbnail=" + thumbnail +
'}';
}
}

View File

@ -1,34 +0,0 @@
package roomescape.theme.domain.repository;
import java.time.LocalDate;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import roomescape.theme.domain.Theme;
public interface ThemeRepository extends JpaRepository<Theme, Long> {
@Query(value = """
SELECT t
FROM Theme t
RIGHT JOIN Reservation r ON t.id = r.theme.id
WHERE r.date BETWEEN :startDate AND :endDate
GROUP BY r.theme.id
ORDER BY COUNT(r.theme.id) DESC, t.id ASC
LIMIT :limit
""")
List<Theme> findTopNThemeBetweenStartDateAndEndDate(LocalDate startDate, LocalDate endDate, int limit);
boolean existsByName(String name);
@Query(value = """
SELECT EXISTS(
SELECT 1
FROM Reservation r
WHERE r.theme.id = :id
)
""")
boolean isReservedTheme(Long id);
}

View File

@ -1,21 +0,0 @@
package roomescape.theme.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
@Schema(name = "테마 저장 요청", description = "테마 정보를 저장할 때 사용합니다.")
public record ThemeRequest(
@NotBlank(message = "테마의 이름은 null 또는 공백일 수 없습니다.")
@Size(min = 1, max = 20, message = "테마의 이름은 1~20글자 사이여야 합니다.")
@Schema(description = "필수 값이며, 최대 20글자까지 입력 가능합니다.")
String name,
@NotBlank(message = "테마의 설명은 null 또는 공백일 수 없습니다.")
@Size(min = 1, max = 100, message = "테마의 설명은 1~100글자 사이여야 합니다.")
@Schema(description = "필수 값이며, 최대 100글자까지 입력 가능합니다.")
String description,
@NotBlank(message = "테마의 쌈네일은 null 또는 공백일 수 없습니다.")
@Schema(description = "필수 값이며, 썸네일 이미지 URL 을 입력해주세요.")
String thumbnail
) {
}

View File

@ -1,21 +0,0 @@
package roomescape.theme.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import roomescape.theme.domain.Theme;
@Schema(name = "테마 정보", description = "테마 추가 및 조회 응답에 사용됩니다.")
public record ThemeResponse(
@Schema(description = "테마 번호. 테마를 식별할 때 사용합니다.")
Long id,
@Schema(description = "테마 이름. 중복을 허용하지 않습니다.")
String name,
@Schema(description = "테마 설명")
String description,
@Schema(description = "테마 썸네일 이미지 URL")
String thumbnail
) {
public static ThemeResponse from(Theme theme) {
return new ThemeResponse(theme.getId(), theme.getName(), theme.getDescription(), theme.getThumbnail());
}
}

View File

@ -1,11 +0,0 @@
package roomescape.theme.dto;
import java.util.List;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(name = "테마 목록 조회 응답", description = "모든 테마 목록 조회 응답시 사용됩니다.")
public record ThemesResponse(
@Schema(description = "모든 테마 목록") List<ThemeResponse> themes
) {
}

View File

@ -0,0 +1,15 @@
package roomescape.theme.infrastructure.persistence
import jakarta.persistence.*
@Entity
@Table(name = "theme")
class ThemeEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
var name: String,
var description: String,
var thumbnail: String
)

View File

@ -0,0 +1,32 @@
package roomescape.theme.infrastructure.persistence
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import java.time.LocalDate
interface ThemeRepository : JpaRepository<ThemeEntity, Long> {
@Query(value = """
SELECT t
FROM ThemeEntity t
RIGHT JOIN Reservation r ON t.id = r.theme.id
WHERE r.date BETWEEN :startDate AND :endDate
GROUP BY r.theme.id
ORDER BY COUNT(r.theme.id) DESC, t.id ASC
LIMIT :limit
"""
)
fun findTopNThemeBetweenStartDateAndEndDate(startDate: LocalDate, endDate: LocalDate, limit: Int): List<ThemeEntity>
fun existsByName(name: String): Boolean
@Query(value = """
SELECT EXISTS(
SELECT 1
FROM Reservation r
WHERE r.theme.id = :id
)
""")
fun isReservedTheme(id: Long): Boolean
}

View File

@ -1,82 +0,0 @@
package roomescape.theme.service;
import java.time.LocalDate;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import roomescape.common.exception.ErrorType;
import roomescape.common.exception.RoomescapeException;
import roomescape.reservation.domain.repository.ReservationRepository;
import roomescape.theme.domain.Theme;
import roomescape.theme.domain.repository.ThemeRepository;
import roomescape.theme.dto.ThemeRequest;
import roomescape.theme.dto.ThemeResponse;
import roomescape.theme.dto.ThemesResponse;
@Service
@Transactional
public class ThemeService {
private final ThemeRepository themeRepository;
public ThemeService(ThemeRepository themeRepository) {
this.themeRepository = themeRepository;
}
@Transactional(readOnly = true)
public Theme findThemeById(Long id) {
return themeRepository.findById(id)
.orElseThrow(() -> new RoomescapeException(ErrorType.THEME_NOT_FOUND,
String.format("[themeId: %d]", id), HttpStatus.BAD_REQUEST));
}
@Transactional(readOnly = true)
public ThemesResponse findAllThemes() {
List<ThemeResponse> response = themeRepository.findAll()
.stream()
.map(ThemeResponse::from)
.toList();
return new ThemesResponse(response);
}
@Transactional(readOnly = true)
public ThemesResponse getMostReservedThemesByCount(int count) {
LocalDate today = LocalDate.now();
LocalDate startDate = today.minusDays(7);
LocalDate endDate = today.minusDays(1);
List<ThemeResponse> response = themeRepository.findTopNThemeBetweenStartDateAndEndDate(startDate, endDate,
count)
.stream()
.map(ThemeResponse::from)
.toList();
return new ThemesResponse(response);
}
public ThemeResponse addTheme(ThemeRequest request) {
validateIsSameThemeNameExist(request.name());
Theme theme = themeRepository.save(new Theme(request.name(), request.description(), request.thumbnail()));
return ThemeResponse.from(theme);
}
private void validateIsSameThemeNameExist(String name) {
if (themeRepository.existsByName(name)) {
throw new RoomescapeException(ErrorType.THEME_DUPLICATED,
String.format("[name: %s]", name), HttpStatus.CONFLICT);
}
}
public void removeThemeById(Long id) {
if (themeRepository.isReservedTheme(id)) {
throw new RoomescapeException(ErrorType.THEME_IS_USED_CONFLICT,
String.format("[themeId: %d]", id), HttpStatus.CONFLICT);
}
themeRepository.deleteById(id);
}
}

View File

@ -0,0 +1,51 @@
package roomescape.theme.web
import io.swagger.v3.oas.annotations.Parameter
import jakarta.validation.Valid
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import roomescape.common.dto.response.CommonApiResponse
import roomescape.theme.business.ThemeService
import roomescape.theme.docs.ThemeAPI
import java.net.URI
@RestController
class ThemeController(
private val themeService: ThemeService
) : ThemeAPI {
@GetMapping("/themes")
override fun getAllThemes(): ResponseEntity<CommonApiResponse<ThemesResponse>> {
val response: ThemesResponse = themeService.findAllThemes()
return ResponseEntity.ok(CommonApiResponse(response))
}
@GetMapping("/themes/most-reserved-last-week")
override fun getMostReservedThemes(
@RequestParam(defaultValue = "10") @Parameter(description = "최대로 조회할 테마 갯수") count: Int
): ResponseEntity<CommonApiResponse<ThemesResponse>> {
val response: ThemesResponse = themeService.getMostReservedThemesByCount(count)
return ResponseEntity.ok(CommonApiResponse(response))
}
@PostMapping("/themes")
override fun saveTheme(
@RequestBody @Valid request: ThemeRequest
): ResponseEntity<CommonApiResponse<ThemeResponse>> {
val themeResponse: ThemeResponse = themeService.save(request)
return ResponseEntity.created(URI.create("/themes/${themeResponse.id}"))
.body(CommonApiResponse(themeResponse))
}
@DeleteMapping("/themes/{id}")
override fun removeTheme(
@PathVariable id: Long
): ResponseEntity<CommonApiResponse<Unit>> {
themeService.deleteById(id)
return ResponseEntity.noContent().build()
}
}

View File

@ -0,0 +1,68 @@
package roomescape.theme.web
import io.swagger.v3.oas.annotations.media.Schema
import jakarta.validation.constraints.NotBlank
import jakarta.validation.constraints.Size
import org.hibernate.validator.constraints.URL
import roomescape.theme.infrastructure.persistence.ThemeEntity
@Schema(name = "테마 저장 요청", description = "테마 정보를 저장할 때 사용합니다.")
@JvmRecord
data class ThemeRequest(
@field:Schema(description = "필수 값이며, 최대 20글자까지 입력 가능합니다.")
@NotBlank
@Size(max = 20, message = "테마의 이름은 1~20글자 사이여야 합니다.")
val name: String,
@field:Schema(description = "필수 값이며, 최대 100글자까지 입력 가능합니다.")
@NotBlank
@Size(max = 100, message = "테마의 설명은 1~100글자 사이여야 합니다.")
val description: String,
@field:Schema(description = "필수 값이며, 썸네일 이미지 URL 을 입력해주세요.")
@NotBlank
@URL
val thumbnail: String
)
@Schema(name = "테마 정보", description = "테마 추가 및 조회 응답에 사용됩니다.")
@JvmRecord
data class ThemeResponse(
@field:Schema(description = "테마 번호. 테마를 식별할 때 사용합니다.")
val id: Long,
@field:Schema(description = "테마 이름. 중복을 허용하지 않습니다.")
val name: String,
@field:Schema(description = "테마 설명")
val description: String,
@field:Schema(description = "테마 썸네일 이미지 URL")
val thumbnail: String
) {
companion object {
@JvmStatic
fun from(themeEntity: ThemeEntity): ThemeResponse {
return ThemeResponse(themeEntity.id!!, themeEntity.name, themeEntity.description, themeEntity.thumbnail)
}
}
}
fun ThemeEntity.toResponse(): ThemeResponse = ThemeResponse(
id = this.id!!,
name = this.name,
description = this.description,
thumbnail = this.thumbnail
)
@Schema(name = "테마 목록 조회 응답", description = "모든 테마 목록 조회 응답시 사용됩니다.")
@JvmRecord
data class ThemesResponse(
@field:Schema(description = "모든 테마 목록")
val themes: List<ThemeResponse>
)
fun List<ThemeEntity>.toResponse(): ThemesResponse = ThemesResponse(
themes = this.map { it.toResponse()}
)

View File

@ -27,8 +27,8 @@ import roomescape.reservation.domain.ReservationStatus;
import roomescape.reservation.domain.ReservationTime; import roomescape.reservation.domain.ReservationTime;
import roomescape.reservation.domain.repository.ReservationRepository; import roomescape.reservation.domain.repository.ReservationRepository;
import roomescape.reservation.domain.repository.ReservationTimeRepository; import roomescape.reservation.domain.repository.ReservationTimeRepository;
import roomescape.theme.domain.Theme; import roomescape.theme.infrastructure.persistence.ThemeEntity;
import roomescape.theme.domain.repository.ThemeRepository; import roomescape.theme.infrastructure.persistence.ThemeRepository;
@SpringBootTest @SpringBootTest
@Sql(scripts = "/truncate.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = "/truncate.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
@ -57,7 +57,7 @@ class PaymentServiceTest {
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(null, "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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "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));
@ -79,7 +79,7 @@ class PaymentServiceTest {
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(null, "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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "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));
@ -116,7 +116,7 @@ class PaymentServiceTest {
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(null, "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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "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.*;
@ -12,7 +11,6 @@ import java.time.LocalTime;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -51,8 +49,8 @@ import roomescape.reservation.domain.repository.ReservationTimeRepository;
import roomescape.reservation.dto.request.AdminReservationRequest; import roomescape.reservation.dto.request.AdminReservationRequest;
import roomescape.reservation.dto.request.ReservationRequest; import roomescape.reservation.dto.request.ReservationRequest;
import roomescape.reservation.dto.request.WaitingRequest; import roomescape.reservation.dto.request.WaitingRequest;
import roomescape.theme.domain.Theme; import roomescape.theme.infrastructure.persistence.ThemeEntity;
import roomescape.theme.domain.repository.ThemeRepository; import roomescape.theme.infrastructure.persistence.ThemeRepository;
@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)
@ -91,7 +89,7 @@ public class ReservationControllerTest {
LocalDate date = LocalDate.now().plusDays(1L); LocalDate date = LocalDate.now().plusDays(1L);
reservationTimeRepository.save(new ReservationTime(time)); reservationTimeRepository.save(new ReservationTime(time));
themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Map<String, String> reservationParams = Map.of( Map<String, String> reservationParams = Map.of(
"date", date.toString(), "date", date.toString(),
@ -126,7 +124,7 @@ public class ReservationControllerTest {
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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Member member1 = memberRepository.save(new Member(null, "name1", "email1r@email.com", "password", Role.MEMBER)); Member member1 = memberRepository.save(new Member(null, "name1", "email1r@email.com", "password", Role.MEMBER));
// when // when
@ -154,7 +152,7 @@ public class ReservationControllerTest {
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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Member waitingMember = memberRepository.save( Member waitingMember = memberRepository.save(
new Member(null, "name1", "email1r@email.com", "password", Role.MEMBER)); new Member(null, "name1", "email1r@email.com", "password", Role.MEMBER));
@ -181,7 +179,7 @@ public class ReservationControllerTest {
String accessTokenCookie = getAdminAccessTokenCookieByLogin("admin@admin.com", "12341234"); String accessTokenCookie = getAdminAccessTokenCookieByLogin("admin@admin.com", "12341234");
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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
// when // when
@ -210,7 +208,7 @@ public class ReservationControllerTest {
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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Reservation reservation = reservationRepository.save( Reservation reservation = reservationRepository.save(
new Reservation(LocalDate.now(), reservationTime, theme, member, ReservationStatus.CONFIRMED)); new Reservation(LocalDate.now(), reservationTime, theme, member, ReservationStatus.CONFIRMED));
@ -230,7 +228,7 @@ public class ReservationControllerTest {
String adminTokenCookie = getAdminAccessTokenCookieByLogin("admin@email.com", "password"); String adminTokenCookie = getAdminAccessTokenCookieByLogin("admin@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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Member confirmedMember = memberRepository.save( Member confirmedMember = memberRepository.save(
new Member(null, "name1", "email@email.com", "password", Role.MEMBER)); new Member(null, "name1", "email@email.com", "password", Role.MEMBER));
Member waitingMember = memberRepository.save( Member waitingMember = memberRepository.save(
@ -258,7 +256,7 @@ public class ReservationControllerTest {
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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Member anotherMember = memberRepository.save( Member anotherMember = memberRepository.save(
new Member(null, "name", "email@email.com", "password", Role.MEMBER)); new Member(null, "name", "email@email.com", "password", Role.MEMBER));
@ -339,7 +337,7 @@ public class ReservationControllerTest {
void getAllReservations(String requestURI, String responseFieldName, int expectedSize) { void getAllReservations(String requestURI, String responseFieldName, int expectedSize) {
// given // given
LocalDate date = LocalDate.now().plusDays(1); LocalDate date = LocalDate.now().plusDays(1);
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
ReservationTime time = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 30))); ReservationTime time = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 30)));
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)));
@ -368,7 +366,7 @@ public class ReservationControllerTest {
void removeNotPaidReservation() { void removeNotPaidReservation() {
// given // given
LocalDate date = LocalDate.now().plusDays(1); LocalDate date = LocalDate.now().plusDays(1);
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
ReservationTime time = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 30))); ReservationTime time = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 30)));
String accessToken = getAdminAccessTokenCookieByLogin("admin@email.com", "password"); String accessToken = getAdminAccessTokenCookieByLogin("admin@email.com", "password");
@ -392,7 +390,7 @@ public class ReservationControllerTest {
// given // given
String accessToken = getAdminAccessTokenCookieByLogin("admin@email.com", "password"); String accessToken = getAdminAccessTokenCookieByLogin("admin@email.com", "password");
LocalDate date = LocalDate.now().plusDays(1); LocalDate date = LocalDate.now().plusDays(1);
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일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(null, "name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
@ -423,7 +421,7 @@ public class ReservationControllerTest {
LocalDateTime localDateTime = LocalDateTime.now().minusHours(1L).withNano(0); LocalDateTime localDateTime = LocalDateTime.now().minusHours(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()));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member(null, "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());
@ -516,7 +514,7 @@ public class ReservationControllerTest {
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()));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member(null, "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(null, "name1", "email1@email.com", "password", Role.MEMBER)); Member member1 = memberRepository.save(new Member(null, "name1", "email1@email.com", "password", Role.MEMBER));
@ -542,7 +540,7 @@ public class ReservationControllerTest {
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()));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member(null, "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");
@ -587,7 +585,7 @@ public class ReservationControllerTest {
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()));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member(null, "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");

View File

@ -29,8 +29,8 @@ import roomescape.reservation.domain.ReservationStatus;
import roomescape.reservation.domain.ReservationTime; import roomescape.reservation.domain.ReservationTime;
import roomescape.reservation.domain.repository.ReservationRepository; import roomescape.reservation.domain.repository.ReservationRepository;
import roomescape.reservation.domain.repository.ReservationTimeRepository; import roomescape.reservation.domain.repository.ReservationTimeRepository;
import roomescape.theme.domain.Theme; import roomescape.theme.infrastructure.persistence.ThemeEntity;
import roomescape.theme.domain.repository.ThemeRepository; import roomescape.theme.infrastructure.persistence.ThemeRepository;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) @SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@Sql(scripts = "/truncate.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = "/truncate.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
@ -224,7 +224,7 @@ public class ReservationTimeControllerTest {
ReservationTime reservationTime1 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 0))); ReservationTime reservationTime1 = reservationTimeRepository.save(new ReservationTime(LocalTime.of(17, 0)));
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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명1", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member(null, "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(

View File

@ -13,14 +13,14 @@ import org.junit.jupiter.params.provider.MethodSource;
import roomescape.member.infrastructure.persistence.Member; import roomescape.member.infrastructure.persistence.Member;
import roomescape.member.infrastructure.persistence.Role; import roomescape.member.infrastructure.persistence.Role;
import roomescape.common.exception.RoomescapeException; import roomescape.common.exception.RoomescapeException;
import roomescape.theme.domain.Theme; import roomescape.theme.infrastructure.persistence.ThemeEntity;
public class ReservationTest { public class ReservationTest {
@ParameterizedTest @ParameterizedTest
@MethodSource("validateConstructorParameterBlankSource") @MethodSource("validateConstructorParameterBlankSource")
@DisplayName("객체 생성 시, null 또는 공백이 존재하면 예외를 발생한다.") @DisplayName("객체 생성 시, null 또는 공백이 존재하면 예외를 발생한다.")
void validateConstructorParameterBlank(LocalDate date, ReservationTime reservationTime, Theme theme, void validateConstructorParameterBlank(LocalDate date, ReservationTime reservationTime, ThemeEntity theme,
Member member) { Member member) {
// when & then // when & then
@ -33,12 +33,12 @@ public class ReservationTest {
return Stream.of( return Stream.of(
Arguments.of(null, Arguments.of(null,
new ReservationTime(LocalTime.now().plusHours(1)), new ReservationTime(LocalTime.now().plusHours(1)),
new Theme("테마명", "설명", "썸네일URI"), new ThemeEntity(null, "테마명", "설명", "썸네일URI"),
new Member(null, "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 ThemeEntity(null, "테마명", "설명", "썸네일URI"),
new Member(null, "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(),
@ -48,7 +48,7 @@ public class ReservationTest {
Arguments.of( Arguments.of(
LocalDate.now(), LocalDate.now(),
new ReservationTime(LocalTime.now().plusHours(1)), new ReservationTime(LocalTime.now().plusHours(1)),
new Theme("테마명", "설명", "썸네일URI"), new ThemeEntity(null, "테마명", "설명", "썸네일URI"),
null) null)
); );
} }

View File

@ -19,8 +19,8 @@ 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;
import roomescape.theme.domain.Theme; import roomescape.theme.infrastructure.persistence.ThemeEntity;
import roomescape.theme.domain.repository.ThemeRepository; import roomescape.theme.infrastructure.persistence.ThemeRepository;
@DataJpaTest @DataJpaTest
class ReservationSearchSpecificationTest { class ReservationSearchSpecificationTest {
@ -52,7 +52,7 @@ class ReservationSearchSpecificationTest {
LocalDateTime dateTime = LocalDateTime.now(); LocalDateTime dateTime = LocalDateTime.now();
Member member = memberRepository.save(new Member(null, "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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "name", "description", "thumbnail"));
reservation1 = reservationRepository.save( reservation1 = reservationRepository.save(
new Reservation(dateTime.toLocalDate(), time, theme, member, ReservationStatus.CONFIRMED)); new Reservation(dateTime.toLocalDate(), time, theme, member, ReservationStatus.CONFIRMED));

View File

@ -27,9 +27,9 @@ import roomescape.reservation.dto.request.ReservationRequest;
import roomescape.reservation.dto.request.WaitingRequest; import roomescape.reservation.dto.request.WaitingRequest;
import roomescape.reservation.dto.response.ReservationResponse; import roomescape.reservation.dto.response.ReservationResponse;
import roomescape.common.exception.RoomescapeException; import roomescape.common.exception.RoomescapeException;
import roomescape.theme.domain.Theme; import roomescape.theme.infrastructure.persistence.ThemeEntity;
import roomescape.theme.domain.repository.ThemeRepository; import roomescape.theme.infrastructure.persistence.ThemeRepository;
import roomescape.theme.service.ThemeService; import roomescape.theme.business.ThemeService;
@SpringBootTest @SpringBootTest
@Sql(scripts = "/truncate.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = "/truncate.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
@ -52,7 +52,7 @@ class ReservationServiceTest {
void reservationAlreadyExistFail() { void reservationAlreadyExistFail() {
// 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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Member member1 = memberRepository.save(new Member(null, "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(null, "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);
@ -74,7 +74,7 @@ class ReservationServiceTest {
void requestWaitWhenAlreadyReserveFail() { void requestWaitWhenAlreadyReserveFail() {
// 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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member(null, "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);
@ -94,7 +94,7 @@ class ReservationServiceTest {
void requestWaitTwiceFail() { void requestWaitTwiceFail() {
// 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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member(null, "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(null, "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);
@ -118,7 +118,7 @@ class ReservationServiceTest {
void beforeDateReservationFail() { void beforeDateReservationFail() {
// 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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member(null, "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);
@ -135,7 +135,7 @@ class ReservationServiceTest {
// given // given
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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member(null, "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
@ -151,7 +151,7 @@ class ReservationServiceTest {
// given // given
LocalDateTime beforeTime = LocalDateTime.now().minusDays(1L).withNano(0); LocalDateTime beforeTime = LocalDateTime.now().minusDays(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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Long NotExistMemberId = 1L; Long NotExistMemberId = 1L;
// when & then // when & then
@ -179,7 +179,7 @@ class ReservationServiceTest {
void confirmWaitingWhenReservationExist() { void confirmWaitingWhenReservationExist() {
// 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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Member admin = memberRepository.save(new Member(null, "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(null, "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(null, "name1", "email1@email.com", "password", Role.MEMBER)); Member member1 = memberRepository.save(new Member(null, "name1", "email1@email.com", "password", Role.MEMBER));
@ -202,7 +202,7 @@ class ReservationServiceTest {
void approveWaiting() { void approveWaiting() {
// 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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Member admin = memberRepository.save(new Member(null, "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(null, "name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));

View File

@ -23,8 +23,8 @@ import roomescape.reservation.domain.repository.ReservationRepository;
import roomescape.reservation.domain.repository.ReservationTimeRepository; import roomescape.reservation.domain.repository.ReservationTimeRepository;
import roomescape.reservation.dto.request.ReservationTimeRequest; import roomescape.reservation.dto.request.ReservationTimeRequest;
import roomescape.common.exception.RoomescapeException; import roomescape.common.exception.RoomescapeException;
import roomescape.theme.domain.Theme; import roomescape.theme.infrastructure.persistence.ThemeEntity;
import roomescape.theme.domain.repository.ThemeRepository; import roomescape.theme.infrastructure.persistence.ThemeRepository;
@SpringBootTest @SpringBootTest
@Import(ReservationTimeService.class) @Import(ReservationTimeService.class)
@ -74,7 +74,7 @@ class ReservationTimeServiceTest {
LocalDateTime localDateTime = LocalDateTime.now().plusDays(1L).withNano(0); LocalDateTime localDateTime = LocalDateTime.now().plusDays(1L).withNano(0);
ReservationTime reservationTime = reservationTimeRepository.save( ReservationTime reservationTime = reservationTimeRepository.save(
new ReservationTime(localDateTime.toLocalTime())); new ReservationTime(localDateTime.toLocalTime()));
Theme theme = themeRepository.save(new Theme("테마명", "설명", "썸네일URL")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "테마명", "설명", "썸네일URL"));
Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER)); Member member = memberRepository.save(new Member(null, "name", "email@email.com", "password", Role.MEMBER));
// when // when

View File

@ -28,8 +28,8 @@ import roomescape.reservation.domain.repository.ReservationRepository;
import roomescape.reservation.domain.repository.ReservationTimeRepository; import roomescape.reservation.domain.repository.ReservationTimeRepository;
import roomescape.reservation.dto.request.ReservationRequest; import roomescape.reservation.dto.request.ReservationRequest;
import roomescape.reservation.dto.response.ReservationResponse; import roomescape.reservation.dto.response.ReservationResponse;
import roomescape.theme.domain.Theme; import roomescape.theme.infrastructure.persistence.ThemeEntity;
import roomescape.theme.domain.repository.ThemeRepository; import roomescape.theme.infrastructure.persistence.ThemeRepository;
@SpringBootTest @SpringBootTest
@Sql(scripts = "/truncate.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = "/truncate.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
@ -60,7 +60,7 @@ class ReservationWithPaymentServiceTest {
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(null, "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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "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");
@ -96,7 +96,7 @@ class ReservationWithPaymentServiceTest {
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(null, "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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "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");
@ -124,7 +124,7 @@ class ReservationWithPaymentServiceTest {
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(null, "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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "name", "desc", "thumbnail"));
Reservation saved = reservationRepository.save( Reservation saved = reservationRepository.save(
new Reservation(date, time, theme, member, ReservationStatus.CONFIRMED_PAYMENT_REQUIRED)); new Reservation(date, time, theme, member, ReservationStatus.CONFIRMED_PAYMENT_REQUIRED));
@ -146,7 +146,7 @@ class ReservationWithPaymentServiceTest {
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(null, "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")); ThemeEntity theme = themeRepository.save(new ThemeEntity(null, "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,103 @@
package roomescape.theme.business
import io.kotest.assertions.assertSoftly
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.mockk
import org.springframework.data.repository.findByIdOrNull
import org.springframework.http.HttpStatus
import roomescape.common.exception.ErrorType
import roomescape.common.exception.RoomescapeException
import roomescape.theme.infrastructure.persistence.ThemeEntity
import roomescape.theme.infrastructure.persistence.ThemeRepository
import roomescape.theme.web.ThemeRequest
import roomescape.util.ThemeFixture
class ThemeServiceTest : FunSpec({
val themeRepository: ThemeRepository = mockk()
val themeService = ThemeService(themeRepository)
context("findThemeById") {
val themeId = 1L
test("조회 성공") {
val theme: ThemeEntity = ThemeFixture.create(id = themeId)
every {
themeRepository.findByIdOrNull(themeId)
} returns theme
theme.id shouldBe themeId
}
test("ID로 테마를 찾을 수 없으면 400 예외를 던진다.") {
every {
themeRepository.findByIdOrNull(themeId)
} returns null
val exception = shouldThrow<RoomescapeException> {
themeService.findThemeById(themeId)
}
exception.errorType shouldBe ErrorType.THEME_NOT_FOUND
}
}
context("findAllThemes") {
test("모든 테마를 조회한다.") {
val themes = listOf(ThemeFixture.create(id = 1, name = "t1"), ThemeFixture.create(id = 2, name = "t2"))
every {
themeRepository.findAll()
} returns themes
assertSoftly(themeService.findAllThemes()) {
this.themes.size shouldBe themes.size
this.themes[0].name shouldBe "t1"
this.themes[1].name shouldBe "t2"
}
}
}
context("save") {
test("테마 이름이 중복되면 409 예외를 던진다.") {
val name = "Duplicate Theme"
every {
themeRepository.existsByName(name)
} returns true
val exception = shouldThrow<RoomescapeException> {
themeService.save(ThemeRequest(
name = name,
description = "Description",
thumbnail = "http://example.com/thumbnail.jpg"
))
}
assertSoftly(exception) {
this.errorType shouldBe ErrorType.THEME_DUPLICATED
this.httpStatus shouldBe HttpStatus.CONFLICT
}
}
}
context("deleteById") {
test("이미 예약 중인 테마라면 409 예외를 던진다.") {
val themeId = 1L
every {
themeRepository.isReservedTheme(themeId)
} returns true
val exception = shouldThrow<RoomescapeException> {
themeService.deleteById(themeId)
}
assertSoftly(exception) {
this.errorType shouldBe ErrorType.THEME_IS_USED_CONFLICT
this.httpStatus shouldBe HttpStatus.CONFLICT
}
}
}
})

View File

@ -1,184 +0,0 @@
package roomescape.theme.controller;
import static org.hamcrest.Matchers.*;
import java.util.Map;
import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
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 org.springframework.test.context.jdbc.Sql.ExecutionPhase;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.http.Header;
import roomescape.member.infrastructure.persistence.Member;
import roomescape.member.infrastructure.persistence.MemberRepository;
import roomescape.member.infrastructure.persistence.Role;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Sql(scripts = "/truncate.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
class ThemeControllerTest {
@LocalServerPort
private int port;
@Autowired
private MemberRepository memberRepository;
@Test
@DisplayName("모든 테마 정보를 조회한다.")
void readThemes() {
String email = "admin@test.com";
String password = "12341234";
String adminAccessTokenCookie = getAdminAccessTokenCookieByLogin(email, password);
RestAssured.given().log().all()
.contentType(ContentType.JSON)
.header(new Header("Cookie", adminAccessTokenCookie))
.port(port)
.when().get("/themes")
.then().log().all()
.statusCode(200)
.body("data.themes.size()", is(0));
}
@Test
@DisplayName("테마를 추가한다.")
void createThemes() {
String email = "admin@test.com";
String password = "12341234";
String adminAccessTokenCookie = getAdminAccessTokenCookieByLogin(email, password);
Map<String, String> params = Map.of(
"name", "테마명",
"description", "설명",
"thumbnail", "http://testsfasdgasd.com"
);
RestAssured.given().log().all()
.contentType(ContentType.JSON)
.header(new Header("Cookie", adminAccessTokenCookie))
.port(port)
.body(params)
.when().post("/themes")
.then().log().all()
.statusCode(201)
.body("data.id", is(1))
.header("Location", "/themes/1");
}
@Test
@DisplayName("테마를 삭제한다.")
void deleteThemes() {
String email = "admin@test.com";
String password = "12341234";
String adminAccessTokenCookie = getAdminAccessTokenCookieByLogin(email, password);
Map<String, String> params = Map.of(
"name", "테마명",
"description", "설명",
"thumbnail", "http://testsfasdgasd.com"
);
RestAssured.given().log().all()
.contentType(ContentType.JSON)
.header(new Header("Cookie", adminAccessTokenCookie))
.port(port)
.body(params)
.when().post("/themes")
.then().log().all()
.statusCode(201)
.body("data.id", is(1))
.header("Location", "/themes/1");
RestAssured.given().log().all()
.contentType(ContentType.JSON)
.header(new Header("Cookie", adminAccessTokenCookie))
.port(port)
.when().delete("/themes/1")
.then().log().all()
.statusCode(204);
}
/*
* reservationData DataSet ThemeID reservation 개수
* 5,4,2,5,2,3,1,1,1,1,1
* 예약 내림차순 + ThemeId 오름차순 정렬 순서
* 1, 4, 2, 6, 3, 5, 7, 8, 9, 10
*/
@Test
@DisplayName("예약 수 상위 10개 테마를 조회했을 때 내림차순으로 정렬된다. 만약 예약 수가 같다면, id 순으로 오름차순 정렬된다.")
@Sql(scripts = {"/truncate.sql", "/reservationData.sql"}, executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
void readTop10ThemesDescOrder() {
RestAssured.given().log().all()
.contentType(ContentType.JSON)
.port(port)
.when().get("/themes/most-reserved-last-week?count=10")
.then().log().all()
.statusCode(200)
.body("data.themes.size()", is(10))
.body("data.themes.id", contains(1, 4, 2, 6, 3, 5, 7, 8, 9, 10));
}
@ParameterizedTest
@MethodSource("requestValidateSource")
@DisplayName("테마 생성 시, 요청 값에 공백 또는 null이 포함되어 있으면 400 에러를 발생한다.")
void validateBlankRequest(Map<String, String> invalidRequestBody) {
String email = "admin@test.com";
String password = "12341234";
String adminAccessTokenCookie = getAdminAccessTokenCookieByLogin(email, password);
RestAssured.given().log().all()
.contentType(ContentType.JSON)
.header(new Header("Cookie", adminAccessTokenCookie))
.port(port)
.body(invalidRequestBody)
.when().post("/themes")
.then().log().all()
.statusCode(400);
}
static Stream<Map<String, String>> requestValidateSource() {
return Stream.of(
Map.of(
"name", "테마명",
"thumbnail", "http://testsfasdgasd.com"
),
Map.of(
"name", "",
"description", "설명",
"thumbnail", "http://testsfasdgasd.com"
),
Map.of(
"name", " ",
"description", "설명",
"thumbnail", "http://testsfasdgasd.com"
)
);
}
private String getAdminAccessTokenCookieByLogin(final String email, final String password) {
memberRepository.save(new Member(null, "이름", 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,147 @@
package roomescape.theme.infrastructure.persistence
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.collections.shouldContainInOrder
import io.kotest.matchers.shouldBe
import jakarta.persistence.EntityManager
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
import roomescape.theme.util.TestThemeCreateUtil
import java.time.LocalDate
@DataJpaTest
class ThemeRepositoryTest(
val themeRepository: ThemeRepository,
val entityManager: EntityManager
) : FunSpec() {
init {
context("findTopNThemeBetweenStartDateAndEndDate") {
beforeTest {
for (i in 1..10) {
TestThemeCreateUtil.createThemeWithReservations(
entityManager = entityManager,
name = "테마$i",
reservedCount = i,
date = LocalDate.now().minusDays(i.toLong()),
)
}
}
test("지난 10일간 예약 수가 가장 많은 테마 5개를 조회한다.") {
themeRepository.findTopNThemeBetweenStartDateAndEndDate(
LocalDate.now().minusDays(10),
LocalDate.now().minusDays(1),
5
).also { themes ->
themes.size shouldBe 5
themes.map { it.name } shouldContainInOrder listOf(
"테마10", "테마9", "테마8", "테마7", "테마6"
)
}
}
test("8일 전부터 5일 전까지 예약 수가 가장 많은 테마 3개를 조회한다.") {
themeRepository.findTopNThemeBetweenStartDateAndEndDate(
LocalDate.now().minusDays(8),
LocalDate.now().minusDays(5),
3
).also { themes ->
themes.size shouldBe 3
themes.map { it.name } shouldContainInOrder listOf(
"테마8", "테마7", "테마6"
)
}
}
test("예약 수가 동일하면 먼저 생성된 테마를 우선 조회한다.") {
TestThemeCreateUtil.createThemeWithReservations(
entityManager = entityManager,
name = "테마11",
reservedCount = 5,
date = LocalDate.now().minusDays(5),
)
themeRepository.findTopNThemeBetweenStartDateAndEndDate(
LocalDate.now().minusDays(6),
LocalDate.now().minusDays(4),
5
).also { themes ->
themes.size shouldBe 4
themes.map { it.name } shouldContainInOrder listOf(
"테마6", "테마5", "테마11", "테마4"
)
}
}
test("입력된 갯수보다 조회된 갯수가 작으면, 조회된 갯수만큼 반환한다.") {
themeRepository.findTopNThemeBetweenStartDateAndEndDate(
LocalDate.now().minusDays(10),
LocalDate.now().minusDays(6),
10
).also { themes ->
themes.size shouldBe 5
}
}
test("입력된 갯수보다 조회된 갯수가 많으면, 입력된 갯수만큼 반환한다.") {
themeRepository.findTopNThemeBetweenStartDateAndEndDate(
LocalDate.now().minusDays(10),
LocalDate.now().minusDays(1),
15
).also { themes ->
themes.size shouldBe 10
}
}
test("입력된 날짜 범위에 예약된 테마가 없을 경우 빈 리스트를 반환한다.") {
themeRepository.findTopNThemeBetweenStartDateAndEndDate(
LocalDate.now().plusDays(1),
LocalDate.now().plusDays(10),
5
).also { themes ->
themes.size shouldBe 0
}
}
}
context("existsByName ") {
val themeName = "test-theme"
beforeTest {
TestThemeCreateUtil.createThemeWithReservations(
entityManager = entityManager,
name = themeName,
reservedCount = 0,
date = LocalDate.now()
)
}
test("테마 이름이 존재하면 true를 반환한다.") {
themeRepository.existsByName(themeName) shouldBe true
}
test("테마 이름이 존재하지 않으면 false를 반환한다.") {
themeRepository.existsByName(themeName.repeat(2)) shouldBe false
}
}
context("isReservedTheme") {
test("테마가 예약 중이면 true를 반환한다.") {
val theme = TestThemeCreateUtil.createThemeWithReservations(
entityManager = entityManager,
name = "예약된 테마",
reservedCount = 1,
date = LocalDate.now()
)
themeRepository.isReservedTheme(theme.id!!) shouldBe true
}
test("테마가 예약 중이 아니면 false를 반환한다.") {
val theme = TestThemeCreateUtil.createThemeWithReservations(
entityManager = entityManager,
name = "예약되지 않은 테마",
reservedCount = 0,
date = LocalDate.now()
)
themeRepository.isReservedTheme(theme.id!!) shouldBe false
}
}
}
}

View File

@ -1,164 +0,0 @@
package roomescape.theme.service;
import static org.assertj.core.api.Assertions.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.jdbc.Sql;
import roomescape.member.business.MemberService;
import roomescape.member.infrastructure.persistence.Member;
import roomescape.member.infrastructure.persistence.MemberRepository;
import roomescape.member.infrastructure.persistence.Role;
import roomescape.reservation.dto.request.ReservationRequest;
import roomescape.reservation.dto.request.ReservationTimeRequest;
import roomescape.reservation.dto.response.ReservationTimeResponse;
import roomescape.reservation.service.ReservationService;
import roomescape.reservation.service.ReservationTimeService;
import roomescape.common.exception.RoomescapeException;
import roomescape.theme.domain.Theme;
import roomescape.theme.domain.repository.ThemeRepository;
import roomescape.theme.dto.ThemeRequest;
import roomescape.theme.dto.ThemeResponse;
import roomescape.theme.dto.ThemesResponse;
@DataJpaTest
@Import({ReservationTimeService.class, ReservationService.class, MemberService.class, ThemeService.class})
@Sql(scripts = "/truncate.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
class ThemeServiceTest {
@Autowired
private ThemeRepository themeRepository;
@Autowired
private ThemeService themeService;
@Autowired
private ReservationTimeService reservationTimeService;
@Autowired
private MemberRepository memberRepository;
@Autowired
private ReservationService reservationService;
@Test
@DisplayName("테마를 조회한다.")
void findThemeById() {
// given
Theme theme = themeRepository.save(new Theme("name", "description", "thumbnail"));
// when
Theme foundTheme = themeService.findThemeById(theme.getId());
// then
assertThat(foundTheme).isEqualTo(theme);
}
@Test
@DisplayName("존재하지 않는 ID로 테마를 조회하면 예외가 발생한다.")
void findThemeByNotExistId() {
// given
Theme theme = themeRepository.save(new Theme("name", "description", "thumbnail"));
// when
Long notExistId = theme.getId() + 1;
// then
assertThatThrownBy(() -> themeService.findThemeById(notExistId))
.isInstanceOf(RoomescapeException.class);
}
@Test
@DisplayName("모든 테마를 조회한다.")
void findAllThemes() {
// given
Theme theme = themeRepository.save(new Theme("name", "description", "thumbnail"));
Theme theme1 = themeRepository.save(new Theme("name1", "description1", "thumbnail1"));
// when
ThemesResponse found = themeService.findAllThemes();
// then
assertThat(found.themes()).extracting("id").containsExactly(theme.getId(), theme1.getId());
}
@Test
@DisplayName("예약 수 상위 10개 테마를 조회했을 때 내림차순으로 정렬된다. 만약 예약 수가 같다면, id 순으로 오름차순 정렬된다.")
@Sql({"/truncate.sql", "/reservationData.sql"})
void getMostReservedThemesByCount() {
// given
LocalDate today = LocalDate.now();
// when
List<ThemeResponse> found = themeService.getMostReservedThemesByCount(10).themes();
// then : 11번 테마는 조회되지 않아야 한다.
assertThat(found).extracting("id").containsExactly(1L, 4L, 2L, 6L, 3L, 5L, 7L, 8L, 9L, 10L);
}
@Test
@DisplayName("테마를 추가한다.")
void addTheme() {
// given
ThemeResponse themeResponse = themeService.addTheme(new ThemeRequest("name", "description", "thumbnail"));
// when
Theme found = themeRepository.findById(themeResponse.id()).orElse(null);
// then
assertThat(found).isNotNull();
}
@Test
@DisplayName("테마를 추가할 때 같은 이름의 테마가 존재하면 예외가 발생한다. ")
void addDuplicateTheme() {
// given
ThemeResponse themeResponse = themeService.addTheme(new ThemeRequest("name", "description", "thumbnail"));
// when
ThemeRequest invalidRequest = new ThemeRequest(themeResponse.name(), "description", "thumbnail");
// then
assertThatThrownBy(() -> themeService.addTheme(invalidRequest))
.isInstanceOf(RoomescapeException.class);
}
@Test
@DisplayName("테마를 삭제한다.")
void removeThemeById() {
// given
Theme theme = themeRepository.save(new Theme("name", "description", "thumbnail"));
// when
themeService.removeThemeById(theme.getId());
// then
assertThat(themeRepository.findById(theme.getId())).isEmpty();
}
@Test
@DisplayName("예약이 존재하는 테마를 삭제하면 예외가 발생한다.")
void removeReservedTheme() {
// given
LocalDateTime dateTime = LocalDateTime.now().plusDays(1);
ReservationTimeResponse time = reservationTimeService.addTime(
new ReservationTimeRequest(dateTime.toLocalTime()));
Theme theme = themeRepository.save(new Theme("name", "description", "thumbnail"));
Member member = memberRepository.save(new Member(null, "member", "password", "name", Role.MEMBER));
reservationService.addReservation(
new ReservationRequest(dateTime.toLocalDate(), time.id(), theme.getId(), "paymentKey", "orderId", 1000L,
"NORMAL"), member.getId());
// when & then
assertThatThrownBy(() -> themeService.removeThemeById(theme.getId()))
.isInstanceOf(RoomescapeException.class);
}
}

View File

@ -0,0 +1,44 @@
package roomescape.theme.util
import jakarta.persistence.EntityManager
import roomescape.member.infrastructure.persistence.Member
import roomescape.reservation.domain.ReservationStatus
import roomescape.reservation.domain.ReservationTime
import roomescape.theme.infrastructure.persistence.ThemeEntity
import roomescape.util.MemberFixture
import roomescape.util.ReservationFixture
import roomescape.util.ReservationTimeFixture
import roomescape.util.ThemeFixture
import java.time.LocalDate
import java.time.LocalTime
object TestThemeCreateUtil {
fun createThemeWithReservations(
entityManager: EntityManager,
name: String,
reservedCount: Int,
date: LocalDate,
): ThemeEntity {
val themeEntity: ThemeEntity = ThemeFixture.create(name = name).also { entityManager.persist(it) }
val member: Member = MemberFixture.create().also { entityManager.persist(it) }
for (i in 1..reservedCount) {
val time: ReservationTime = ReservationTimeFixture.create(
startAt = LocalTime.now().plusMinutes(i.toLong())
).also { entityManager.persist(it) }
ReservationFixture.create(
date = date,
themeEntity = themeEntity,
member = member,
reservationTime = time,
status = ReservationStatus.CONFIRMED
).also { entityManager.persist(it) }
}
entityManager.flush()
entityManager.clear()
return themeEntity
}
}

View File

@ -0,0 +1,113 @@
package roomescape.theme.web
import io.kotest.core.spec.style.FunSpec
import io.restassured.module.kotlin.extensions.Given
import io.restassured.module.kotlin.extensions.Then
import io.restassured.module.kotlin.extensions.When
import jakarta.persistence.EntityManager
import org.hamcrest.Matchers.equalTo
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.web.server.LocalServerPort
import org.springframework.test.context.jdbc.Sql
import org.springframework.transaction.support.TransactionTemplate
import roomescape.theme.business.ThemeService
import roomescape.theme.util.TestThemeCreateUtil
import java.time.LocalDate
import kotlin.random.Random
/**
* GET /themes/most-reserved-last-week API 테스트
* 상세 테스트는 Repository 테스트에서 진행
* 날짜 범위, 예약 수만 검증
*/
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Sql(value = ["/truncate.sql"], executionPhase = Sql.ExecutionPhase.AFTER_TEST_CLASS)
class MostReservedThemeAPITest(
@LocalServerPort val port: Int,
val themeService: ThemeService,
val transactionTemplate: TransactionTemplate,
val entityManager: EntityManager,
) : FunSpec() {
init {
beforeSpec {
transactionTemplate.executeWithoutResult {
// 지난 7일간 예약된 테마 10개 생성
for (i in 1..10) {
TestThemeCreateUtil.createThemeWithReservations(
entityManager = entityManager,
name = "테마$i",
reservedCount = 1,
date = LocalDate.now().minusDays(Random.nextLong(1, 7))
)
}
// 8일 전 예약된 테마 1개 생성
TestThemeCreateUtil.createThemeWithReservations(
entityManager = entityManager,
name = "테마11",
reservedCount = 1,
date = LocalDate.now().minusDays(8)
)
}
}
context("가장 많이 예약된 테마를 조회할 때, ") {
val endpoint = "/themes/most-reserved-last-week"
test("갯수를 입력하지 않으면 10개를 반환한다.") {
Given {
port(port)
} When {
get(endpoint)
} Then {
log().all()
statusCode(200)
body("data.themes.size()", equalTo(10))
}
}
test("입력된 갯수가 조회된 갯수보다 크면 조회된 갯수만큼 반환한다.") {
val count = 15
Given {
port(port)
} When {
param("count", count)
get("/themes/most-reserved-last-week")
} Then {
log().all()
statusCode(200)
body("data.themes.size()", equalTo(10))
}
}
test("입력된 갯수가 조회된 갯수보다 작으면 입력된 갯수만큼 반환한다.") {
val count = 5
Given {
port(port)
} When {
param("count", count)
get("/themes/most-reserved-last-week")
} Then {
log().all()
statusCode(200)
body("data.themes.size()", equalTo(count))
}
}
test("7일 전 부터 1일 전 까지 예약된 테마를 대상으로 한다.") {
// 현재 저장된 데이터는 지난 7일간 예약된 테마 10개와 8일 전 예약된 테마 1개
// 8일 전 예약된 테마는 제외되어야 하므로, 10개가 조회되어야 한다.
val count = 11
Given {
port(port)
} When {
param("count", count)
get("/themes/most-reserved-last-week")
} Then {
log().all()
statusCode(200)
body("data.themes.size()", equalTo(10))
}
}
}
}
}

View File

@ -0,0 +1,307 @@
package roomescape.theme.web
import com.ninjasquad.springmockk.MockkBean
import com.ninjasquad.springmockk.SpykBean
import io.kotest.assertions.assertSoftly
import io.kotest.matchers.collections.shouldContainAll
import io.kotest.matchers.shouldBe
import io.mockk.every
import io.mockk.just
import io.mockk.runs
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import roomescape.theme.business.ThemeService
import roomescape.theme.infrastructure.persistence.ThemeRepository
import roomescape.util.RoomescapeApiTest
import roomescape.util.ThemeFixture
@WebMvcTest(ThemeController::class)
class ThemeControllerTest(mockMvc: MockMvc) : RoomescapeApiTest() {
@SpykBean
private lateinit var themeService: ThemeService
@MockkBean
private lateinit var themeRepository: ThemeRepository
init {
Given("모든 테마를 조회할 때") {
val endpoint = "/themes"
When("로그인 상태가 아니라면") {
doNotLogin()
Then("로그인 페이지로 이동한다.") {
runGetTest(
mockMvc = mockMvc,
endpoint = endpoint,
log = true
) {
status { is3xxRedirection() }
header {
string("Location", "/login")
}
}
}
}
When("로그인 상태라면") {
loginAsUser()
Then("조회에 성공한다.") {
every {
themeRepository.findAll()
} returns listOf(
ThemeFixture.create(id = 1, name = "theme1"),
ThemeFixture.create(id = 2, name = "theme2"),
ThemeFixture.create(id = 3, name = "theme3")
)
val response: ThemesResponse = runGetTest(
mockMvc = mockMvc,
endpoint = endpoint,
log = true
) {
status { isOk() }
content {
contentType(MediaType.APPLICATION_JSON)
}
}.andReturn().readValue(ThemesResponse::class.java)
assertSoftly(response.themes) {
it.size shouldBe 3
it.map { m -> m.name } shouldContainAll listOf("theme1", "theme2", "theme3")
}
}
}
}
Given("테마를 추가할 때") {
val endpoint = "/themes"
val request = ThemeRequest(
name = "theme1",
description = "description1",
thumbnail = "http://example.com/thumbnail1.jpg"
)
When("로그인 상태가 아니라면") {
doNotLogin()
Then("로그인 페이지로 이동한다.") {
runPostTest(
mockMvc = mockMvc,
endpoint = endpoint,
body = request,
log = true
) {
status { is3xxRedirection() }
header {
string("Location", "/login")
}
}
}
}
When("관리자가 아닌 회원은") {
loginAsUser()
Then("로그인 페이지로 이동한다.") {
runPostTest(
mockMvc = mockMvc,
endpoint = endpoint,
body = request,
log = true
) {
status { is3xxRedirection() }
jsonPath("$.errorType") { value("PERMISSION_DOES_NOT_EXIST") }
}
}
}
When("동일한 이름의 테마가 있으면") {
loginAsAdmin()
Then("409 에러를 응답한다.") {
every {
themeRepository.existsByName(request.name)
} returns true
runPostTest(
mockMvc = mockMvc,
endpoint = endpoint,
body = request,
log = true
) {
status { isConflict() }
jsonPath("$.errorType") { value("THEME_DUPLICATED") }
}
}
}
When("값이 잘못 입력되면 400 에러를 응답한다") {
beforeTest {
loginAsAdmin()
}
val request = ThemeRequest(
name = "theme1",
description = "description1",
thumbnail = "http://example.com/thumbnail1.jpg"
)
fun runTest(request: ThemeRequest) {
runPostTest(
mockMvc = mockMvc,
endpoint = endpoint,
body = request,
log = true
) {
status { isBadRequest() }
}
}
Then("이름이 공백인 경우") {
val invalidRequest = request.copy(name = " ")
runTest(invalidRequest)
}
Then("이름이 20글자를 초과하는 경우") {
val invalidRequest = request.copy(name = "a".repeat(21))
runTest(invalidRequest)
}
Then("설명이 공백인 경우") {
val invalidRequest = request.copy(description = " ")
runTest(invalidRequest)
}
Then("설명이 100글자를 초과하는 경우") {
val invalidRequest = request.copy(description = "a".repeat(101))
runTest(invalidRequest)
}
Then("썸네일이 공백인 경우") {
val invalidRequest = request.copy(thumbnail = " ")
runTest(invalidRequest)
}
Then("썸네일이 URL 형식이 아닌 경우") {
val invalidRequest = request.copy(thumbnail = "invalid-url")
runTest(invalidRequest)
}
}
When("저장에 성공하면") {
loginAsAdmin()
val theme = ThemeFixture.create(
id = 1,
name = request.name,
description = request.description,
thumbnail = request.thumbnail
)
every {
themeRepository.existsByName(request.name)
} returns false
every {
themeRepository.save(any())
} returns theme
Then("201 응답을 받는다.") {
runPostTest(
mockMvc = mockMvc,
endpoint = endpoint,
body = request,
log = true
) {
status { isCreated() }
header {
string("Location", "/themes/${theme.id}")
}
jsonPath("$.data.id") { value(theme.id) }
jsonPath("$.data.name") { value(theme.name) }
jsonPath("$.data.description") { value(theme.description) }
jsonPath("$.data.thumbnail") { value(theme.thumbnail) }
}
}
}
}
Given("테마를 제거할 때") {
val themeId = 1L
val endpoint = "/themes/$themeId"
When("로그인 상태가 아니라면") {
doNotLogin()
Then("로그인 페이지로 이동한다.") {
runDeleteTest(
mockMvc = mockMvc,
endpoint = endpoint,
log = true
) {
status { is3xxRedirection() }
header {
string("Location", "/login")
}
}
}
}
When("관리자가 아닌 회원은") {
loginAsUser()
Then("로그인 페이지로 이동한다.") {
runDeleteTest(
mockMvc = mockMvc,
endpoint = endpoint,
log = true
) {
status { is3xxRedirection() }
jsonPath("$.errorType") { value("PERMISSION_DOES_NOT_EXIST") }
}
}
}
When("입력된 ID에 해당하는 테마가 없으면") {
loginAsAdmin()
Then("409 에러를 응답한다.") {
every {
themeRepository.isReservedTheme(themeId)
} returns true
runDeleteTest(
mockMvc = mockMvc,
endpoint = endpoint,
log = true
) {
status { isConflict() }
jsonPath("$.errorType") { value("THEME_IS_USED_CONFLICT") }
}
}
}
When("정상적으로 제거되면") {
loginAsAdmin()
every {
themeRepository.isReservedTheme(themeId)
} returns false
every {
themeRepository.deleteById(themeId)
} just runs
Then("204 응답을 받는다.") {
runDeleteTest(
mockMvc = mockMvc,
endpoint = endpoint,
log = true
) {
status { isNoContent() }
}
}
}
}
}
}

View File

@ -11,7 +11,7 @@ import roomescape.payment.web.PaymentCancel
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
import roomescape.theme.domain.Theme import roomescape.theme.infrastructure.persistence.ThemeEntity
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalTime import java.time.LocalTime
import java.time.OffsetDateTime import java.time.OffsetDateTime
@ -62,18 +62,18 @@ object ThemeFixture {
name: String = "Default Theme", name: String = "Default Theme",
description: String = "Default Description", description: String = "Default Description",
thumbnail: String = "https://example.com/default-thumbnail.jpg" thumbnail: String = "https://example.com/default-thumbnail.jpg"
): Theme = Theme(id, name, description, thumbnail) ): ThemeEntity = ThemeEntity(id, name, description, thumbnail)
} }
object ReservationFixture { object ReservationFixture {
fun create( fun create(
id: Long? = null, id: Long? = null,
date: LocalDate = LocalDate.now().plusWeeks(1), date: LocalDate = LocalDate.now().plusWeeks(1),
theme: Theme = ThemeFixture.create(), themeEntity: ThemeEntity = ThemeFixture.create(),
reservationTime: ReservationTime = ReservationTimeFixture.create(), reservationTime: ReservationTime = ReservationTimeFixture.create(),
member: Member = MemberFixture.create(), member: Member = MemberFixture.create(),
status: ReservationStatus = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED status: ReservationStatus = ReservationStatus.CONFIRMED_PAYMENT_REQUIRED
): Reservation = Reservation(id, date, reservationTime, theme, member, status) ): Reservation = Reservation(id, date, reservationTime, themeEntity, member, status)
} }
object JwtFixture { object JwtFixture {

View File

@ -55,9 +55,7 @@ abstract class RoomescapeApiTest : BehaviorSpec() {
header(HttpHeaders.COOKIE, "accessToken=token") header(HttpHeaders.COOKIE, "accessToken=token")
}.apply { }.apply {
log.takeIf { it }?.let { this.andDo { print() } } log.takeIf { it }?.let { this.andDo { print() } }
}.andExpect { }.andExpect(assert)
assert
}
fun runPostTest( fun runPostTest(
mockMvc: MockMvc, mockMvc: MockMvc,
@ -77,6 +75,19 @@ abstract class RoomescapeApiTest : BehaviorSpec() {
assert assert
} }
fun runDeleteTest(
mockMvc: MockMvc,
endpoint: String,
log: Boolean = false,
assert: MockMvcResultMatchersDsl.() -> Unit
): ResultActionsDsl = mockMvc.delete(endpoint) {
header(HttpHeaders.COOKIE, "accessToken=token")
}.apply {
log.takeIf { it }?.let { this.andDo { print() } }
}.andExpect {
assert
}
fun loginAsAdmin() { fun loginAsAdmin() {
every { every {
jwtHandler.getMemberIdFromToken(any()) jwtHandler.getMemberIdFromToken(any())
@ -104,6 +115,13 @@ abstract class RoomescapeApiTest : BehaviorSpec() {
every { memberRepository.findByIdOrNull(NOT_LOGGED_IN_USERID) } returns null every { memberRepository.findByIdOrNull(NOT_LOGGED_IN_USERID) } returns null
} }
fun <T> MvcResult.readValue(valueType: Class<T>): T = this.response.contentAsString
.takeIf { it.isNotBlank() }
?.let { readValue(it, valueType) }
?: throw RuntimeException("""
[Test] Exception occurred while reading response json: ${this.response.contentAsString} with value type: $valueType
""".trimIndent())
fun <T> readValue(responseJson: String, valueType: Class<T>): T = objectMapper fun <T> readValue(responseJson: String, valueType: Class<T>): T = objectMapper
.readTree(responseJson)["data"] .readTree(responseJson)["data"]
?.let { objectMapper.convertValue(it, valueType) } ?.let { objectMapper.convertValue(it, valueType) }