-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: App token 추가 - App 생성시 UUID 기반 토큰이 생성되도록 했습니다. - 이에 따른 테스트를 수정했습니다. * feat: App 생성 로직 구현 - 서비스 - App 생성을 진행하고 DB 저장후 결과를 반환하는 서비스 로직을 구현했습니다. - 요청에 대한 반환은 `AppCommonResponse` DTO를 통해 반환됩니다. * feat: App 조회 로직 구현 - 서비스 - Token을 활용해 App을 조회하는 로직을 구현했습니다. - ID를 활용해 App을 조회하는 로직을 구현했습니다. - 이에 따른 테스트를 추가했습니다. * chore: 인덱싱 추가 - 토큰 기반 조회 쿼리 성능 향상을 위해 인덱싱을 명시했습니다. * feat: App 삭제 로직 구현 - 서비스 - App 삭제를 진행하고 성공시 삭제한 App Id를 반환하도록 했습니다. * feat: App 서비스 트랜젝션 추가 - App 서비스 메서드들에 대해 `@Transactional`을 적용했습니다.
- Loading branch information
Showing
6 changed files
with
280 additions
and
5 deletions.
There are no files selected for viewing
55 changes: 55 additions & 0 deletions
55
logbat/src/main/java/info/logbat/domain/project/application/AppService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package info.logbat.domain.project.application; | ||
|
||
import info.logbat.domain.project.domain.App; | ||
import info.logbat.domain.project.domain.Project; | ||
import info.logbat.domain.project.domain.enums.AppType; | ||
import info.logbat.domain.project.presentation.payload.response.AppCommonResponse; | ||
import info.logbat.domain.project.repository.AppJpaRepository; | ||
import info.logbat.domain.project.repository.ProjectJpaRepository; | ||
import java.util.UUID; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@Service | ||
@Transactional | ||
@RequiredArgsConstructor | ||
public class AppService { | ||
|
||
private static final String APP_NOT_FOUND_MESSAGE = "앱을 찾을 수 없습니다."; | ||
|
||
private final AppJpaRepository appRepository; | ||
private final ProjectJpaRepository projectRepository; | ||
|
||
public AppCommonResponse createApp(Long projectId, AppType appType) { | ||
Project project = getProject(projectId); | ||
return AppCommonResponse.from(appRepository.save(App.of(project, appType))); | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public AppCommonResponse getAppByToken(String token) { | ||
UUID tokenUUID = UUID.fromString(token); | ||
App app = appRepository.findByToken(tokenUUID) | ||
.orElseThrow(() -> new IllegalArgumentException(APP_NOT_FOUND_MESSAGE)); | ||
return AppCommonResponse.from(app); | ||
} | ||
|
||
@Transactional(readOnly = true) | ||
public AppCommonResponse getAppById(Long id) { | ||
App app = appRepository.findById(id) | ||
.orElseThrow(() -> new IllegalArgumentException(APP_NOT_FOUND_MESSAGE)); | ||
return AppCommonResponse.from(app); | ||
} | ||
|
||
public Long deleteApp(Long projectId, Long appId) { | ||
App app = appRepository.findByProject_IdAndId(projectId, appId) | ||
.orElseThrow(() -> new IllegalArgumentException(APP_NOT_FOUND_MESSAGE)); | ||
appRepository.delete(app); | ||
return app.getId(); | ||
} | ||
|
||
private Project getProject(Long id) { | ||
return projectRepository.findById(id) | ||
.orElseThrow(() -> new IllegalArgumentException("프로젝트를 찾을 수 없습니다.")); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
...main/java/info/logbat/domain/project/presentation/payload/response/AppCommonResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package info.logbat.domain.project.presentation.payload.response; | ||
|
||
import info.logbat.domain.project.domain.App; | ||
import java.time.LocalDateTime; | ||
|
||
public record AppCommonResponse(Long id, Long projectId, String appType, String token, | ||
LocalDateTime createdAt) { | ||
|
||
public static AppCommonResponse from(App app) { | ||
return new AppCommonResponse(app.getId(), app.getProject().getId(), app.getAppType().name(), | ||
app.getToken().toString(), app.getCreatedAt()); | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
logbat/src/main/java/info/logbat/domain/project/repository/AppJpaRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package info.logbat.domain.project.repository; | ||
|
||
import info.logbat.domain.project.domain.App; | ||
import java.util.Optional; | ||
import java.util.UUID; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.lang.NonNull; | ||
import org.springframework.stereotype.Repository; | ||
|
||
@Repository | ||
public interface AppJpaRepository extends JpaRepository<App, Long> { | ||
|
||
Optional<App> findByToken(@NonNull UUID token); | ||
|
||
Optional<App> findByProject_IdAndId(@NonNull Long id, @NonNull Long id1); | ||
} |
178 changes: 178 additions & 0 deletions
178
logbat/src/test/java/info/logbat/domain/project/application/AppServiceTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
package info.logbat.domain.project.application; | ||
|
||
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; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.spy; | ||
|
||
import info.logbat.domain.project.domain.App; | ||
import info.logbat.domain.project.domain.Project; | ||
import info.logbat.domain.project.domain.enums.AppType; | ||
import info.logbat.domain.project.presentation.payload.response.AppCommonResponse; | ||
import info.logbat.domain.project.repository.AppJpaRepository; | ||
import info.logbat.domain.project.repository.ProjectJpaRepository; | ||
import java.time.LocalDateTime; | ||
import java.util.Optional; | ||
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; | ||
|
||
@ExtendWith(MockitoExtension.class) | ||
@DisplayName("AppService는") | ||
class AppServiceTest { | ||
|
||
@InjectMocks | ||
private AppService appService; | ||
|
||
@Mock | ||
private AppJpaRepository appRepository; | ||
@Mock | ||
private ProjectJpaRepository projectRepository; | ||
|
||
private final Project expectedProject = mock(Project.class); | ||
private final App expectedApp = spy(App.of(expectedProject, AppType.JAVA)); | ||
private final Long expectedProjectId = 1L; | ||
private final Long expectedAppId = 1L; | ||
|
||
@Nested | ||
@DisplayName("새로운 App을 생성할 때") | ||
class whenCreateNewApp { | ||
|
||
@Test | ||
@DisplayName("프로젝트가 존재하지 않으면 IllegalArgumentException을 던진다.") | ||
void willThrowExceptionWhenProjectNotFound() { | ||
// Arrange | ||
Long notExistProjectId = 2L; | ||
given(projectRepository.findById(notExistProjectId)).willReturn(Optional.empty()); | ||
// Act & Assert | ||
assertThatThrownBy(() -> appService.createApp(notExistProjectId, AppType.JAVA)) | ||
.isInstanceOf(IllegalArgumentException.class) | ||
.hasMessage("프로젝트를 찾을 수 없습니다."); | ||
} | ||
|
||
@Test | ||
@DisplayName("정상적으로 생성한다.") | ||
void willCreateNewApp() { | ||
// Arrange | ||
LocalDateTime expectedCreatedAt = LocalDateTime.now(); | ||
given(projectRepository.findById(expectedProjectId)).willReturn( | ||
Optional.of(expectedProject)); | ||
given(expectedProject.getId()).willReturn(expectedProjectId); | ||
given(appRepository.save(any(App.class))).willReturn(expectedApp); | ||
given(expectedApp.getId()).willReturn(expectedAppId); | ||
given(expectedApp.getCreatedAt()).willReturn(expectedCreatedAt); | ||
// Act | ||
AppCommonResponse actualResult = appService.createApp(expectedProjectId, AppType.JAVA); | ||
// Assert | ||
assertAll( | ||
() -> assertThat(actualResult) | ||
.extracting("id", "projectId", "appType", "createdAt") | ||
.containsExactly(expectedAppId, expectedProjectId, AppType.JAVA.name(), | ||
expectedCreatedAt), | ||
() -> assertThat(actualResult.token()).isNotNull() | ||
); | ||
|
||
} | ||
|
||
} | ||
|
||
@Nested | ||
@DisplayName("App을 조회할 때") | ||
class whenGetApp { | ||
|
||
private final UUID expectedToken = UUID.randomUUID(); | ||
private final App expectedApp = spy(App.of(expectedProject, AppType.JAVA)); | ||
|
||
@Test | ||
@DisplayName("토큰으로 조회할 수 있다.") | ||
void canGetAppByToken() { | ||
// Arrange | ||
String expectedTokenString = expectedToken.toString(); | ||
given(appRepository.findByToken(expectedToken)).willReturn(Optional.of(expectedApp)); | ||
given(expectedApp.getToken()).willReturn(expectedToken); | ||
// Act | ||
AppCommonResponse actualResult = appService.getAppByToken(expectedTokenString); | ||
// Assert | ||
assertThat(actualResult) | ||
.extracting("token") | ||
.isEqualTo(expectedTokenString); | ||
} | ||
|
||
@Test | ||
@DisplayName("ID로 조회할 수 있다.") | ||
void canGetAppById() { | ||
// Arrange | ||
given(appRepository.findById(expectedAppId)).willReturn(Optional.of(expectedApp)); | ||
given(expectedApp.getId()).willReturn(expectedAppId); | ||
// Act | ||
AppCommonResponse actualResult = appService.getAppById(expectedAppId); | ||
// Assert | ||
assertThat(actualResult) | ||
.extracting("id") | ||
.isEqualTo(expectedAppId); | ||
} | ||
|
||
@Test | ||
@DisplayName("ID 조회시 앱을 찾을 수 없으면 예외를 던진다.") | ||
void willThrowExceptionWhenAppNotFoundById() { | ||
// Arrange | ||
given(appRepository.findById(expectedAppId)).willReturn(Optional.empty()); | ||
// Act & Assert | ||
assertThatThrownBy(() -> appService.getAppById(expectedAppId)) | ||
.isInstanceOf(IllegalArgumentException.class) | ||
.hasMessage("앱을 찾을 수 없습니다."); | ||
} | ||
|
||
@Test | ||
@DisplayName("토큰 조회시 앱을 찾을 수 없으면 예외를 던진다.") | ||
void willThrowExceptionWhenAppNotFoundByToken() { | ||
// Arrange | ||
String notExistToken = UUID.randomUUID().toString(); | ||
// Arrange | ||
given(appRepository.findByToken(any(UUID.class))).willReturn(Optional.empty()); | ||
// Act & Assert | ||
assertThatThrownBy(() -> appService.getAppByToken(notExistToken)) | ||
.isInstanceOf(IllegalArgumentException.class) | ||
.hasMessage("앱을 찾을 수 없습니다."); | ||
} | ||
} | ||
|
||
@Nested | ||
@DisplayName("App을 삭제할 때") | ||
class whenDeleteApp { | ||
|
||
@Test | ||
@DisplayName("프로젝트와 앱 ID로 삭제할 수 있다.") | ||
void canDeleteAppByProjectIdAndAppId() { | ||
// Arrange | ||
given(appRepository.findByProject_IdAndId(expectedProjectId, expectedAppId)).willReturn( | ||
Optional.of(expectedApp)); | ||
given(expectedApp.getId()).willReturn(expectedAppId); | ||
// Act | ||
Long actualResult = appService.deleteApp(expectedProjectId, expectedAppId); | ||
// Assert | ||
assertThat(actualResult).isEqualTo(expectedAppId); | ||
} | ||
|
||
@Test | ||
@DisplayName("앱을 찾을 수 없으면 예외를 던진다.") | ||
void willThrowExceptionWhenAppNotFound() { | ||
// Arrange | ||
given(appRepository.findByProject_IdAndId(expectedProjectId, expectedAppId)).willReturn( | ||
Optional.empty()); | ||
// Act & Assert | ||
assertThatThrownBy(() -> appService.deleteApp(expectedProjectId, expectedAppId)) | ||
.isInstanceOf(IllegalArgumentException.class) | ||
.hasMessage("앱을 찾을 수 없습니다."); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters