Skip to content

Commit

Permalink
싱글 결과 조회 API 구현 (#61)
Browse files Browse the repository at this point in the history
* feat: 싱글 조회 로직 구현

* feat: 싱글 조회 관련 로직 테스트 추가

* feat: 싱글 조회 adoc 파일에 추가
  • Loading branch information
seong-wooo authored Sep 18, 2023
1 parent e9e206a commit e44252a
Show file tree
Hide file tree
Showing 13 changed files with 195 additions and 27 deletions.
5 changes: 5 additions & 0 deletions src/docs/asciidoc/battle.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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<RunnerRecordResponse> records) {

public SingleResponse(Single single) {
this(new RunningTimeResponse(single.getRunningTime()), single.getRunnerRecords().stream().map(RunnerRecordResponse::new).toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,8 @@ private void validateRunnerRecords(List<RunnerRecord> runnerRecords) {
throw new SingleRunnerRecordEmptyException();
}
}

public boolean isOwner(String runnerId) {
return this.runnerId.equals(runnerId);
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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.*;
Expand All @@ -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;
Expand All @@ -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)
Expand All @@ -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");
}
}

Original file line number Diff line number Diff line change
@@ -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")
Expand All @@ -28,4 +28,25 @@ void throwException(List<RunnerRecord> 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();
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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)
);
}
}
}
Original file line number Diff line number Diff line change
@@ -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 {
}

0 comments on commit e44252a

Please sign in to comment.