diff --git a/src/docs/asciidoc/battle.adoc b/src/docs/asciidoc/battle.adoc index eaaea44b..3541d8ca 100644 --- a/src/docs/asciidoc/battle.adoc +++ b/src/docs/asciidoc/battle.adoc @@ -46,3 +46,8 @@ operation::get finished battle[snippets='http-request,http-response'] 싱글 경기에 대한 기록을 저장할 때 operation::create single[snippets='http-request,http-response'] + +=== 싱글 기록 조회 +싱글 경기에 대한 기록을 조회할 때 + +operation::get single[snippets='http-request,http-response'] diff --git a/src/main/java/online/partyrun/partyrunbattleservice/domain/single/controller/SingleController.java b/src/main/java/online/partyrun/partyrunbattleservice/domain/single/controller/SingleController.java index 118efa91..2cc1366b 100644 --- a/src/main/java/online/partyrun/partyrunbattleservice/domain/single/controller/SingleController.java +++ b/src/main/java/online/partyrun/partyrunbattleservice/domain/single/controller/SingleController.java @@ -5,6 +5,7 @@ import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; import online.partyrun.partyrunbattleservice.domain.single.dto.SingleIdResponse; +import online.partyrun.partyrunbattleservice.domain.single.dto.SingleResponse; import online.partyrun.partyrunbattleservice.domain.single.dto.SingleRunnerRecordsRequest; import online.partyrun.partyrunbattleservice.domain.single.service.SingleService; import online.partyrun.partyrunbattleservice.global.logging.Logging; @@ -27,4 +28,11 @@ public SingleIdResponse createSingle(Authentication auth, @RequestBody @Valid Si final String runnerId = auth.getName(); return singleService.create(runnerId, request); } + + @GetMapping("{singleId}") + @ResponseStatus(HttpStatus.OK) + public SingleResponse getSingle(Authentication auth, @PathVariable String singleId) { + final String runnerId = auth.getName(); + return singleService.getSingle(singleId, runnerId); + } } diff --git a/src/main/java/online/partyrun/partyrunbattleservice/domain/single/dto/RunningTimeResponse.java b/src/main/java/online/partyrun/partyrunbattleservice/domain/single/dto/RunningTimeResponse.java new file mode 100644 index 00000000..aeda27e4 --- /dev/null +++ b/src/main/java/online/partyrun/partyrunbattleservice/domain/single/dto/RunningTimeResponse.java @@ -0,0 +1,10 @@ +package online.partyrun.partyrunbattleservice.domain.single.dto; + +import online.partyrun.partyrunbattleservice.domain.single.entity.RunningTime; + +public record RunningTimeResponse(int hours, int minutes, int seconds) { + + public RunningTimeResponse(RunningTime runningTime) { + this(runningTime.getHours(), runningTime.getMinutes(), runningTime.getSeconds()); + } +} diff --git a/src/main/java/online/partyrun/partyrunbattleservice/domain/single/dto/SingleResponse.java b/src/main/java/online/partyrun/partyrunbattleservice/domain/single/dto/SingleResponse.java new file mode 100644 index 00000000..da885810 --- /dev/null +++ b/src/main/java/online/partyrun/partyrunbattleservice/domain/single/dto/SingleResponse.java @@ -0,0 +1,13 @@ +package online.partyrun.partyrunbattleservice.domain.single.dto; + +import online.partyrun.partyrunbattleservice.domain.runner.dto.RunnerRecordResponse; +import online.partyrun.partyrunbattleservice.domain.single.entity.Single; + +import java.util.List; + +public record SingleResponse(RunningTimeResponse runningTime, List records) { + + public SingleResponse(Single single) { + this(new RunningTimeResponse(single.getRunningTime()), single.getRunnerRecords().stream().map(RunnerRecordResponse::new).toList()); + } +} diff --git a/src/main/java/online/partyrun/partyrunbattleservice/domain/single/entity/Single.java b/src/main/java/online/partyrun/partyrunbattleservice/domain/single/entity/Single.java index f9c246a6..cb24f8d9 100644 --- a/src/main/java/online/partyrun/partyrunbattleservice/domain/single/entity/Single.java +++ b/src/main/java/online/partyrun/partyrunbattleservice/domain/single/entity/Single.java @@ -34,4 +34,8 @@ private void validateRunnerRecords(List runnerRecords) { throw new SingleRunnerRecordEmptyException(); } } + + public boolean isOwner(String runnerId) { + return this.runnerId.equals(runnerId); + } } diff --git a/src/main/java/online/partyrun/partyrunbattleservice/domain/single/exception/InvalidSingleOwnerException.java b/src/main/java/online/partyrun/partyrunbattleservice/domain/single/exception/InvalidSingleOwnerException.java new file mode 100644 index 00000000..dd5f29d0 --- /dev/null +++ b/src/main/java/online/partyrun/partyrunbattleservice/domain/single/exception/InvalidSingleOwnerException.java @@ -0,0 +1,9 @@ +package online.partyrun.partyrunbattleservice.domain.single.exception; + +import online.partyrun.partyrunbattleservice.global.exception.BadRequestException; + +public class InvalidSingleOwnerException extends BadRequestException { + public InvalidSingleOwnerException(String singleId, String runnerId) { + super(String.format("%s 러너는 %s 싱글 기록의 주인이 아닙니다.", runnerId, singleId)); + } +} diff --git a/src/main/java/online/partyrun/partyrunbattleservice/domain/single/exception/SingleNotFoundException.java b/src/main/java/online/partyrun/partyrunbattleservice/domain/single/exception/SingleNotFoundException.java new file mode 100644 index 00000000..e2b9c211 --- /dev/null +++ b/src/main/java/online/partyrun/partyrunbattleservice/domain/single/exception/SingleNotFoundException.java @@ -0,0 +1,10 @@ +package online.partyrun.partyrunbattleservice.domain.single.exception; + +import online.partyrun.partyrunbattleservice.global.exception.NotFoundException; + +public class SingleNotFoundException extends NotFoundException { + + public SingleNotFoundException(String singleId) { + super(String.format("%s 의 싱글 기록은 존재하지 않습니다.", singleId)); + } +} diff --git a/src/main/java/online/partyrun/partyrunbattleservice/domain/single/service/SingleService.java b/src/main/java/online/partyrun/partyrunbattleservice/domain/single/service/SingleService.java index b98283bd..7ad94c8b 100644 --- a/src/main/java/online/partyrun/partyrunbattleservice/domain/single/service/SingleService.java +++ b/src/main/java/online/partyrun/partyrunbattleservice/domain/single/service/SingleService.java @@ -5,9 +5,12 @@ import lombok.experimental.FieldDefaults; import online.partyrun.partyrunbattleservice.domain.runner.entity.record.RunnerRecord; import online.partyrun.partyrunbattleservice.domain.single.dto.SingleIdResponse; +import online.partyrun.partyrunbattleservice.domain.single.dto.SingleResponse; import online.partyrun.partyrunbattleservice.domain.single.dto.SingleRunnerRecordsRequest; import online.partyrun.partyrunbattleservice.domain.single.entity.RunningTime; import online.partyrun.partyrunbattleservice.domain.single.entity.Single; +import online.partyrun.partyrunbattleservice.domain.single.exception.InvalidSingleOwnerException; +import online.partyrun.partyrunbattleservice.domain.single.exception.SingleNotFoundException; import online.partyrun.partyrunbattleservice.domain.single.repository.SingleRepository; import org.springframework.stereotype.Service; @@ -25,4 +28,13 @@ public SingleIdResponse create(String runnerId, SingleRunnerRecordsRequest reque final Single newSingleRecord = singleRepository.save(new Single(runnerId, runningTime, records)); return new SingleIdResponse(newSingleRecord.getId()); } + + public SingleResponse getSingle(String singleId, String runnerId) { + final Single single = singleRepository.findById(singleId).orElseThrow(() -> new SingleNotFoundException(singleId)); + if (!single.isOwner(runnerId)) { + throw new InvalidSingleOwnerException(singleId, runnerId); + } + + return new SingleResponse(single); + } } diff --git a/src/test/java/online/partyrun/partyrunbattleservice/domain/battle/service/BattleServiceTest.java b/src/test/java/online/partyrun/partyrunbattleservice/domain/battle/service/BattleServiceTest.java index 1e236286..eba316e4 100644 --- a/src/test/java/online/partyrun/partyrunbattleservice/domain/battle/service/BattleServiceTest.java +++ b/src/test/java/online/partyrun/partyrunbattleservice/domain/battle/service/BattleServiceTest.java @@ -1,7 +1,5 @@ package online.partyrun.partyrunbattleservice.domain.battle.service; -import online.partyrun.partyrunbattleservice.domain.battle.config.TestApplicationContextConfig; -import online.partyrun.partyrunbattleservice.domain.battle.config.TestTimeConfig; import online.partyrun.partyrunbattleservice.domain.battle.dto.*; import online.partyrun.partyrunbattleservice.domain.battle.entity.Battle; import online.partyrun.partyrunbattleservice.domain.battle.event.BattleRunningEvent; @@ -14,12 +12,10 @@ import online.partyrun.partyrunbattleservice.domain.runner.entity.Runner; import online.partyrun.partyrunbattleservice.domain.runner.entity.RunnerStatus; import online.partyrun.partyrunbattleservice.domain.runner.entity.record.GpsData; -import online.partyrun.testmanager.redis.EnableRedisTest; +import online.partyrun.partyrunbattleservice.global.ServiceTest; import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.annotation.Import; import org.springframework.data.mongodb.core.MongoTemplate; import java.time.Clock; @@ -34,11 +30,8 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; -@SpringBootTest -@EnableRedisTest -@Import({TestApplicationContextConfig.class, TestTimeConfig.class}) @DisplayName("BattleService") -class BattleServiceTest { +class BattleServiceTest extends ServiceTest { @Autowired BattleService battleService; @Autowired BattleRepository battleRepository; diff --git a/src/test/java/online/partyrun/partyrunbattleservice/domain/single/controller/SingleControllerTest.java b/src/test/java/online/partyrun/partyrunbattleservice/domain/single/controller/SingleControllerTest.java index 0657949b..75f48c51 100644 --- a/src/test/java/online/partyrun/partyrunbattleservice/domain/single/controller/SingleControllerTest.java +++ b/src/test/java/online/partyrun/partyrunbattleservice/domain/single/controller/SingleControllerTest.java @@ -1,9 +1,7 @@ package online.partyrun.partyrunbattleservice.domain.single.controller; -import online.partyrun.partyrunbattleservice.domain.single.dto.RunningTimeRequest; -import online.partyrun.partyrunbattleservice.domain.single.dto.SingleIdResponse; -import online.partyrun.partyrunbattleservice.domain.single.dto.SingleRunnerRecordRequest; -import online.partyrun.partyrunbattleservice.domain.single.dto.SingleRunnerRecordsRequest; +import online.partyrun.partyrunbattleservice.domain.runner.dto.RunnerRecordResponse; +import online.partyrun.partyrunbattleservice.domain.single.dto.*; import online.partyrun.partyrunbattleservice.domain.single.service.SingleService; import online.partyrun.testmanager.docs.RestControllerTest; import org.junit.jupiter.api.*; @@ -18,6 +16,7 @@ import static org.mockito.BDDMockito.given; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -44,7 +43,7 @@ class 러너의_기록이_주어지면 { @DisplayName("싱글을_생성한다") void createSingle() throws Exception { SingleRunnerRecordsRequest request = new SingleRunnerRecordsRequest( - new RunningTimeRequest(0,0,1), + new RunningTimeRequest(0, 0, 1), List.of( new SingleRunnerRecordRequest(0, 0, 0, now, 0), new SingleRunnerRecordRequest(0.0001, 0.0001, 0.0001, now, 1) @@ -71,4 +70,30 @@ void createSingle() throws Exception { } } } + + @Test + @DisplayName("싱글을 조회한다") + void getSingle() throws Exception { + + final String singleId = "singleId"; + final SingleResponse response = new SingleResponse(new RunningTimeResponse(1, 1, 1), List.of(new RunnerRecordResponse(0, 0, 0, LocalDateTime.now(), 0))); + + given(singleService.getSingle(singleId, "defaultUser")).willReturn(response); + + final ResultActions actions = + mockMvc.perform( + get(String.format("%s/%s", SINGLE_URL, singleId)) + .with(csrf()) + .header( + "Authorization", + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c") + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(StandardCharsets.UTF_8)); + + actions.andExpect(status().isOk()) + .andExpect(content().json(toRequestBody(response))); + + setPrintDocs(actions, "get single"); + } } + diff --git a/src/test/java/online/partyrun/partyrunbattleservice/domain/single/entity/SingleTest.java b/src/test/java/online/partyrun/partyrunbattleservice/domain/single/entity/SingleTest.java index 9e7bc0a6..c351a07c 100644 --- a/src/test/java/online/partyrun/partyrunbattleservice/domain/single/entity/SingleTest.java +++ b/src/test/java/online/partyrun/partyrunbattleservice/domain/single/entity/SingleTest.java @@ -1,16 +1,16 @@ package online.partyrun.partyrunbattleservice.domain.single.entity; +import online.partyrun.partyrunbattleservice.domain.runner.entity.record.GpsData; import online.partyrun.partyrunbattleservice.domain.runner.entity.record.RunnerRecord; import online.partyrun.partyrunbattleservice.domain.single.exception.SingleRunnerRecordEmptyException; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.DisplayNameGeneration; -import org.junit.jupiter.api.DisplayNameGenerator; -import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.*; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.NullAndEmptySource; +import java.time.LocalDateTime; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @DisplayName("Single") @@ -28,4 +28,25 @@ void throwException(List records) { .isInstanceOf(SingleRunnerRecordEmptyException.class); } } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class Single_기록의_주인을_판단할_때 { + + final String runnerId = "runnerId"; + + @Test + @DisplayName("주인이라면 true를 반환한다.") + void returnTrue() { + final Single single = new Single(runnerId, new RunningTime(1, 1, 1), List.of(new RunnerRecord(GpsData.of(1, 1, 1, LocalDateTime.now()), 0))); + assertThat(single.isOwner(runnerId)).isTrue(); + } + + @Test + @DisplayName("주인이 아니라면 false를 반환한다.") + void returnFalse() { + final Single single = new Single(runnerId, new RunningTime(1, 1, 1), List.of(new RunnerRecord(GpsData.of(1, 1, 1, LocalDateTime.now()), 0))); + assertThat(single.isOwner("notOwner")).isFalse(); + } + } } diff --git a/src/test/java/online/partyrun/partyrunbattleservice/domain/single/service/SingleServiceTest.java b/src/test/java/online/partyrun/partyrunbattleservice/domain/single/service/SingleServiceTest.java index 08105958..62178c75 100644 --- a/src/test/java/online/partyrun/partyrunbattleservice/domain/single/service/SingleServiceTest.java +++ b/src/test/java/online/partyrun/partyrunbattleservice/domain/single/service/SingleServiceTest.java @@ -1,23 +1,24 @@ package online.partyrun.partyrunbattleservice.domain.single.service; -import online.partyrun.partyrunbattleservice.domain.single.dto.RunningTimeRequest; -import online.partyrun.partyrunbattleservice.domain.single.dto.SingleIdResponse; -import online.partyrun.partyrunbattleservice.domain.single.dto.SingleRunnerRecordRequest; -import online.partyrun.partyrunbattleservice.domain.single.dto.SingleRunnerRecordsRequest; +import online.partyrun.partyrunbattleservice.domain.runner.entity.Runner; +import online.partyrun.partyrunbattleservice.domain.single.dto.*; +import online.partyrun.partyrunbattleservice.domain.single.exception.InvalidSingleOwnerException; +import online.partyrun.partyrunbattleservice.domain.single.exception.SingleNotFoundException; import online.partyrun.partyrunbattleservice.domain.single.repository.SingleRepository; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +import online.partyrun.partyrunbattleservice.global.ServiceTest; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.*; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; import java.time.LocalDateTime; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; -@SpringBootTest @DisplayName("SingleService") -class SingleServiceTest { +class SingleServiceTest extends ServiceTest { @Autowired SingleService singleService; @@ -42,4 +43,48 @@ void create() { assertThat(response.id()).isNotBlank(); } + + @Nested + @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) + class 싱글_기록을_조회할_때 { + String singleId; + + @BeforeEach + void setUp() { + singleId = singleService.create( + runnerId, + new SingleRunnerRecordsRequest( + new RunningTimeRequest(1,1,1), + List.of( + new SingleRunnerRecordRequest(0, 0, 0, now, 0), + new SingleRunnerRecordRequest(0.0001, 0.0001, 0.0001, now, 1) + ) + ) + ).id(); + } + + @Test + @DisplayName("기록의 주인이 아니라면 예외를 던진다.") + void throwNotOwnerException() { + assertThatThrownBy(() -> singleService.getSingle(singleId, "notOwnerId")) + .isInstanceOf(InvalidSingleOwnerException.class); + } + + @Test + @DisplayName("기록이 존재하지 않으면 예외를 던진다.") + void throwNotFoundException() { + assertThatThrownBy(() -> singleService.getSingle("invalid" + singleId, runnerId)) + .isInstanceOf(SingleNotFoundException.class); + } + + @Test + @DisplayName("싱글 기록을 조회한다.") + void getSingle() { + final SingleResponse response = singleService.getSingle(singleId, runnerId); + assertAll( + () -> assertThat(response.runningTime()).isEqualTo(new RunningTimeResponse(1, 1, 1)), + () -> assertThat(response.records()).hasSize(2) + ); + } + } } diff --git a/src/test/java/online/partyrun/partyrunbattleservice/global/ServiceTest.java b/src/test/java/online/partyrun/partyrunbattleservice/global/ServiceTest.java new file mode 100644 index 00000000..b087eecf --- /dev/null +++ b/src/test/java/online/partyrun/partyrunbattleservice/global/ServiceTest.java @@ -0,0 +1,13 @@ +package online.partyrun.partyrunbattleservice.global; + +import online.partyrun.partyrunbattleservice.domain.battle.config.TestApplicationContextConfig; +import online.partyrun.partyrunbattleservice.domain.battle.config.TestTimeConfig; +import online.partyrun.testmanager.redis.EnableRedisTest; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Import; + +@SpringBootTest +@EnableRedisTest +@Import({TestApplicationContextConfig.class, TestTimeConfig.class}) +public abstract class ServiceTest { +}