diff --git a/backend/src/main/java/site/coduo/sync/controller/SseController.java b/backend/src/main/java/site/coduo/sync/controller/SseController.java index d6ab642a..779de46b 100644 --- a/backend/src/main/java/site/coduo/sync/controller/SseController.java +++ b/backend/src/main/java/site/coduo/sync/controller/SseController.java @@ -1,6 +1,7 @@ package site.coduo.sync.controller; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @@ -10,6 +11,7 @@ import site.coduo.sync.controller.docs.SseDocs; import site.coduo.sync.service.SseService; + @RequiredArgsConstructor @RestController public class SseController implements SseDocs { @@ -22,4 +24,12 @@ public ResponseEntity createConnection(@PathVariable("key") final St return ResponseEntity.ok(sseEmitter); } + + @DeleteMapping("/{key}/connect") + public ResponseEntity deleteConnection(@PathVariable("key") final String key) { + sseService.disconnectAll(key); + + return ResponseEntity.noContent() + .build(); + } } diff --git a/backend/src/main/java/site/coduo/sync/controller/docs/SseDocs.java b/backend/src/main/java/site/coduo/sync/controller/docs/SseDocs.java index 44de585c..d339859a 100644 --- a/backend/src/main/java/site/coduo/sync/controller/docs/SseDocs.java +++ b/backend/src/main/java/site/coduo/sync/controller/docs/SseDocs.java @@ -16,4 +16,8 @@ public interface SseDocs { @ApiResponse(responseCode = "200", description = "SSE 연결 성공 - event:connect\ndata:OK 메시지를 응답", content = @Content(mediaType = MediaType.TEXT_EVENT_STREAM_VALUE)) @ApiResponse(responseCode = "4xx", description = "SSE 연결 실패", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ApiErrorResponse.class))) ResponseEntity createConnection(String key); + + @Operation(summary = "특정 key에 속하는 SSE 연결을 모두 삭제한다.") + @ApiResponse(responseCode = "204", description = "SSE 삭제 성공 - event:close\ndata:OK 메시지를 응답", content = @Content(mediaType = MediaType.TEXT_EVENT_STREAM_VALUE)) + ResponseEntity deleteConnection(String key); } diff --git a/backend/src/main/java/site/coduo/sync/service/EventStream.java b/backend/src/main/java/site/coduo/sync/service/EventStream.java index 275c03a5..8b9e321d 100644 --- a/backend/src/main/java/site/coduo/sync/service/EventStream.java +++ b/backend/src/main/java/site/coduo/sync/service/EventStream.java @@ -7,4 +7,6 @@ public interface EventStream { SseEmitter connect(); void flush(String name, String message); + + void close(); } diff --git a/backend/src/main/java/site/coduo/sync/service/EventStreams.java b/backend/src/main/java/site/coduo/sync/service/EventStreams.java index f9f19e45..1b045210 100644 --- a/backend/src/main/java/site/coduo/sync/service/EventStreams.java +++ b/backend/src/main/java/site/coduo/sync/service/EventStreams.java @@ -32,6 +32,10 @@ public void broadcast(final String name, final String message) { streams.forEach(eventStream -> eventStream.flush(name, message)); } + public void closeAll() { + streams.forEach(EventStream::close); + } + public boolean isEmpty() { return streams.isEmpty(); } diff --git a/backend/src/main/java/site/coduo/sync/service/EventStreamsRegistry.java b/backend/src/main/java/site/coduo/sync/service/EventStreamsRegistry.java index 62c34c12..68f9515a 100644 --- a/backend/src/main/java/site/coduo/sync/service/EventStreamsRegistry.java +++ b/backend/src/main/java/site/coduo/sync/service/EventStreamsRegistry.java @@ -17,25 +17,34 @@ public EventStreamsRegistry() { this.registry = new ConcurrentHashMap<>(); } - public SseEmitter register(final String name) { - final EventStreams eventStreams = registry.getOrDefault(name, new EventStreams()); + public SseEmitter register(final String key) { + final EventStreams eventStreams = registry.getOrDefault(key, new EventStreams()); final EventStream eventStream = new SseEventStream(); eventStreams.add(eventStream); - registry.put(name, eventStreams); + registry.put(key, eventStreams); return eventStreams.publish(eventStream); } - public EventStreams findEventStreams(final String key) { + public void release(final String key) { if (!registry.containsKey(key)) { - throw new NotFoundSseConnectionException("존재하지 않는 SSE 커넥션입니다."); + return; } - return registry.get(key); + final EventStreams eventStreams = registry.get(key); + eventStreams.closeAll(); + registry.remove(key); } - public boolean hasEmptyConnection(final String key) { - if (!registry.containsKey(key)) { - throw new NotFoundSseConnectionException("SSE 커넥션을 찾을 수 없습니다."); + public EventStreams findEventStreams(final String key) { + if (registry.containsKey(key)) { + return registry.get(key); + } + throw new NotFoundSseConnectionException("존재하지 않는 SSE 커넥션입니다."); + } + + public boolean hasNoStreams(final String key) { + if (registry.containsKey(key)) { + return registry.get(key).isEmpty(); } - return registry.get(key).isEmpty(); + throw new NotFoundSseConnectionException("SSE 커넥션을 찾을 수 없습니다."); } } diff --git a/backend/src/main/java/site/coduo/sync/service/SchedulerRegistry.java b/backend/src/main/java/site/coduo/sync/service/SchedulerRegistry.java index bddd9989..33e52744 100644 --- a/backend/src/main/java/site/coduo/sync/service/SchedulerRegistry.java +++ b/backend/src/main/java/site/coduo/sync/service/SchedulerRegistry.java @@ -25,11 +25,20 @@ public void release(final String key) { if (!registry.containsKey(key)) { throw new NotFoundScheduledFutureException("키에 해당하는 스케줄러 결과가 존재하지 않습니다."); } - registry.get(key).cancel(false); + registry.get(key) + .cancel(false); registry.remove(key); } public boolean has(final String key) { return registry.containsKey(key); } + + public boolean isActive(final String key) { + if (registry.containsKey(key)) { + final ScheduledFuture scheduledFuture = registry.get(key); + return !scheduledFuture.isDone() && !scheduledFuture.isCancelled(); + } + return false; + } } diff --git a/backend/src/main/java/site/coduo/sync/service/SchedulerService.java b/backend/src/main/java/site/coduo/sync/service/SchedulerService.java index 6b92d43f..5f1dabae 100644 --- a/backend/src/main/java/site/coduo/sync/service/SchedulerService.java +++ b/backend/src/main/java/site/coduo/sync/service/SchedulerService.java @@ -12,6 +12,7 @@ import lombok.RequiredArgsConstructor; import site.coduo.timer.domain.Timer; import site.coduo.timer.repository.TimerRepository; +import site.coduo.timer.service.TimestampRegistry; @RequiredArgsConstructor @Component @@ -26,40 +27,50 @@ public class SchedulerService { private final SseService sseService; public void start(final String key) { + sseService.broadcast(key, "timer", "start"); if (isInitial(key)) { - final Timer timer = timerRepository.fetchTimerByAccessCode(key).toDomain(); + final Timer timer = timerRepository.fetchTimerByAccessCode(key) + .toDomain(); scheduling(key, timer); timestampRegistry.register(key, timer); return; } - if (isResume(key)) { - final Timer timer = timestampRegistry.get(key); - scheduling(key, timer); - } + final Timer timer = timestampRegistry.get(key); + scheduling(key, timer); } private boolean isInitial(final String key) { return !schedulerRegistry.has(key) && !timestampRegistry.has(key); } - private boolean isResume(final String key) { - return !schedulerRegistry.has(key) && timestampRegistry.has(key); - } - private void scheduling(final String key, final Timer timer) { final Trigger trigger = new PeriodicTrigger(DELAY_SECOND); - final ScheduledFuture schedule = taskScheduler.schedule(() -> { - timer.decreaseRemainingTime(DELAY_SECOND.toMillis()); - if (timer.getRemainingTime() == 0 || sseService.hasEmptyConnection(key)) { - schedulerRegistry.release(key); - timestampRegistry.release(key); - } - sseService.broadcast(key, "remaining-time", String.valueOf(timer.getRemainingTime())); - }, trigger); + final ScheduledFuture schedule = taskScheduler.schedule(() -> runTimer(key, timer), trigger); schedulerRegistry.register(key, schedule); } + private void runTimer(final String key, final Timer timer) { + if (timer.isTimeUp()) { + stop(key); + final Timer initalTimer = new Timer(timer.getAccessCode(), timer.getDuration(), timer.getDuration()); + timestampRegistry.register(key, initalTimer); + return; + } + if (sseService.hasNoConnections(key)) { + stop(key); + return; + } + timer.decreaseRemainingTime(DELAY_SECOND.toMillis()); + sseService.broadcast(key, "remaining-time", String.valueOf(timer.getRemainingTime())); + } + + public void pause(final String key) { + sseService.broadcast(key, "timer", "pause"); + schedulerRegistry.release(key); + } + public void stop(final String key) { + sseService.broadcast(key, "timer", "stop"); schedulerRegistry.release(key); } } diff --git a/backend/src/main/java/site/coduo/sync/service/SseEventStream.java b/backend/src/main/java/site/coduo/sync/service/SseEventStream.java index e15fc4a6..f48d1418 100644 --- a/backend/src/main/java/site/coduo/sync/service/SseEventStream.java +++ b/backend/src/main/java/site/coduo/sync/service/SseEventStream.java @@ -15,13 +15,16 @@ @Getter public class SseEventStream implements EventStream { - private static final long INFINITE_TIME_OUT = -1; + private static final Duration TIME_OUT = Duration.ofMinutes(20); + private static final String CLOSE_NAME = "close"; + private static final String CONNECT_NAME = "connect"; + private static final String SUCCESS_MESSAGE = "OK"; private final AtomicLong id = new AtomicLong(0); private final SseEmitter sseEmitter; public SseEventStream() { - this.sseEmitter = new SseEmitter(INFINITE_TIME_OUT); + this.sseEmitter = new SseEmitter(TIME_OUT.toMillis()); } public SseEventStream(final Duration timeout) { @@ -38,8 +41,8 @@ public SseEmitter connect() { try { sseEmitter.send(SseEmitter.event() .id(eventId) - .name("connect") - .data("OK")); + .name(CONNECT_NAME) + .data(SUCCESS_MESSAGE)); } catch (final IOException e) { throw new SseConnectionFailureException("SSE 연결이 실패했습니다."); } @@ -56,10 +59,16 @@ public void flush(final String name, final String message) { .data(message) ); } catch (IOException e) { - throw new SseConnectionFailureException("SSE 통신에 실패했습니다."); + log.warn("SSE 통신 중 에러가 발생했습니다."); } } + @Override + public void close() { + flush(CLOSE_NAME, SUCCESS_MESSAGE); + sseEmitter.complete(); + } + @Override public boolean equals(final Object o) { if (this == o) { diff --git a/backend/src/main/java/site/coduo/sync/service/SseService.java b/backend/src/main/java/site/coduo/sync/service/SseService.java index f1222ed1..31fe54d8 100644 --- a/backend/src/main/java/site/coduo/sync/service/SseService.java +++ b/backend/src/main/java/site/coduo/sync/service/SseService.java @@ -4,15 +4,25 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import lombok.RequiredArgsConstructor; +import site.coduo.timer.service.TimerService; @RequiredArgsConstructor @Service public class SseService { private final EventStreamsRegistry eventStreamsRegistry; + private final TimerService timerService; + private final SchedulerRegistry schedulerRegistry; public SseEmitter connect(final String key) { - return eventStreamsRegistry.register(key); + final SseEmitter emitter = eventStreamsRegistry.register(key); + final long remainingTime = timerService.readTimerRemainingTime(key); + // todo: SchedulerService 분리된 상수화 어떻게 할지 생각 + broadcast(key, "remaining-time", String.valueOf(remainingTime)); + if (schedulerRegistry.isActive(key)) { + broadcast(key, "timer", "running"); + } + return emitter; } public void broadcast(final String key, final String event, final String data) { @@ -20,7 +30,11 @@ public void broadcast(final String key, final String event, final String data) { emitters.broadcast(event, data); } - public boolean hasEmptyConnection(final String key) { - return eventStreamsRegistry.hasEmptyConnection(key); + public boolean hasNoConnections(final String key) { + return eventStreamsRegistry.hasNoStreams(key); + } + + public void disconnectAll(final String key) { + eventStreamsRegistry.release(key); } } diff --git a/backend/src/main/java/site/coduo/timer/controller/TimerController.java b/backend/src/main/java/site/coduo/timer/controller/TimerController.java index 3c877d88..d276c323 100644 --- a/backend/src/main/java/site/coduo/timer/controller/TimerController.java +++ b/backend/src/main/java/site/coduo/timer/controller/TimerController.java @@ -33,7 +33,7 @@ public ResponseEntity createTimerStart(@PathVariable("accessCode") final S @PatchMapping("/{accessCode}/timer/stop") public ResponseEntity createTimerStop(@PathVariable("accessCode") final String accessCode) { - schedulerService.stop(accessCode); + schedulerService.pause(accessCode); return ResponseEntity.noContent() .build(); diff --git a/backend/src/main/java/site/coduo/timer/controller/error/TimerApiError.java b/backend/src/main/java/site/coduo/timer/controller/error/TimerApiError.java index 10b73df9..d7724bc4 100644 --- a/backend/src/main/java/site/coduo/timer/controller/error/TimerApiError.java +++ b/backend/src/main/java/site/coduo/timer/controller/error/TimerApiError.java @@ -9,9 +9,9 @@ @RequiredArgsConstructor public enum TimerApiError { - INVALID_REQUEST(HttpStatus.BAD_REQUEST, "유효하지 않은 페어룸 히스토리 요청입니다."), + INVALID_REQUEST(HttpStatus.BAD_REQUEST, "유효하지 않은 요청입니다."), INVALID_TIMER_REQUEST(HttpStatus.BAD_REQUEST, "유효하지 않은 타이머 시간이 존재합니다."), - TIMER_NOT_FOUND(HttpStatus.NOT_FOUND, "페어룸 히스토리가 존재하지 않습니다."); + TIMER_NOT_FOUND(HttpStatus.NOT_FOUND, "타이머를 찾을 수 없습니다."); private final HttpStatus httpStatus; private final String message; diff --git a/backend/src/main/java/site/coduo/timer/domain/Timer.java b/backend/src/main/java/site/coduo/timer/domain/Timer.java index 329ee4df..d232e239 100644 --- a/backend/src/main/java/site/coduo/timer/domain/Timer.java +++ b/backend/src/main/java/site/coduo/timer/domain/Timer.java @@ -31,6 +31,10 @@ public long getRemainingTime() { return remainingTime.get(); } + public boolean isTimeUp() { + return remainingTime.get() == 0; + } + public void decreaseRemainingTime(final long decrease) { if (remainingTime.get() == 0L) { return; diff --git a/backend/src/main/java/site/coduo/timer/service/TimerService.java b/backend/src/main/java/site/coduo/timer/service/TimerService.java index 6e1011ac..c594b671 100644 --- a/backend/src/main/java/site/coduo/timer/service/TimerService.java +++ b/backend/src/main/java/site/coduo/timer/service/TimerService.java @@ -19,6 +19,7 @@ public class TimerService { private final TimerRepository timerRepository; + private final TimestampRegistry timestampRegistry; private final PairRoomRepository pairRoomRepository; public TimerReadResponse readTimer(final String accessCode) { @@ -27,15 +28,26 @@ public TimerReadResponse readTimer(final String accessCode) { return TimerReadResponse.of(timerEntity.getId(), timerEntity.toDomain()); } + public long readTimerRemainingTime(final String accessCode) { + if (timestampRegistry.has(accessCode)) { + return timestampRegistry.get(accessCode) + .getRemainingTime(); + } + final Timer timer = timerRepository.fetchTimerByAccessCode(accessCode) + .toDomain(); + return timer.getDuration(); + } + @Transactional - public void updateTimer(final String accessCode, final TimerUpdateRequest newTimer) { + public void updateTimer(final String accessCode, final TimerUpdateRequest updateRequest) { final PairRoomEntity pairRoomEntity = pairRoomRepository.fetchByAccessCode(accessCode); final TimerEntity timerEntity = timerRepository.fetchTimerByPairRoomId(pairRoomEntity.getId()); - final Timer timer = new Timer( + final Timer newTimer = new Timer( new AccessCode(pairRoomEntity.getAccessCode()), - newTimer.duration(), - newTimer.remainingTime() + updateRequest.duration(), + updateRequest.remainingTime() ); - timerEntity.updateTimer(timer); + timerEntity.updateTimer(newTimer); + timestampRegistry.register(accessCode, newTimer); } } diff --git a/backend/src/main/java/site/coduo/sync/service/TimestampRegistry.java b/backend/src/main/java/site/coduo/timer/service/TimestampRegistry.java similarity index 81% rename from backend/src/main/java/site/coduo/sync/service/TimestampRegistry.java rename to backend/src/main/java/site/coduo/timer/service/TimestampRegistry.java index 70f022c4..1bd5d567 100644 --- a/backend/src/main/java/site/coduo/sync/service/TimestampRegistry.java +++ b/backend/src/main/java/site/coduo/timer/service/TimestampRegistry.java @@ -1,23 +1,21 @@ -package site.coduo.sync.service; +package site.coduo.timer.service; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.springframework.stereotype.Component; -import site.coduo.sync.exception.DuplicateTimestampException; +import lombok.NoArgsConstructor; import site.coduo.sync.exception.NotFoundTimeStampException; import site.coduo.timer.domain.Timer; @Component +@NoArgsConstructor public class TimestampRegistry { private final Map registry = new ConcurrentHashMap<>(); public void register(final String key, final Timer timer) { - if (registry.containsKey(key)) { - throw new DuplicateTimestampException("이미 존재하는 타임 스탬프입니다."); - } registry.put(key, timer); } diff --git a/backend/src/test/java/site/coduo/acceptance/PairRoomAcceptanceTest.java b/backend/src/test/java/site/coduo/acceptance/PairRoomAcceptanceTest.java index e20d2e69..b68cd09b 100644 --- a/backend/src/test/java/site/coduo/acceptance/PairRoomAcceptanceTest.java +++ b/backend/src/test/java/site/coduo/acceptance/PairRoomAcceptanceTest.java @@ -4,8 +4,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; import org.springframework.transaction.annotation.Transactional; @@ -18,15 +16,12 @@ @Transactional class PairRoomAcceptanceTest extends AcceptanceFixture { - - private static final Logger log = LoggerFactory.getLogger(PairRoomAcceptanceTest.class); - - static PairRoomCreateResponse createPairRoom(final PairRoomCreateRequest pairRoom) { + static PairRoomCreateResponse createPairRoom(final PairRoomCreateRequest request) { return RestAssured .given() .contentType(MediaType.APPLICATION_JSON_VALUE) .accept(MediaType.APPLICATION_JSON_VALUE) - .body(pairRoom) + .body(request) .when() .post("/api/pair-room") @@ -43,7 +38,6 @@ void show_pair_room() { final PairRoomCreateResponse pairRoomUrl = createPairRoom(new PairRoomCreateRequest("레디", "프람", 10000L, 10000L, "IN_PROGRESS")); - log.info("여기까지 생성 로그"); //when & then RestAssured .given() diff --git a/backend/src/test/java/site/coduo/acceptance/SseAcceptanceTest.java b/backend/src/test/java/site/coduo/acceptance/SseAcceptanceTest.java index 1150a143..4e4543b7 100644 --- a/backend/src/test/java/site/coduo/acceptance/SseAcceptanceTest.java +++ b/backend/src/test/java/site/coduo/acceptance/SseAcceptanceTest.java @@ -22,7 +22,6 @@ static void createConnect(final String accessCode) { .then() .log().all() .statusCode(200); - } @Test @@ -45,4 +44,31 @@ void create_sse_connection() { .log().all() .statusCode(200); } + + @Test + @DisplayName("페어룸의 모든 SSE connection을 종료한다.") + void delete_sse_connection() { + // given + final PairRoomCreateRequest request = new PairRoomCreateRequest( + "해시", + "잉크", + 1000L, + 1000L, + PairRoomStatus.IN_PROGRESS.name() + ); + final String accessCode = createPairRoom(request).accessCode(); + createConnect(accessCode); + + // when & then + RestAssured + .given() + + .when() + .log().all() + .delete("/api/{key}/connect", accessCode) + + .then() + .log().all() + .statusCode(204); + } } diff --git a/backend/src/test/java/site/coduo/fake/FakeEvenStream.java b/backend/src/test/java/site/coduo/fake/FakeEvenStream.java index 25000315..8b3e519d 100644 --- a/backend/src/test/java/site/coduo/fake/FakeEvenStream.java +++ b/backend/src/test/java/site/coduo/fake/FakeEvenStream.java @@ -27,4 +27,9 @@ public SseEmitter connect() { public void flush(final String name, final String message) { sseEmitter.complete(); } + + @Override + public void close() { + sseEmitter.complete(); + } } diff --git a/backend/src/test/java/site/coduo/fake/FakeEventStreamRegistry.java b/backend/src/test/java/site/coduo/fake/FakeEventStreamRegistry.java index ae12891a..80cb91a3 100644 --- a/backend/src/test/java/site/coduo/fake/FakeEventStreamRegistry.java +++ b/backend/src/test/java/site/coduo/fake/FakeEventStreamRegistry.java @@ -18,9 +18,9 @@ public FakeEventStreamRegistry() { } @Override - public SseEmitter register(final String name) { - final EventStreams eventStreams = registry.getOrDefault(name, new EventStreams()); - registry.put(name, eventStreams); + public SseEmitter register(final String key) { + final EventStreams eventStreams = registry.getOrDefault(key, new EventStreams()); + registry.put(key, eventStreams); final FakeEvenStream fakeEvenStream = new FakeEvenStream(); eventStreams.add(fakeEvenStream); return eventStreams.publish(fakeEvenStream); diff --git a/backend/src/test/java/site/coduo/sync/service/EventStreamsRegistryTest.java b/backend/src/test/java/site/coduo/sync/service/EventStreamsRegistryTest.java index 53036f83..ee2401a8 100644 --- a/backend/src/test/java/site/coduo/sync/service/EventStreamsRegistryTest.java +++ b/backend/src/test/java/site/coduo/sync/service/EventStreamsRegistryTest.java @@ -31,14 +31,13 @@ void return_stored_event_stream() { // given final EventStreamsRegistry eventStreamsRegistry = new EventStreamsRegistry(); final String key = "test"; - final SseEmitter emitter = eventStreamsRegistry.register(key); + eventStreamsRegistry.register(key); // when & then assertThatCode(() -> eventStreamsRegistry.findEventStreams(key)) .doesNotThrowAnyException(); } - @Test @DisplayName("등록되지 않은 이벤트 스트림들을 조회할 경우 예외를 발생시킨다.") void throw_exception_when_search_un_registered_event_stream() { @@ -60,7 +59,7 @@ void check_has_any_connection_with_specific_key_false_case() { eventStreamsRegistry.register(key); // when - final boolean hasEmptyConnection = eventStreamsRegistry.hasEmptyConnection(key); + final boolean hasEmptyConnection = eventStreamsRegistry.hasNoStreams(key); // then assertThat(hasEmptyConnection).isFalse(); @@ -74,7 +73,23 @@ void throw_exception_when_check_contain_connection_with_unsaved_key() { final String key = "tes"; // when & then - assertThatThrownBy(() -> eventStreamsRegistry.hasEmptyConnection(key)) + assertThatThrownBy(() -> eventStreamsRegistry.hasNoStreams(key)) .isInstanceOf(NotFoundSseConnectionException.class); } + + @Test + @DisplayName("키에 해당하는 모든 스트림을 종료 후 삭제한다.") + void close_all_of_key() { + // given + final String key = "hellow"; + final EventStreamsRegistry registry = new EventStreamsRegistry(); + registry.register(key); + + // when + registry.release(key); + + // then + assertThatThrownBy(() -> registry.findEventStreams(key)) + .isExactlyInstanceOf(NotFoundSseConnectionException.class); + } } diff --git a/backend/src/test/java/site/coduo/sync/service/EventStreamsTest.java b/backend/src/test/java/site/coduo/sync/service/EventStreamsTest.java index 11a4195f..d713e10b 100644 --- a/backend/src/test/java/site/coduo/sync/service/EventStreamsTest.java +++ b/backend/src/test/java/site/coduo/sync/service/EventStreamsTest.java @@ -100,7 +100,7 @@ void check_emitters_empty_true_case() { } @Test - @DisplayName("등록된 에미터가 비었지 확인한다. - 거짓") + @DisplayName("등록된 에미터가 비었는지 확인한다. - 거짓") void check_emitters_empty_false_case() { // given final EventStreams eventStreams = new EventStreams(); @@ -112,4 +112,19 @@ void check_emitters_empty_false_case() { // then assertThat(empty).isFalse(); } + + @Test + @DisplayName("등록된 에미터가 모두 종료되었는지 확인한다.") + void check_emitters_all_close() { + // given + final EventStreams eventStreams = new EventStreams(); + final SseEventStream target = new SseEventStream(); + eventStreams.add(target); + + // when + eventStreams.closeAll(); + + // then + assertThatThrownBy(() -> target.flush("종료 후 메세지 보내면", "에러 발생")); + } } diff --git a/backend/src/test/java/site/coduo/sync/service/SchedulerRegistryTest.java b/backend/src/test/java/site/coduo/sync/service/SchedulerRegistryTest.java index a0a4293f..ecf633f0 100644 --- a/backend/src/test/java/site/coduo/sync/service/SchedulerRegistryTest.java +++ b/backend/src/test/java/site/coduo/sync/service/SchedulerRegistryTest.java @@ -42,7 +42,7 @@ void release_scheduler() { } @Test - @DisplayName("key의 스케줄링 상태를 조회한다.") + @DisplayName("key의 스케줄링 결과가 존재하는지 조회한다.") void has_scheduler() { // given final SchedulerRegistry schedulerRegistry = new SchedulerRegistry(); @@ -53,4 +53,37 @@ void has_scheduler() { // when & then assertThat(schedulerRegistry.has(key)).isTrue(); } + + @Test + @DisplayName("key의 스케줄링이 종료되었을 경우 false를 반환한다.") + void is_scheduler_done() { + // given + final SchedulerRegistry schedulerRegistry = new SchedulerRegistry(); + final String key = "access"; + final ScheduledFuture future = new FakeScheduledFuture(); + schedulerRegistry.register(key, future); + schedulerRegistry.release(key); + + // when + final boolean actual = schedulerRegistry.isActive(key); + + // then + assertThat(actual).isFalse(); + } + + @Test + @DisplayName("key의 스케줄링이 실행중일 경우 true를 반환한다.") + void is_scheduler_active() { + // given + final SchedulerRegistry schedulerRegistry = new SchedulerRegistry(); + final String key = "access"; + final ScheduledFuture future = new FakeScheduledFuture(); + schedulerRegistry.register(key, future); + + // when + final boolean actual = schedulerRegistry.isActive(key); + + // then + assertThat(actual).isTrue(); + } } diff --git a/backend/src/test/java/site/coduo/sync/service/SchedulerServiceTest.java b/backend/src/test/java/site/coduo/sync/service/SchedulerServiceTest.java index 718cff56..0dbdfb7d 100644 --- a/backend/src/test/java/site/coduo/sync/service/SchedulerServiceTest.java +++ b/backend/src/test/java/site/coduo/sync/service/SchedulerServiceTest.java @@ -20,6 +20,7 @@ import site.coduo.timer.domain.Timer; import site.coduo.timer.repository.TimerEntity; import site.coduo.timer.repository.TimerRepository; +import site.coduo.timer.service.TimestampRegistry; @Disabled @SpringBootTest @@ -115,7 +116,7 @@ void stop_timer() { .pollDelay(1, TimeUnit.SECONDS) .pollInterval(1, TimeUnit.SECONDS) .untilAsserted(() -> { - schedulerService.stop(key); + schedulerService.pause(key); assertThat(schedulerRegistry.has(key)).isFalse(); }); } diff --git a/backend/src/test/java/site/coduo/timer/service/TimerServiceTest.java b/backend/src/test/java/site/coduo/timer/service/TimerServiceTest.java index f85775f8..98b17062 100644 --- a/backend/src/test/java/site/coduo/timer/service/TimerServiceTest.java +++ b/backend/src/test/java/site/coduo/timer/service/TimerServiceTest.java @@ -12,8 +12,10 @@ import org.springframework.transaction.annotation.Transactional; import site.coduo.pairroom.domain.PairRoomStatus; +import site.coduo.pairroom.domain.accesscode.AccessCode; import site.coduo.pairroom.service.PairRoomService; import site.coduo.pairroom.service.dto.PairRoomCreateRequest; +import site.coduo.timer.domain.Timer; import site.coduo.timer.service.dto.TimerReadResponse; import site.coduo.timer.service.dto.TimerUpdateRequest; import site.coduo.utils.CascadeCleaner; @@ -25,6 +27,9 @@ class TimerServiceTest extends CascadeCleaner { @Autowired private PairRoomService pairRoomService; + @Autowired + private TimestampRegistry timestampRegistry; + @Autowired private TimerService timerService; @@ -82,4 +87,36 @@ void update_timer() { .extracting("duration", "remainingTime") .contains(timerRequest.duration(), timerRequest.remainingTime()); } + + @Test + @DisplayName("타이머 남은 시간을 반환한다. - 타이머 타임 스탬프가 존재할 경우") + void get_remaining_time_when_exist_timestamp() { + // given + final PairRoomCreateRequest pairRoomCreateRequest = new PairRoomCreateRequest("켈리", "레모네", 3000L, 3000L, + PairRoomStatus.IN_PROGRESS.name()); + final String accessCode = pairRoomService.savePairRoom(pairRoomCreateRequest, null); + final Timer timeStamp = new Timer(new AccessCode(accessCode), 10000L, 10000L); + timestampRegistry.register(accessCode, timeStamp); + + // when + final long remainingTime = timerService.readTimerRemainingTime(accessCode); + + // then + assertThat(remainingTime).isEqualTo(timeStamp.getRemainingTime()); + } + + @Test + @DisplayName("타이머 남은 시간을 반환한다. - 타이머가 한번도 동작하지 않았을 경우") + void get_remaining_time_when_not_exist_timestamp() { + // given + final PairRoomCreateRequest pairRoomCreateRequest = new PairRoomCreateRequest("켈리", "레모네", 3000L, 3000L, + PairRoomStatus.IN_PROGRESS.name()); + final String accessCode = pairRoomService.savePairRoom(pairRoomCreateRequest, null); + + // when + final long remainingTime = timerService.readTimerRemainingTime(accessCode); + + // then + assertThat(remainingTime).isEqualTo(pairRoomCreateRequest.timerRemainingTime()); + } }