diff --git a/logbat_view/build.gradle b/logbat_view/build.gradle index 7c82d29..1c44644 100644 --- a/logbat_view/build.gradle +++ b/logbat_view/build.gradle @@ -18,9 +18,20 @@ repositories { } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc' + // SpringBoot WebFlux implementation 'org.springframework.boot:spring-boot-starter-webflux' + // SpringBoot R2DBC + implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc' + // Lombok + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + // MySQL + runtimeOnly 'io.asyncer:r2dbc-mysql' + runtimeOnly 'com.mysql:mysql-connector-j' + + // SpringBoot Test testImplementation 'org.springframework.boot:spring-boot-starter-test' + // Reactor Test testImplementation 'io.projectreactor:reactor-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } diff --git a/logbat_view/src/main/java/info/logbat_view/domain/log/domain/Log.java b/logbat_view/src/main/java/info/logbat_view/domain/log/domain/Log.java new file mode 100644 index 0000000..1b78a37 --- /dev/null +++ b/logbat_view/src/main/java/info/logbat_view/domain/log/domain/Log.java @@ -0,0 +1,26 @@ +package info.logbat_view.domain.log.domain; + +import info.logbat_view.domain.log.domain.enums.LogLevel; +import java.time.LocalDateTime; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class Log { + + private final Long id; + private final String appKey; + private final LogLevel level; + private final String data; + private final LocalDateTime timestamp; + + public static Log from(LogData logData) { + return new Log(logData.getLogId(), + logData.getAppKey(), + LogLevel.valueOf(logData.getLevel()), + logData.getData(), + logData.getTimestamp()); + } +} diff --git a/logbat_view/src/main/java/info/logbat_view/domain/log/domain/LogData.java b/logbat_view/src/main/java/info/logbat_view/domain/log/domain/LogData.java new file mode 100644 index 0000000..a1eda24 --- /dev/null +++ b/logbat_view/src/main/java/info/logbat_view/domain/log/domain/LogData.java @@ -0,0 +1,20 @@ +package info.logbat_view.domain.log.domain; + +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Table; + +@Getter +@Table("logs") +@AllArgsConstructor +public class LogData { + + @Id + private Long logId; + private String appKey; + private String level; + private String data; + private LocalDateTime timestamp; +} diff --git a/logbat_view/src/main/java/info/logbat_view/domain/log/domain/enums/LogLevel.java b/logbat_view/src/main/java/info/logbat_view/domain/log/domain/enums/LogLevel.java new file mode 100644 index 0000000..13d3c8b --- /dev/null +++ b/logbat_view/src/main/java/info/logbat_view/domain/log/domain/enums/LogLevel.java @@ -0,0 +1,5 @@ +package info.logbat_view.domain.log.domain.enums; + +public enum LogLevel { + INFO, ERROR +} diff --git a/logbat_view/src/main/java/info/logbat_view/domain/log/domain/service/LogService.java b/logbat_view/src/main/java/info/logbat_view/domain/log/domain/service/LogService.java new file mode 100644 index 0000000..a96fbcb --- /dev/null +++ b/logbat_view/src/main/java/info/logbat_view/domain/log/domain/service/LogService.java @@ -0,0 +1,19 @@ +package info.logbat_view.domain.log.domain.service; + +import info.logbat_view.domain.log.domain.Log; +import info.logbat_view.domain.log.repository.LogDataRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import reactor.core.publisher.Flux; + +@Service +@RequiredArgsConstructor +public class LogService { + + private final LogDataRepository logDataRepository; + + public Flux findLogsByAppKey(String appKey) { + return logDataRepository.findByAppKey(appKey).map(Log::from); + } + +} diff --git a/logbat_view/src/main/java/info/logbat_view/domain/log/repository/LogDataRepository.java b/logbat_view/src/main/java/info/logbat_view/domain/log/repository/LogDataRepository.java new file mode 100644 index 0000000..ab33c5e --- /dev/null +++ b/logbat_view/src/main/java/info/logbat_view/domain/log/repository/LogDataRepository.java @@ -0,0 +1,13 @@ +package info.logbat_view.domain.log.repository; + +import info.logbat_view.domain.log.domain.LogData; +import org.springframework.data.repository.reactive.ReactiveCrudRepository; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Flux; + +@Repository +public interface LogDataRepository extends ReactiveCrudRepository { + + Flux findByAppKey(@NonNull String appKey); +} diff --git a/logbat_view/src/test/java/info/logbat_view/domain/log/domain/service/LogServiceTest.java b/logbat_view/src/test/java/info/logbat_view/domain/log/domain/service/LogServiceTest.java new file mode 100644 index 0000000..cfd47f4 --- /dev/null +++ b/logbat_view/src/test/java/info/logbat_view/domain/log/domain/service/LogServiceTest.java @@ -0,0 +1,62 @@ +package info.logbat_view.domain.log.domain.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; + +import info.logbat_view.domain.log.domain.LogData; +import info.logbat_view.domain.log.domain.enums.LogLevel; +import info.logbat_view.domain.log.repository.LogDataRepository; +import java.time.LocalDateTime; +import java.util.UUID; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import reactor.core.publisher.Flux; +import reactor.test.StepVerifier; + +@ExtendWith(MockitoExtension.class) +@DisplayName("LogService는") +class LogServiceTest { + + @InjectMocks + private LogService logService; + + @Mock + private LogDataRepository logDataRepository; + + private final Long expectedLogDataId = 1L; + private final String expectedAppKey = UUID.randomUUID().toString(); + private final LogLevel expectedLogLevel = LogLevel.ERROR; + private final String expectedData = "data"; + private final LocalDateTime expectedTimestamp = LocalDateTime.of(2024, 8, 15, 12, 0, 0, 0); + private final LogData expectedLogData = new LogData(expectedLogDataId, expectedAppKey, + expectedLogLevel.name(), expectedData, expectedTimestamp); + + @Nested + @DisplayName("AppKey로 Log들을 조회할 때") + class whenGetAppsByAppKey { + + @Test + @DisplayName("조회된 LogData들을 Log로 매핑해서 반환한다.") + void shouldReturnApps() { + // Arrange + given(logDataRepository.findByAppKey(any(String.class))).willReturn( + Flux.just(expectedLogData)); + // Act & Assert + StepVerifier.create(logService.findLogsByAppKey(expectedAppKey)) + .expectNextMatches(log -> { + assertThat(log).extracting("id", "appKey", "level", "data", "timestamp") + .containsExactly(expectedLogDataId, expectedAppKey, expectedLogLevel, + expectedData, expectedTimestamp); + return true; + }) + .verifyComplete(); + } + + } +} \ No newline at end of file