diff --git a/src/main/java/org/runimo/runimo/rewards/service/lovepoint/LoveGrantService.java b/src/main/java/org/runimo/runimo/rewards/service/lovepoint/LoveGrantService.java index b49fb2e3..75a23a64 100644 --- a/src/main/java/org/runimo/runimo/rewards/service/lovepoint/LoveGrantService.java +++ b/src/main/java/org/runimo/runimo/rewards/service/lovepoint/LoveGrantService.java @@ -23,7 +23,7 @@ public Long grantLoveToUserWithDistance(RunningRecord runningRecord) { User user = userFinder.findUserById(runningRecord.getUserId()) .orElseThrow(IllegalStateException::new); Long loveAmount = calculateLoveAmount(runningRecord.getTotalDistance()); - LovePoint lovePoint = lovePointProcessor.updateLovePoint(user.getId(), loveAmount); + LovePoint lovePoint = lovePointProcessor.acquireLovePoint(user.getId(), loveAmount); return lovePoint.getAmount(); } diff --git a/src/main/java/org/runimo/runimo/user/service/LovePointProcessor.java b/src/main/java/org/runimo/runimo/user/service/LovePointProcessor.java index c49485d0..162e5304 100644 --- a/src/main/java/org/runimo/runimo/user/service/LovePointProcessor.java +++ b/src/main/java/org/runimo/runimo/user/service/LovePointProcessor.java @@ -19,10 +19,18 @@ public LovePointProcessor(LovePointRepository lovePointRepository) { * 유저의 러브포인트를 업데이트한다. XLOCK을 걸어서 동시성 문제를 해결한다. * */ @Transactional - public LovePoint updateLovePoint(Long userId, Long loveAmount) { + public LovePoint acquireLovePoint(Long userId, Long loveAmount) { LovePoint lp = lovePointRepository.findByUserIdWithXLock(userId) .orElseThrow(IllegalStateException::new); lp.add(loveAmount); return lovePointRepository.save(lp); } + + @Transactional + public LovePoint useLovePoint(Long userId, Long loveAmount) { + LovePoint lp = lovePointRepository.findByUserIdWithXLock(userId) + .orElseThrow(IllegalStateException::new); + lp.subtract(loveAmount); + return lovePointRepository.save(lp); + } } diff --git a/src/main/java/org/runimo/runimo/user/service/usecases/eggs/GiveLovePointToEggUsecaseImpl.java b/src/main/java/org/runimo/runimo/user/service/usecases/eggs/GiveLovePointToEggUsecaseImpl.java index bef35765..5f123b55 100644 --- a/src/main/java/org/runimo/runimo/user/service/usecases/eggs/GiveLovePointToEggUsecaseImpl.java +++ b/src/main/java/org/runimo/runimo/user/service/usecases/eggs/GiveLovePointToEggUsecaseImpl.java @@ -19,7 +19,7 @@ public class GiveLovePointToEggUsecaseImpl implements GiveLovePointToEggUsecase @Transactional public UseLovePointResponse execute(UseLovePointCommand command) { IncubatingEgg incubatingEgg = incubatingEggProcessor.giveLovePoint(command); - lovePointProcessor.updateLovePoint(command.userId(), command.lovePoint()); + lovePointProcessor.useLovePoint(command.userId(), command.lovePoint()); return new UseLovePointResponse( incubatingEgg.getId(), incubatingEgg.getCurrentLovePointAmount(), diff --git a/src/test/java/org/runimo/runimo/user/api/IncubatingEggAcceptanceTest.java b/src/test/java/org/runimo/runimo/user/api/IncubatingEggAcceptanceTest.java index 269603b9..233689db 100644 --- a/src/test/java/org/runimo/runimo/user/api/IncubatingEggAcceptanceTest.java +++ b/src/test/java/org/runimo/runimo/user/api/IncubatingEggAcceptanceTest.java @@ -18,11 +18,9 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.jdbc.Sql; -import java.util.Optional; import static io.restassured.RestAssured.given; import static org.hamcrest.Matchers.*; -import static org.junit.jupiter.api.Assertions.assertEquals; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @ActiveProfiles("test") @@ -111,4 +109,135 @@ void tearDown() { .body("payload.required_love_point_amount", equalTo(100)) .body("payload.egg_hatchable", equalTo(false)); } + + @Test + @Sql(scripts = "/sql/incubating_egg_test_data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void 알에_애정을_부여하면_사용자의_보유_애정이_감소한다() throws JsonProcessingException { + // given + String userUuid = "test-user-uuid-1"; + String token = "Bearer " + jwtTokenFactory.generateAccessToken(userUuid); + + // 사용자의 초기 애정 포인트 조회 + Integer initialLovePoint = given() + .header("Authorization", token) + .contentType(ContentType.JSON) + .when() + .get("/api/v1/main") + .then() + .statusCode(200) + .extract() + .path("payload.love_point"); + + // when + // 20포인트의 애정을 부여 + Long useLovePointAmount = 20L; + UseLovePointRequest request = new UseLovePointRequest(1L, useLovePointAmount); + + // 알에 애정 부여 요청 + given() + .header("Authorization", token) + .contentType(ContentType.JSON) + .body(objectMapper.writeValueAsString(request)) + .when() + .patch("/api/v1/users/eggs") + .then() + .log().all() + .statusCode(200) + .body("payload.current_love_point_amount", equalTo(70)); + + // then + // 사용자의 애정 포인트가 감소했는지 확인 + given() + .header("Authorization", token) + .contentType(ContentType.JSON) + .when() + .get("/api/v1/main") + .then() + .log().all() + .statusCode(200) + .body("payload.love_point", equalTo(initialLovePoint - useLovePointAmount.intValue())); + } + + + @Test + @Sql(scripts = "/sql/incubating_egg_test_data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void 보유한_애정보다_더_많은_애정부여_요청_시_예외() throws JsonProcessingException { + // given + String userUuid = "test-user-uuid-1"; + String token = "Bearer " + jwtTokenFactory.generateAccessToken(userUuid); + + // 사용자의 초기 애정 포인트 조회 + given() + .header("Authorization", token) + .contentType(ContentType.JSON) + .when() + .get("/api/v1/main") + .then() + .statusCode(200) + .extract() + .path("payload.love_point"); + + // when + // 20포인트의 애정을 부여 + Long useLovePointAmount = 25L; + UseLovePointRequest request = new UseLovePointRequest(1L, useLovePointAmount); + + // 알에 애정 부여 요청 + given() + .header("Authorization", token) + .contentType(ContentType.JSON) + .body(objectMapper.writeValueAsString(request)) + .when() + .patch("/api/v1/users/eggs") + .then() + .log().all() + .statusCode(400); + } + + @Test + @Sql(scripts = "/sql/incubating_egg_test_data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + void 애정부여_예외_시_애정보유량_유지() throws JsonProcessingException { + // given + String userUuid = "test-user-uuid-1"; + String token = "Bearer " + jwtTokenFactory.generateAccessToken(userUuid); + + // 사용자의 초기 애정 포인트 조회 + Integer initialLovePoint = given() + .header("Authorization", token) + .contentType(ContentType.JSON) + .when() + .get("/api/v1/main") + .then() + .statusCode(200) + .extract() + .path("payload.love_point"); + + // when + // 20포인트의 애정을 부여 + Long useLovePointAmount = 25L; + UseLovePointRequest request = new UseLovePointRequest(1L, useLovePointAmount); + + // 알에 애정 부여 요청 + given() + .header("Authorization", token) + .contentType(ContentType.JSON) + .body(objectMapper.writeValueAsString(request)) + .when() + .patch("/api/v1/users/eggs") + .then() + .log().all() + .statusCode(400); + + // then + // 사용자의 애정 포인트가 감소했는지 확인 + given() + .header("Authorization", token) + .contentType(ContentType.JSON) + .when() + .get("/api/v1/main") + .then() + .log().all() + .statusCode(200) + .body("payload.love_point", equalTo(initialLovePoint)); + } } diff --git a/src/test/resources/sql/incubating_egg_test_data.sql b/src/test/resources/sql/incubating_egg_test_data.sql index 89f5ea52..5fb3fed2 100644 --- a/src/test/resources/sql/incubating_egg_test_data.sql +++ b/src/test/resources/sql/incubating_egg_test_data.sql @@ -11,7 +11,7 @@ INSERT INTO users (id, public_id, nickname, img_url, total_distance_in_meters, t VALUES (1, 'test-user-uuid-1', 'Daniel', 'https://example.com/images/user1.png', 3000, 3600, NOW(), NOW()); INSERT INTO user_love_point (id, user_id, amount, created_at, updated_at) -VALUES (1, 1, 1000, NOW(), NOW()); +VALUES (1, 1, 20, NOW(), NOW()); -- 보유 아이템 INSERT INTO user_item (id, user_id, item_id, quantity, created_at, updated_at)