From 588da5ef963bf41c0ecd46cc47f6dd3f8acad97c Mon Sep 17 00:00:00 2001 From: Hyeonjun Park Date: Wed, 20 Sep 2023 14:09:02 +0900 Subject: [PATCH 1/4] =?UTF-8?q?feat=20:=20Party=20=EC=B4=88=EB=8C=80=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../party/controller/PartyController.java | 47 +++++++++++ .../domain/party/dto/PartyEvent.java | 12 +++ .../domain/party/dto/PartyIdResponse.java | 9 +++ .../domain/party/dto/PartyRequest.java | 4 + .../domain/party/entity/EntryCode.java | 26 ++++++ .../domain/party/entity/Party.java | 41 ++++++++++ .../domain/party/entity/PartyStatus.java | 5 ++ .../party/repository/PartyRepository.java | 11 +++ .../domain/party/service/PartyService.java | 81 +++++++++++++++++++ .../party/service/PartySinkHandler.java | 9 +++ 10 files changed, 245 insertions(+) create mode 100644 src/main/java/online/partyrun/partyrunmatchingservice/domain/party/controller/PartyController.java create mode 100644 src/main/java/online/partyrun/partyrunmatchingservice/domain/party/dto/PartyEvent.java create mode 100644 src/main/java/online/partyrun/partyrunmatchingservice/domain/party/dto/PartyIdResponse.java create mode 100644 src/main/java/online/partyrun/partyrunmatchingservice/domain/party/dto/PartyRequest.java create mode 100644 src/main/java/online/partyrun/partyrunmatchingservice/domain/party/entity/EntryCode.java create mode 100644 src/main/java/online/partyrun/partyrunmatchingservice/domain/party/entity/Party.java create mode 100644 src/main/java/online/partyrun/partyrunmatchingservice/domain/party/entity/PartyStatus.java create mode 100644 src/main/java/online/partyrun/partyrunmatchingservice/domain/party/repository/PartyRepository.java create mode 100644 src/main/java/online/partyrun/partyrunmatchingservice/domain/party/service/PartyService.java create mode 100644 src/main/java/online/partyrun/partyrunmatchingservice/domain/party/service/PartySinkHandler.java diff --git a/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/controller/PartyController.java b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/controller/PartyController.java new file mode 100644 index 0000000..48757f6 --- /dev/null +++ b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/controller/PartyController.java @@ -0,0 +1,47 @@ +package online.partyrun.partyrunmatchingservice.domain.party.controller; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import online.partyrun.partyrunmatchingservice.domain.party.dto.PartyEvent; +import online.partyrun.partyrunmatchingservice.domain.party.dto.PartyIdResponse; +import online.partyrun.partyrunmatchingservice.domain.party.dto.PartyRequest; +import online.partyrun.partyrunmatchingservice.domain.party.service.PartyService; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.security.Principal; + +@RestController +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@RequestMapping("parties") +public class PartyController { + PartyService partyService; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public Mono postParties(Mono auth, @RequestBody PartyRequest request) { + return partyService.create(auth.map(Principal::getName), request); + } + + @GetMapping(path = "{entryCode}/join", produces = "text/event-stream") + public Flux getPartyEventStream(Mono auth, @PathVariable String entryCode) { + return partyService.getEventStream(auth.map(Principal::getName), entryCode); + } + + @PostMapping("{entryCode}/start") + @ResponseStatus(HttpStatus.NO_CONTENT) + public Mono postPartyStart(Mono auth, @PathVariable String entryCode) { + return partyService.start(auth.map(Principal::getName), entryCode); + } + + @PostMapping("{entryCode}/quit") + @ResponseStatus(HttpStatus.NO_CONTENT) + public Mono postPartyQuit(Mono auth, @PathVariable String entryCode) { + return partyService.quit(auth.map(Principal::getName), entryCode); + } +} diff --git a/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/dto/PartyEvent.java b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/dto/PartyEvent.java new file mode 100644 index 0000000..da37eb5 --- /dev/null +++ b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/dto/PartyEvent.java @@ -0,0 +1,12 @@ +package online.partyrun.partyrunmatchingservice.domain.party.dto; + +import online.partyrun.partyrunmatchingservice.domain.party.entity.Party; +import online.partyrun.partyrunmatchingservice.domain.party.entity.PartyStatus; + +import java.util.List; + +public record PartyEvent(String entryCode, String leaderId, PartyStatus status, List participants, String battleId) { + public PartyEvent(Party party) { + this(party.getEntryCode().getCode(), party.getLeaderId(), party.getStatus(), party.getParticipants(), party.getBattleId()); + } +} diff --git a/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/dto/PartyIdResponse.java b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/dto/PartyIdResponse.java new file mode 100644 index 0000000..8b92efb --- /dev/null +++ b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/dto/PartyIdResponse.java @@ -0,0 +1,9 @@ +package online.partyrun.partyrunmatchingservice.domain.party.dto; + +import online.partyrun.partyrunmatchingservice.domain.party.entity.Party; + +public record PartyIdResponse(String code) { + public PartyIdResponse(Party party) { + this(party.getEntryCode().getCode()); + } +} diff --git a/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/dto/PartyRequest.java b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/dto/PartyRequest.java new file mode 100644 index 0000000..730e8ab --- /dev/null +++ b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/dto/PartyRequest.java @@ -0,0 +1,4 @@ +package online.partyrun.partyrunmatchingservice.domain.party.dto; + +public record PartyRequest(int distance) { +} diff --git a/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/entity/EntryCode.java b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/entity/EntryCode.java new file mode 100644 index 0000000..f0d8e0e --- /dev/null +++ b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/entity/EntryCode.java @@ -0,0 +1,26 @@ +package online.partyrun.partyrunmatchingservice.domain.party.entity; + + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import java.util.concurrent.ThreadLocalRandom; + +@Getter +@AllArgsConstructor +@EqualsAndHashCode +public class EntryCode { + private static int MIN_RANDOM_NUMBER = 100_000; + private static int MAX_RANDOM_NUMBER = 999_999; + + private String code; + + public EntryCode() { + this.code = generateRandomRoomId(); + } + + private String generateRandomRoomId() { + return String.valueOf(ThreadLocalRandom.current().nextInt(MIN_RANDOM_NUMBER, MAX_RANDOM_NUMBER + 1)); + } +} diff --git a/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/entity/Party.java b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/entity/Party.java new file mode 100644 index 0000000..c38aa09 --- /dev/null +++ b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/entity/Party.java @@ -0,0 +1,41 @@ +package online.partyrun.partyrunmatchingservice.domain.party.entity; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.FieldDefaults; +import online.partyrun.partyrunmatchingservice.domain.waiting.root.RunningDistance; +import org.springframework.data.annotation.Id; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@FieldDefaults(level = AccessLevel.PRIVATE) +public class Party { + @Id + String id; + EntryCode entryCode = new EntryCode(); + String leaderId; + RunningDistance distance; + + PartyStatus status = PartyStatus.WAITING; + List participants = new ArrayList<>(); + String battleId; + + public Party(String leaderId, RunningDistance distance) { + this.leaderId = leaderId; + this.distance = distance; + } + + public void join(String memberId) { + participants.add(memberId); + } + + + public void start(String battleId) { + this.battleId = battleId; + status = PartyStatus.COMPLETED; + } +} diff --git a/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/entity/PartyStatus.java b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/entity/PartyStatus.java new file mode 100644 index 0000000..1cfd85c --- /dev/null +++ b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/entity/PartyStatus.java @@ -0,0 +1,5 @@ +package online.partyrun.partyrunmatchingservice.domain.party.entity; + +public enum PartyStatus { + WAITING, COMPLETED +} diff --git a/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/repository/PartyRepository.java b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/repository/PartyRepository.java new file mode 100644 index 0000000..ba9d30a --- /dev/null +++ b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/repository/PartyRepository.java @@ -0,0 +1,11 @@ +package online.partyrun.partyrunmatchingservice.domain.party.repository; + +import online.partyrun.partyrunmatchingservice.domain.party.entity.EntryCode; +import online.partyrun.partyrunmatchingservice.domain.party.entity.Party; +import online.partyrun.partyrunmatchingservice.domain.party.entity.PartyStatus; +import org.springframework.data.mongodb.repository.ReactiveMongoRepository; +import reactor.core.publisher.Mono; + +public interface PartyRepository extends ReactiveMongoRepository { + Mono findByEntryCodeAndStatus(EntryCode code, PartyStatus status); +} diff --git a/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/service/PartyService.java b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/service/PartyService.java new file mode 100644 index 0000000..6b553a4 --- /dev/null +++ b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/service/PartyService.java @@ -0,0 +1,81 @@ +package online.partyrun.partyrunmatchingservice.domain.party.service; + +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import online.partyrun.partyrunmatchingservice.domain.battle.service.BattleService; +import online.partyrun.partyrunmatchingservice.domain.party.dto.PartyEvent; +import online.partyrun.partyrunmatchingservice.domain.party.dto.PartyIdResponse; +import online.partyrun.partyrunmatchingservice.domain.party.dto.PartyRequest; +import online.partyrun.partyrunmatchingservice.domain.party.entity.EntryCode; +import online.partyrun.partyrunmatchingservice.domain.party.entity.Party; +import online.partyrun.partyrunmatchingservice.domain.party.entity.PartyStatus; +import online.partyrun.partyrunmatchingservice.domain.party.repository.PartyRepository; +import online.partyrun.partyrunmatchingservice.domain.waiting.root.RunningDistance; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +@Service +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@RequiredArgsConstructor +public class PartyService { + PartyRepository partyRepository; + PartySinkHandler partySinkHandler; + BattleService battleService; + + public Mono create(Mono member, PartyRequest request) { + return member.map(memberId -> + new Party(memberId, RunningDistance.getBy(request.distance()))) + .flatMap(partyRepository::save) + .map(PartyIdResponse::new); + } + + public Flux getEventStream(Mono member, String entryCode) { + return member + .doOnNext(partySinkHandler::create) + .flatMap(memberId -> joinParty(memberId, entryCode)) + .then(member) + .flatMapMany(partySinkHandler::connect); + } + + private Mono joinParty(String memberId, String entryCode) { + return getWaitingParty(entryCode) + .flatMap(party -> { + party.join(memberId); + return partyRepository.save(party); + } + ) + .then(multicast(entryCode)); + } + + private Mono multicast(String code) { + return getWaitingParty(code) + .doOnNext(party -> party.getParticipants().forEach( + member -> partySinkHandler.sendEvent(member, new PartyEvent(party)) + )).then(); + } + + private Mono getWaitingParty(String code) { + return partyRepository.findByEntryCodeAndStatus(new EntryCode(code), PartyStatus.WAITING); + } + + public Mono start(Mono member, String code) { + // TODO 방장 여부 확인 + return getWaitingParty(code).flatMap(party -> + battleService.create(party.getParticipants(), party.getDistance().getMeter()) + ).flatMap(battleId -> + getWaitingParty(code).doOnNext(party -> party.start(battleId)) + .flatMap(partyRepository::save)) + .doOnNext(party -> party.getParticipants().forEach( + partyMember -> { + partySinkHandler.sendEvent(partyMember, new PartyEvent(party)); + partySinkHandler.complete(partyMember); + } + )).then(); + } + public Mono quit(Mono member, String code) { + // TODO + throw new UnsupportedOperationException("Not supported yet"); + } +} diff --git a/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/service/PartySinkHandler.java b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/service/PartySinkHandler.java new file mode 100644 index 0000000..212fd82 --- /dev/null +++ b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/service/PartySinkHandler.java @@ -0,0 +1,9 @@ +package online.partyrun.partyrunmatchingservice.domain.party.service; + +import online.partyrun.partyrunmatchingservice.domain.party.dto.PartyEvent; +import online.partyrun.partyrunmatchingservice.global.sse.SinkHandlerTemplate; +import org.springframework.stereotype.Component; + +@Component +public class PartySinkHandler extends SinkHandlerTemplate { +} From ae662e4c0cf68498ba9beba8c021cd20bbd3bf7f Mon Sep 17 00:00:00 2001 From: Hyeonjun Park Date: Thu, 21 Sep 2023 11:13:39 +0900 Subject: [PATCH 2/4] =?UTF-8?q?refactor=20:=20getEventStream=20->=20joinAn?= =?UTF-8?q?dConnectSink=EB=A1=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=AA=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/party/controller/PartyController.java | 2 +- .../domain/party/service/PartyService.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/controller/PartyController.java b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/controller/PartyController.java index 48757f6..06de355 100644 --- a/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/controller/PartyController.java +++ b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/controller/PartyController.java @@ -30,7 +30,7 @@ public Mono postParties(Mono auth, @RequestBody @GetMapping(path = "{entryCode}/join", produces = "text/event-stream") public Flux getPartyEventStream(Mono auth, @PathVariable String entryCode) { - return partyService.getEventStream(auth.map(Principal::getName), entryCode); + return partyService.joinAndConnectSink(auth.map(Principal::getName), entryCode); } @PostMapping("{entryCode}/start") diff --git a/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/service/PartyService.java b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/service/PartyService.java index 6b553a4..c80dc7b 100644 --- a/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/service/PartyService.java +++ b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/service/PartyService.java @@ -31,7 +31,7 @@ public Mono create(Mono member, PartyRequest request) { .map(PartyIdResponse::new); } - public Flux getEventStream(Mono member, String entryCode) { + public Flux joinAndConnectSink(Mono member, String entryCode) { return member .doOnNext(partySinkHandler::create) .flatMap(memberId -> joinParty(memberId, entryCode)) From 38d8891c7d2535eae2a76f2dc71c449a45b4fafb Mon Sep 17 00:00:00 2001 From: Hyeonjun Park Date: Thu, 21 Sep 2023 11:14:03 +0900 Subject: [PATCH 3/4] =?UTF-8?q?test=20:=20party=20api=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/asciidoc/index.adoc | 11 +- .../matching/service/MatchingServiceTest.java | 2 - .../party/controller/PartyControllerTest.java | 103 ++++++++++++++++++ .../domain/party/entity/EntryCodeTest.java | 17 +++ .../domain/party/entity/PartyTest.java | 34 ++++++ .../party/service/PartyServiceTest.java | 99 +++++++++++++++++ 6 files changed, 263 insertions(+), 3 deletions(-) create mode 100644 src/test/java/online/partyrun/partyrunmatchingservice/domain/party/controller/PartyControllerTest.java create mode 100644 src/test/java/online/partyrun/partyrunmatchingservice/domain/party/entity/EntryCodeTest.java create mode 100644 src/test/java/online/partyrun/partyrunmatchingservice/domain/party/entity/PartyTest.java create mode 100644 src/test/java/online/partyrun/partyrunmatchingservice/domain/party/service/PartyServiceTest.java diff --git a/src/docs/asciidoc/index.adoc b/src/docs/asciidoc/index.adoc index ab5c9f0..ce6dee3 100644 --- a/src/docs/asciidoc/index.adoc +++ b/src/docs/asciidoc/index.adoc @@ -41,5 +41,14 @@ operation::shutdown[snippets='http-request,http-response'] === waiting event 취소 operation::cancel-waiting-event[snippets='http-request,http-response'] - +== Party + +=== party 생성 +operation::create-party[snippets='http-request,http-response'] +=== party 참여 +operation::join-party[snippets='http-request,http-response'] +=== party running 시작 +operation::start-party[snippets='http-request,http-response'] +=== party 나가기 +operation::quit-party[snippets='http-request,http-response'] diff --git a/src/test/java/online/partyrun/partyrunmatchingservice/domain/matching/service/MatchingServiceTest.java b/src/test/java/online/partyrun/partyrunmatchingservice/domain/matching/service/MatchingServiceTest.java index 19ab2ef..1d60a2b 100644 --- a/src/test/java/online/partyrun/partyrunmatchingservice/domain/matching/service/MatchingServiceTest.java +++ b/src/test/java/online/partyrun/partyrunmatchingservice/domain/matching/service/MatchingServiceTest.java @@ -1,6 +1,5 @@ package online.partyrun.partyrunmatchingservice.domain.matching.service; -import lombok.SneakyThrows; import online.partyrun.partyrunmatchingservice.config.redis.RedisTestConfig; import online.partyrun.partyrunmatchingservice.domain.battle.service.BattleService; import online.partyrun.partyrunmatchingservice.domain.matching.controller.MatchingRequest; @@ -179,7 +178,6 @@ class 스케줄러가_동작할_때 { @Test @DisplayName("TIMEOUT된 Match를 삭제한다") - @SneakyThrows void runDeleteIfTimeOver() { matchingService.create(members, 1000).block(); diff --git a/src/test/java/online/partyrun/partyrunmatchingservice/domain/party/controller/PartyControllerTest.java b/src/test/java/online/partyrun/partyrunmatchingservice/domain/party/controller/PartyControllerTest.java new file mode 100644 index 0000000..545fd3e --- /dev/null +++ b/src/test/java/online/partyrun/partyrunmatchingservice/domain/party/controller/PartyControllerTest.java @@ -0,0 +1,103 @@ +package online.partyrun.partyrunmatchingservice.domain.party.controller; + +import online.partyrun.partyrunmatchingservice.config.docs.WebfluxDocsTest; +import online.partyrun.partyrunmatchingservice.domain.party.dto.PartyEvent; +import online.partyrun.partyrunmatchingservice.domain.party.dto.PartyIdResponse; +import online.partyrun.partyrunmatchingservice.domain.party.dto.PartyRequest; +import online.partyrun.partyrunmatchingservice.domain.party.entity.PartyStatus; +import online.partyrun.partyrunmatchingservice.domain.party.service.PartyService; +import online.partyrun.partyrunmatchingservice.global.controller.HttpControllerAdvice; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ContextConfiguration; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.webtestclient.WebTestClientRestDocumentation.document; + +@ContextConfiguration(classes = {PartyController.class, HttpControllerAdvice.class}) +@DisplayName("PartyController") +@WithMockUser +class PartyControllerTest extends WebfluxDocsTest { + @MockBean + PartyService partyService; + + private static final String ENTRY_CODE = "123456"; + + @Test + @DisplayName("post : party 생성") + void postParties() { + PartyRequest request = new PartyRequest(1000); + given(partyService.create(any(Mono.class), any(PartyRequest.class))) + .willReturn(Mono.just(new PartyIdResponse("123456"))); + + client.post() + .uri("/parties") + .bodyValue(request) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus() + .isCreated() + .expectBody() + .consumeWith(document("create-party")); + } + + @Test + @DisplayName("get : join party") + void getPartyEventStream() { + final Flux eventResult = Flux.just( + new PartyEvent(ENTRY_CODE, "member1", PartyStatus.WAITING, List.of("member1"), null), + new PartyEvent(ENTRY_CODE, "member1", PartyStatus.WAITING, List.of("member1", "member2"), null), + new PartyEvent(ENTRY_CODE, "member1", PartyStatus.COMPLETED, List.of("member1", "member2"), "battle-id") + ); + given(partyService.joinAndConnectSink(any(Mono.class), any(String.class))) + .willReturn(eventResult); + + client.get() + .uri("/parties/{entryCode}/join", ENTRY_CODE) + .accept(MediaType.TEXT_EVENT_STREAM) + .exchange() + .expectStatus() + .isOk() + .expectBody() + .consumeWith(document("join-party")); + } + + @Test + @DisplayName("post : start party") + void partyStart() { + given(partyService.start(any(Mono.class), any(String.class))) + .willReturn(Mono.empty()); + + client.post() + .uri("/parties/{entryCode}/start",ENTRY_CODE) + .exchange() + .expectStatus() + .isNoContent() + .expectBody() + .consumeWith(document("start-party")); + } + + @Test + @DisplayName("post : quit party") + void quitParty() { + given(partyService.start(any(Mono.class), any(String.class))) + .willReturn(Mono.empty()); + + client.post() + .uri("/parties/{entryCode}/quit",ENTRY_CODE) + .exchange() + .expectStatus() + .isNoContent() + .expectBody() + .consumeWith(document("quit-party")); + } + +} \ No newline at end of file diff --git a/src/test/java/online/partyrun/partyrunmatchingservice/domain/party/entity/EntryCodeTest.java b/src/test/java/online/partyrun/partyrunmatchingservice/domain/party/entity/EntryCodeTest.java new file mode 100644 index 0000000..d03ab78 --- /dev/null +++ b/src/test/java/online/partyrun/partyrunmatchingservice/domain/party/entity/EntryCodeTest.java @@ -0,0 +1,17 @@ +package online.partyrun.partyrunmatchingservice.domain.party.entity; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class EntryCodeTest { + @Test + @DisplayName("entryCode 생성 시 100,000 - 999,999 중 랜덤한 값으로 생성한다.") + void constructRandom() { + EntryCode code = new EntryCode(); + Integer value = Integer.parseInt(code.getCode()); + + assertThat(value).isBetween(100_000, 999_999); + } +} \ No newline at end of file diff --git a/src/test/java/online/partyrun/partyrunmatchingservice/domain/party/entity/PartyTest.java b/src/test/java/online/partyrun/partyrunmatchingservice/domain/party/entity/PartyTest.java new file mode 100644 index 0000000..053c16e --- /dev/null +++ b/src/test/java/online/partyrun/partyrunmatchingservice/domain/party/entity/PartyTest.java @@ -0,0 +1,34 @@ +package online.partyrun.partyrunmatchingservice.domain.party.entity; + +import online.partyrun.partyrunmatchingservice.domain.waiting.root.RunningDistance; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +@DisplayName("party에서") +class PartyTest { + @Test + @DisplayName("join을 하면 참여자 명단에 추가를 한다.") + void join() { + Party party = new Party("leader", RunningDistance.M1000); + String newMember = "newMember"; + party.join(newMember); + + assertThat(party.getParticipants()).contains(newMember); + } + + @Test + @DisplayName("시작을 하면 주어진 battle ID로 설정을 하고, COMPLETED로 상태로 변경한다.") + void start() { + Party party = new Party("leader", RunningDistance.M1000); + String battleId = "battleId"; + party.start(battleId); + + assertAll( + () -> assertThat(party.getBattleId()).isEqualTo(battleId), + () -> assertThat(party.getStatus()).isEqualTo(PartyStatus.COMPLETED) + ); + } +} \ No newline at end of file diff --git a/src/test/java/online/partyrun/partyrunmatchingservice/domain/party/service/PartyServiceTest.java b/src/test/java/online/partyrun/partyrunmatchingservice/domain/party/service/PartyServiceTest.java new file mode 100644 index 0000000..5289dd1 --- /dev/null +++ b/src/test/java/online/partyrun/partyrunmatchingservice/domain/party/service/PartyServiceTest.java @@ -0,0 +1,99 @@ +package online.partyrun.partyrunmatchingservice.domain.party.service; + +import online.partyrun.partyrunmatchingservice.config.redis.RedisTestConfig; +import online.partyrun.partyrunmatchingservice.domain.battle.service.BattleService; +import online.partyrun.partyrunmatchingservice.domain.party.dto.PartyEvent; +import online.partyrun.partyrunmatchingservice.domain.party.entity.EntryCode; +import online.partyrun.partyrunmatchingservice.domain.party.entity.Party; +import online.partyrun.partyrunmatchingservice.domain.party.entity.PartyStatus; +import online.partyrun.partyrunmatchingservice.domain.party.repository.PartyRepository; +import online.partyrun.partyrunmatchingservice.domain.waiting.root.RunningDistance; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +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; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; + +@SpringBootTest +@DisplayName("partyService") +@Import(RedisTestConfig.class) +class PartyServiceTest { + @Autowired + PartyService partyService; + @Autowired + PartyRepository partyRepository; + @Autowired + PartySinkHandler partySinkHandler; + @MockBean + BattleService battleService; + + + Mono 현준 = Mono.just("현준"); + Mono 성우 = Mono.just("성우"); + Party partySample = new Party(현준.block(), RunningDistance.M1000); + + @AfterEach + public void clear() { + partyRepository.deleteAll().block(); + } + + @Nested + @DisplayNameGeneration(ReplaceUnderscores.class) + class 파티가_생성된_후 { + EntryCode code = partyRepository.save(partySample).block().getEntryCode(); + PartyEvent event = new PartyEvent(code.getCode(), 현준.block(), PartyStatus.WAITING, List.of(현준.block()), null); + + @BeforeEach + void setUp() { + given(battleService.create(any(List.class), any(Integer.class))) + .willReturn(Mono.just("battleId")); + code = partyRepository.save(partySample).block().getEntryCode(); + } + + @Test + @DisplayName("join 시에 party 명단에 추가하고, envet를 sink를 생성하여 참여자에게 전파한다.") + void joinParty() { + + StepVerifier.create(partyService.joinAndConnectSink(현준, code.getCode())) + .assertNext(res -> assertThat(res).isEqualTo(event)) + .thenCancel() + .verify(); + } + + @Test + @DisplayName("게임 시작시에 이벤트를 전송하고, 완료 처리를 한다.") + void gameStart() { + + partyService.joinAndConnectSink(현준, code.getCode()).blockFirst(); + partySinkHandler.create(현준.block()); + partyService.joinAndConnectSink(성우, code.getCode()).blockFirst(); + partySinkHandler.create(성우.block()); + partyService.start(현준, code.getCode()).block(); + + final Party partyResult = partyRepository.findByEntryCodeAndStatus(code, PartyStatus.COMPLETED).block(); + assertAll( + () -> assertThat(partySinkHandler.isExist(현준.block())).isFalse(), + () -> assertThat(partySinkHandler.isExist(성우.block())).isFalse(), + () -> assertThat(partyResult.getParticipants()).contains(현준.block(), 성우.block()) + ); + } + + @Test + @DisplayName("미구현 : 파티 나가기를 수행한다.") + void quitParty() { + assertThatThrownBy(() -> partyService.quit(현준, "asdf")) + .isInstanceOf(UnsupportedOperationException.class); + } + } +} \ No newline at end of file From e78fc6b5b8feeb1c1b24bc8be066a77294b4f004 Mon Sep 17 00:00:00 2001 From: Hyeonjun Park Date: Thu, 21 Sep 2023 11:26:35 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix=20:=20random=EA=B0=92=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=EC=B2=B4=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/party/entity/EntryCode.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/entity/EntryCode.java b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/entity/EntryCode.java index f0d8e0e..5ef63ad 100644 --- a/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/entity/EntryCode.java +++ b/src/main/java/online/partyrun/partyrunmatchingservice/domain/party/entity/EntryCode.java @@ -5,7 +5,7 @@ import lombok.EqualsAndHashCode; import lombok.Getter; -import java.util.concurrent.ThreadLocalRandom; +import java.security.SecureRandom; @Getter @AllArgsConstructor @@ -21,6 +21,6 @@ public EntryCode() { } private String generateRandomRoomId() { - return String.valueOf(ThreadLocalRandom.current().nextInt(MIN_RANDOM_NUMBER, MAX_RANDOM_NUMBER + 1)); + return String.valueOf(new SecureRandom().nextInt(MIN_RANDOM_NUMBER, MAX_RANDOM_NUMBER + 1)); } }