Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,8 @@ curl -X GET http://localhost:8080/api/v1/movies
- [캐싱 성능 테스트 보고서](https://gusty-football-62b.notion.site/17f81b29f03680718163fe0b7798383e)
- [분산락 테스트 보고서](https://gusty-football-62b.notion.site/18781b29f03680049de7db34240a6733)

### jacoco 리포트

| movie-api | booking-api | application | infrastructure | domain |
|----------------------------| ----------- |----------------------------|----------------------------|----------------------------|
| ![j_m](etc/readme/j_m.png) | ![j_b](etc/readme/j_b.png) | ![j_a](etc/readme/j_a.png) | ![j_i](etc/readme/j_i.png) | ![j_d](etc/readme/j_d.png) |
12 changes: 10 additions & 2 deletions application/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@ dependencies {
implementation project(':domain')

testImplementation project(':infrastructure')
testRuntimeOnly 'com.h2database:h2'
}

bootJar {
enabled = false
}

tasks.named('test') {
test {
useJUnitPlatform()
finalizedBy jacocoTestReport
}

jacocoTestReport {
dependsOn test

reports {
html.required = true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,6 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Set;

import static com.example.app.booking.exception.BookingErrorMessage.*;

@Slf4j
Expand All @@ -42,7 +39,7 @@ public Booking createBooking(String lockKey, CreateBookingCommand createBookingC
checkValidUser(createBookingCommand.userId());

// 연속된 row 체크
checkSeatsInSequence(createBookingCommand.seats());
TheaterSeat.checkSeatsInSequence(createBookingCommand.seats());

// 기존 예약 조회
var existingBookingIds = loadBookingPort.loadAllBookings(createBookingCommand.toSearchBookingCommand())
Expand All @@ -63,14 +60,14 @@ public Booking createBooking(String lockKey, CreateBookingCommand createBookingC
var requestSeats = loadSeatPort.loadAllSeats(createBookingCommand.toSearchSeatCommand());

// 요청한 자리 예약 가능 여부 체크
checkSeatsAvailable(requestSeats);
Seat.checkSeatsAvailable(requestSeats);

// 요청한 자리들 업데이트
var requestSeatIds = requestSeats.stream().map(Seat::id).toList();
updateSeatPort.updateAllSeats(requestSeatIds, booking.id());

return booking;
}, lockKey, 1L, 2L);
}, lockKey, 1L, 3L);
}

private void checkLimitMaxSeats(final int totalSeat) {
Expand All @@ -79,23 +76,6 @@ private void checkLimitMaxSeats(final int totalSeat) {
}
}

private void checkSeatsAvailable(List<Seat> seats) {
for (Seat seat : seats) {
if (seat.reserved()) {
throw new APIException(SEAT_ALREADY_OCCUPIED);
}
}
}

private void checkSeatsInSequence(Set<TheaterSeat> theaterSeats) {
String firstRow = TheaterSeat.getRow(theaterSeats.iterator().next());
for (TheaterSeat theaterSeat : theaterSeats) {
if (!TheaterSeat.getRow(theaterSeat).equals(firstRow)) {
throw new APIException(SEAT_ROW_NOT_IN_SEQUENCE);
}
}
}

private void checkValidUser(final long userId) {
log.info(">>>>>> Checking userId : {}", userId);
/* pseudo code
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.example.app.booking.service;

import com.example.app.booking.domain.Booking;
import com.example.app.booking.dto.CreateBookingCommand;
import com.example.app.booking.out.persistence.adapter.BookingPersistenceAdapter;
import com.example.app.booking.out.persistence.adapter.SeatPersistenceAdapter;
import com.example.app.common.exception.APIException;
import com.example.app.common.function.DistributedLockService;
import com.example.app.config.EmbeddedRedisConfig;
import com.example.app.movie.type.TheaterSeat;
import com.navercorp.fixturemonkey.FixtureMonkey;
import com.navercorp.fixturemonkey.api.introspector.ConstructorPropertiesArbitraryIntrospector;
import com.navercorp.fixturemonkey.jakarta.validation.plugin.JakartaValidationPlugin;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.TestPropertySource;

import java.time.LocalDate;
import java.util.Set;
import java.util.function.Supplier;

import static com.example.app.booking.exception.BookingErrorMessage.SEAT_ROW_NOT_IN_SEQUENCE;
import static com.navercorp.fixturemonkey.api.instantiator.Instantiator.constructor;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*;

@SpringBootTest(classes = EmbeddedRedisConfig.class)
@TestPropertySource(properties = "spring.config.location = classpath:application-test.yml")
public class CreateBookingServiceTest {

private FixtureMonkey fixtureMonkey;

@Mock
private DistributedLockService distributedLockService;

@Mock
private BookingPersistenceAdapter bookingPersistenceAdapter;

@Mock
private SeatPersistenceAdapter seatPersistenceAdapter;

@InjectMocks
private CreateBookingService sut;

@BeforeEach
void setUp() {
fixtureMonkey = FixtureMonkey.builder()
.objectIntrospector(ConstructorPropertiesArbitraryIntrospector.INSTANCE)
.plugin(new JakartaValidationPlugin())
.build();
}

@Test
public void 예약_테스트() {
var key = fixtureMonkey.giveMeOne(String.class);
var continuousSeats = Set.of(TheaterSeat.A3, TheaterSeat.A4, TheaterSeat.A5);
var bookingCommand = fixtureMonkey.giveMeBuilder(CreateBookingCommand.class)
.instantiate(constructor()
.parameter(long.class)
.parameter(long.class)
.parameter(long.class)
.parameter(long.class)
.parameter(LocalDate.class)
.parameter(Set.class, "seats"))
.set("seats", continuousSeats)
.sample();

var booking = fixtureMonkey.giveMeBuilder(Booking.class)
.instantiate(constructor()
.parameter(long.class)
.parameter(long.class)
.parameter(long.class)
.parameter(long.class)
.parameter(long.class)
.parameter(int.class, "totalSeats")
.parameter(LocalDate.class))
.set("totalSeats", 3)
.sample();

when(distributedLockService.executeWithLockAndReturn(any(Supplier.class), any(String.class), any(Long.class), any(Long.class)))
.thenReturn(booking);

var result = sut.createBooking(key, bookingCommand);

assertEquals(booking, result);
}

@Test
public void 예약_불가_테스트() {
var key = fixtureMonkey.giveMeOne(String.class);
var discontinuousSeats = Set.of(TheaterSeat.B1, TheaterSeat.C1, TheaterSeat.D1);
var bookingCommand = fixtureMonkey.giveMeBuilder(CreateBookingCommand.class)
.instantiate(constructor()
.parameter(long.class)
.parameter(long.class)
.parameter(long.class)
.parameter(long.class)
.parameter(LocalDate.class)
.parameter(Set.class, "seats"))
.set("seats", discontinuousSeats)
.sample();
Copy link
Contributor

Choose a reason for hiding this comment

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

Set.of(TheaterSeat.B1, TheaterSeat.C1, TheaterSeat.D1) 부분을 불연속적인 좌석 을 의미하는 변수로 추출하여 전달하면 유지보수성 및 가독성이 더 좋아질 것 같습니다.


var exception = assertThrows(APIException.class, () -> sut.createBooking(key, bookingCommand));
assertEquals(SEAT_ROW_NOT_IN_SEQUENCE.getMessage(), exception.getMessage());
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.example.app.config;

import jakarta.annotation.PreDestroy;
import jakarta.annotation.PostConstruct;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.test.context.TestConfiguration;
import redis.embedded.RedisServer;

import java.io.IOException;

@TestConfiguration
public class EmbeddedRedisConfig {

private final RedisServer redisServer;

public EmbeddedRedisConfig(RedisProperties redisProperties) throws IOException {
this.redisServer = new RedisServer(redisProperties.getPort());
}

@PostConstruct
public void postConstruct() throws IOException {
redisServer.start();
}

@PreDestroy
public void preDestroy() throws IOException {
redisServer.stop();
}
}
Loading