From 96ea648fcf7554cac32f4591e9969ac8ee4aa52a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=EC=A7=80=EB=AF=BC?= <108014449+stopmin@users.noreply.github.com> Date: Tue, 12 Nov 2024 13:09:33 +0900 Subject: [PATCH] =?UTF-8?q?[TEST]=20FarmSchedule=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20(#137)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: FarmScheduleServiceTest - 농장 스케쥴 추가 #125 * test: FarmScheduleServiceTest - 농장 스케쥴 추가 #113 * test: FarmScheduleServiceTest - 농장 스케쥴 추가 #113 * test: FarmScheduleServiceTest - 농장이 존재하지 않는 경우 - 이미 삭제된 경우 #113 * test: FarmScheduleServiceTest - 농장이 삭제된 경우 예외를 발생 #113 * test: FarmScheduleServiceTest - 특정 날짜의 스케쥴 조회 #113 * test: FarmScheduleServiceTest - 중복된 스케줄이 있는 경우 save 메서드가 호출되지 않는다 #113 * test: FarmScheduleServiceTest - 스케줄이 없는 날짜에 대해 빈 리스트를 반환한다 #113 * test: FarmScheduleServiceTest - 특정 월에 해당하는 스케줄을 올바르게 조회한다 - 존재하지 않는 스케줄 ID로 조회 시 예외가 발생한다 #113 * test: 테스트 환경 키값 추가 - application-test.yml #113 * build: ci-test.yml 수정 - imp 키값 추가 #113 * build: ci-test.yml 수정 - imp 키값 추가 #113 * chore: 오타 수정 #113 * test: imp-key 값 추가 #113 * test: 테스트 실패 시 로그 남기도록 #113 * test: 테스트 실패 시 로그 남기도록 - 버전 업그레이드 #113 * build: 테스트 디비 추가 #113 * build: AWS 키값 추가 #113 * test: Iamport 모킹 설정 #113 * test: spring profile 설정 #113 --- .github/workflows/ci-test.yml | 20 +- .../java/poomasi/domain/farm/entity/Farm.java | 7 +- .../service/FarmScheduleServiceTest.java | 211 ++++++++++++++++++ .../farm/service/FarmFarmerServiceTest.java | 36 +++ .../domain/farm/service/FarmServiceTest.java | 13 ++ .../payment/iamportTest/IamportTest.java | 44 ++-- .../config/s3/S3PresignedUrlServiceTest.java | 2 +- src/test/resources/application-test.yml | 16 ++ 8 files changed, 324 insertions(+), 25 deletions(-) create mode 100644 src/test/java/poomasi/domain/farm/_schedule/service/FarmScheduleServiceTest.java create mode 100644 src/test/resources/application-test.yml diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index 4e8b8de5..5b739bfb 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -11,6 +11,11 @@ permissions: jobs: test: + env: + IMP_API_KEY: ${{ secrets.IMP_API_KEY }} + IMP_SECRET_KEY: ${{ secrets.IMP_SECRET_KEY }} + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} runs-on: ubuntu-latest steps: - name: Checkout code @@ -21,6 +26,9 @@ jobs: with: java-version: '21' distribution: 'adopt' + env: + IMP_API_KEY: ${{ secrets.IMP_API_KEY }} + IMP_SECRET_KEY: ${{ secrets.IMP_SECRET_KEY }} - name: Cache Gradle packages uses: actions/cache@v3 @@ -29,12 +37,22 @@ jobs: key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: | ${{ runner.os }}-gradle- + env: + IMP_API_KEY: ${{ secrets.IMP_API_KEY }} + IMP_SECRET_KEY: ${{ secrets.IMP_SECRET_KEY }} + + - name: Upload test report + if: always() + uses: actions/upload-artifact@v3 + with: + name: test-report + path: build/reports/tests/test - name: Grant execute permission for gradlew run: chmod +x ./gradlew - name: Run tests - run: ./gradlew test + run: SPRING_PROFILES_ACTIVE=[test] ./gradlew test env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/src/main/java/poomasi/domain/farm/entity/Farm.java b/src/main/java/poomasi/domain/farm/entity/Farm.java index 22c7b6c1..9e278c2d 100644 --- a/src/main/java/poomasi/domain/farm/entity/Farm.java +++ b/src/main/java/poomasi/domain/farm/entity/Farm.java @@ -85,7 +85,7 @@ public class Farm { private OrderedFarm orderedFarm; @Builder - public Farm(Long id, String name, Long ownerId, String address, String addressDetail, Double latitude, Double longitude, String description, int experiencePrice, Integer maxCapacity, Integer maxReservation) { + public Farm(Long id, String name, Long ownerId, String address, String addressDetail, Double latitude, Double longitude, String description, int experiencePrice, Integer maxCapacity, Integer maxReservation, LocalDateTime deletedAt) { this.id = id; this.name = name; this.ownerId = ownerId; @@ -97,6 +97,7 @@ public Farm(Long id, String name, Long ownerId, String address, String addressDe this.experiencePrice = experiencePrice; this.maxCapacity = maxCapacity; this.maxReservation = maxReservation; + this.deletedAt = deletedAt; } public Farm updateFarm(FarmUpdateRequest farmUpdateRequest) { @@ -120,4 +121,8 @@ public void updateMaxCapacity(Integer maxCapacity) { public void updateMaxReservation(Integer maxReservation) { this.maxReservation = maxReservation; } + + public void delete() { + this.deletedAt = LocalDateTime.now(); + } } diff --git a/src/test/java/poomasi/domain/farm/_schedule/service/FarmScheduleServiceTest.java b/src/test/java/poomasi/domain/farm/_schedule/service/FarmScheduleServiceTest.java new file mode 100644 index 00000000..7a611950 --- /dev/null +++ b/src/test/java/poomasi/domain/farm/_schedule/service/FarmScheduleServiceTest.java @@ -0,0 +1,211 @@ +package poomasi.domain.farm._schedule.service; + +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 poomasi.domain.farm._schedule.dto.FarmScheduleRequest; +import poomasi.domain.farm._schedule.dto.FarmScheduleResponse; +import poomasi.domain.farm._schedule.dto.FarmScheduleUpdateRequest; +import poomasi.domain.farm._schedule.entity.FarmSchedule; +import poomasi.domain.farm._schedule.repository.FarmScheduleRepository; +import poomasi.global.error.BusinessException; + +import java.time.LocalDate; +import java.time.LocalTime; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static poomasi.global.error.BusinessError.*; + +@ExtendWith(MockitoExtension.class) +class FarmScheduleServiceTest { + @InjectMocks + private FarmScheduleService farmScheduleService; + + @Mock + private FarmScheduleRepository farmScheduleRepository; + + @Nested + @DisplayName("농장 스케줄 추가") + class AddFarmSchedule { + @Test + @DisplayName("정상적으로 스케줄을 추가한다") + void should_addFarmSchedule() { + // given + FarmScheduleUpdateRequest request = new FarmScheduleUpdateRequest(1L, LocalDate.now(), LocalTime.of(10, 0), LocalTime.of(12, 0)); + + given(farmScheduleRepository.findByFarmIdAndDate(1L, LocalDate.now())).willReturn(List.of()); + + // when + farmScheduleService.addFarmSchedule(request); + + // then + FarmSchedule farmSchedule = FarmSchedule.builder() + .farmId(1L) + .date(LocalDate.now()) + .startTime(LocalTime.of(10, 0)) + .endTime(LocalTime.of(12, 0)) + .build(); + assertAll( + () -> assertEquals(1L, farmSchedule.getFarmId()), + () -> assertEquals(LocalDate.now(), farmSchedule.getDate()), + () -> assertEquals(LocalTime.of(10, 0), farmSchedule.getStartTime()), + () -> assertEquals(LocalTime.of(12, 0), farmSchedule.getEndTime()) + ); + } + + @Test + @DisplayName("시작 시간이 종료 시간보다 늦은 경우 예외를 발생시킨다") + void should_throwException_when_startTimeIsAfterEndTime() { + // given + FarmScheduleUpdateRequest request = new FarmScheduleUpdateRequest(1L, LocalDate.now(), LocalTime.of(10, 0), LocalTime.of(9, 0)); + + // when & then + BusinessException exception = assertThrows(BusinessException.class, () -> farmScheduleService.addFarmSchedule(request)); + assertEquals(START_TIME_SHOULD_BE_BEFORE_END_TIME, exception.getBusinessError()); + } + + @Test + @DisplayName("이미 등록된 스케줄이 있는 경우 예외를 발생시킨다") + void should_throwException_when_scheduleAlreadyExists() { + // given + FarmSchedule farmSchedule = FarmSchedule.builder() + .startTime(LocalTime.of(10, 0)) + .endTime(LocalTime.of(12, 0)) + .build(); + List farmSchedules = List.of(farmSchedule); + + given(farmScheduleRepository.findByFarmIdAndDate(1L, LocalDate.now())).willReturn(farmSchedules); + + FarmScheduleUpdateRequest request = new FarmScheduleUpdateRequest(1L, LocalDate.now(), LocalTime.of(11, 0), LocalTime.of(13, 0)); + + // when & then + BusinessException exception = assertThrows(BusinessException.class, () -> farmScheduleService.addFarmSchedule(request)); + assertEquals(FARM_SCHEDULE_ALREADY_EXISTS, exception.getBusinessError()); + } + + @Test + @DisplayName("중복된 스케줄이 있는 경우 save 메서드가 호출되지 않는다") + void should_notCallSave_when_scheduleAlreadyExists() { + // given + FarmSchedule farmSchedule = FarmSchedule.builder() + .startTime(LocalTime.of(10, 0)) + .endTime(LocalTime.of(12, 0)) + .build(); + List farmSchedules = List.of(farmSchedule); + + given(farmScheduleRepository.findByFarmIdAndDate(1L, LocalDate.now())).willReturn(farmSchedules); + + FarmScheduleUpdateRequest request = new FarmScheduleUpdateRequest(1L, LocalDate.now(), LocalTime.of(11, 0), LocalTime.of(13, 0)); + + // when & then + assertThrows(BusinessException.class, () -> farmScheduleService.addFarmSchedule(request)); + verify(farmScheduleRepository, never()).save(any(FarmSchedule.class)); + } + } + + @Nested + @DisplayName("농장 스케줄 조회") + class GetFarmSchedules { + @Test + @DisplayName("텅 빈 스케줄을 조회한다") + void should_getEmptyFarmSchedules() { + // given + LocalDate date = LocalDate.now(); + given(farmScheduleRepository.findByFarmIdAndDate(1L, date)).willReturn(List.of()); + + // when + List farmSchedules = farmScheduleService.getFarmScheduleByFarmIdAndDate(1L, date); + + // then + assertTrue(farmSchedules.isEmpty()); + } + + @Test + @DisplayName("특정 날짜의 스케줄을 조회한다") + void should_getFarmSchedulesBySpecificDate() { + // given + LocalDate date = LocalDate.now(); + FarmSchedule farmSchedule = FarmSchedule.builder() + .farmId(1L) + .date(date) + .startTime(LocalTime.of(10, 0)) + .endTime(LocalTime.of(12, 0)) + .build(); + given(farmScheduleRepository.findByFarmIdAndDate(1L, date)).willReturn(List.of(farmSchedule)); + + // when + List farmSchedules = farmScheduleService.getFarmScheduleByFarmIdAndDate(1L, date); + + // then + assertAll( + () -> assertEquals(1, farmSchedules.size()), + () -> assertEquals(1L, farmSchedules.get(0).getFarmId()), + () -> assertEquals(date, farmSchedules.get(0).getDate()), + () -> assertEquals(LocalTime.of(10, 0), farmSchedules.get(0).getStartTime()), + () -> assertEquals(LocalTime.of(12, 0), farmSchedules.get(0).getEndTime()) + ); + } + + @Test + @DisplayName("스케줄이 없는 날짜에 대해 빈 리스트를 반환한다") + void should_returnEmptyList_when_noSchedulesOnDate() { + // given + LocalDate date = LocalDate.of(2024, 11, 12); + given(farmScheduleRepository.findByFarmIdAndDate(1L, date)).willReturn(List.of()); + + // when + List result = farmScheduleService.getFarmScheduleByFarmIdAndDate(1L, date); + + // then + assertTrue(result.isEmpty()); + } + + @Test + @DisplayName("특정 월에 해당하는 스케줄을 올바르게 조회한다") + void should_returnSchedulesWithinSpecifiedMonth() { + // given + LocalDate startDate = LocalDate.of(2024, 11, 1); + LocalDate endDate = LocalDate.of(2024, 11, 30); + FarmSchedule farmSchedule = FarmSchedule.builder() + .farmId(1L) + .date(LocalDate.of(2024, 11, 10)) + .startTime(LocalTime.of(10, 0)) + .endTime(LocalTime.of(12, 0)) + .build(); + + given(farmScheduleRepository.findByFarmIdAndDateRange(1L, startDate, endDate)).willReturn(List.of(farmSchedule)); + + // when + FarmScheduleRequest request = new FarmScheduleRequest(1L, 2024, 11); + List result = farmScheduleService.getFarmSchedulesByYearAndMonth(request); + + // then + assertEquals(1, result.size()); + assertEquals(farmSchedule.getDate(), result.get(0).date()); + assertEquals(farmSchedule.getStartTime(), result.get(0).startTime()); + assertEquals(farmSchedule.getEndTime(), result.get(0).endTime()); + } + + @Test + @DisplayName("존재하지 않는 스케줄 ID로 조회 시 예외가 발생한다") + void should_throwException_when_scheduleIdNotFound() { + // given + Long invalidId = 999L; + given(farmScheduleRepository.findById(invalidId)).willReturn(Optional.empty()); + + // when & then + BusinessException exception = assertThrows(BusinessException.class, () -> farmScheduleService.getFarmScheduleByScheduleId(invalidId)); + assertEquals(FARM_SCHEDULE_NOT_FOUND, exception.getBusinessError()); + } + } +} diff --git a/src/test/java/poomasi/domain/farm/service/FarmFarmerServiceTest.java b/src/test/java/poomasi/domain/farm/service/FarmFarmerServiceTest.java index 3fc65918..b6d3ac62 100644 --- a/src/test/java/poomasi/domain/farm/service/FarmFarmerServiceTest.java +++ b/src/test/java/poomasi/domain/farm/service/FarmFarmerServiceTest.java @@ -137,5 +137,41 @@ void should_deleteFarm_when_ownerMatches() { // then verify(farmRepository).delete(farm); } + + @Test + @DisplayName("농장이 존재하지 않는 경우 예외를 발생시킨다") + void should_throwException_when_farmNotExistOnDelete() { + // given + Long farmId = 1L; + Long farmerId = 1L; + given(farmRepository.findByIdAndDeletedAtIsNull(farmId)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> farmFarmerService.deleteFarm(farmerId, farmId)) + .isInstanceOf(BusinessException.class) + .hasFieldOrPropertyWithValue("businessError", BusinessError.FARM_NOT_FOUND); + } + + @Test + @DisplayName("농장이 이미 삭제된 경우 예외를 발생시킨다") + void should_throwException_when_farmAlreadyDeleted() { + // given + Long farmId = 1L; + Long farmerId = 1L; + Farm farm = Farm.builder() + .id(farmId) + .name("Farm") + .ownerId(farmerId) + .deletedAt(null) + .build(); + + given(farmRepository.findByIdAndDeletedAtIsNull(farmId)).willReturn(Optional.of(farm)); + + // when + farmFarmerService.deleteFarm(farmerId, farmId); + + // then + verify(farmRepository).delete(farm); + } } } diff --git a/src/test/java/poomasi/domain/farm/service/FarmServiceTest.java b/src/test/java/poomasi/domain/farm/service/FarmServiceTest.java index cc9603f0..a24ae9a0 100644 --- a/src/test/java/poomasi/domain/farm/service/FarmServiceTest.java +++ b/src/test/java/poomasi/domain/farm/service/FarmServiceTest.java @@ -58,6 +58,19 @@ void should_throwException_when_farmNotExist() { .isInstanceOf(BusinessException.class) .hasFieldOrPropertyWithValue("businessError", BusinessError.FARM_NOT_FOUND); } + + @Test + @DisplayName("농장이 삭제된 경우 예외를 발생시킨다") + void should_throwException_when_farmIsDeleted() { + // given + Farm farm = FarmTestHelper.makeRandomFarm(); + farm.delete(); + + // when & then + assertThatThrownBy(() -> farmService.getFarmByFarmId(farm.getId())) + .isInstanceOf(BusinessException.class) + .hasFieldOrPropertyWithValue("businessError", BusinessError.FARM_NOT_FOUND); + } } } diff --git a/src/test/java/poomasi/domain/payment/iamportTest/IamportTest.java b/src/test/java/poomasi/domain/payment/iamportTest/IamportTest.java index 2d3df7bd..9d0ac2f4 100644 --- a/src/test/java/poomasi/domain/payment/iamportTest/IamportTest.java +++ b/src/test/java/poomasi/domain/payment/iamportTest/IamportTest.java @@ -3,57 +3,57 @@ import com.siot.IamportRestClient.IamportClient; import com.siot.IamportRestClient.exception.IamportResponseException; import com.siot.IamportRestClient.request.PrepareData; -import com.siot.IamportRestClient.response.AccessToken; import com.siot.IamportRestClient.response.IamportResponse; -import com.siot.IamportRestClient.response.Payment; import com.siot.IamportRestClient.response.Prepare; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.web.client.RestTemplate; -import poomasi.domain.order._payment.dto.request.PaymentPreRegisterRequest; -import poomasi.domain.order._payment.dto.response.PaymentPreRegisterResponse; - +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; import java.io.IOException; import java.math.BigDecimal; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; -import static com.fasterxml.jackson.databind.type.LogicalType.DateTime; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +@SpringBootTest +@ActiveProfiles("test") +@TestPropertySource(locations = "classpath:application-test.yml") public class IamportTest { + @MockBean private IamportClient iamportClient; - private String apiKey="~"; - private String secretKey="~"; - private String accessToken; + @Value("${imp.api.key}") + private String apiKey; + + @Value("${imp.api.secretKey}") + private String secretKey; @BeforeEach - public void setUp() throws Exception { + public void setUp() { this.iamportClient = new IamportClient(apiKey, secretKey); - IamportResponse auth_response = iamportClient.getAuth(); - this.accessToken=auth_response.getResponse().getToken(); } @Test - public void portonePrePaymentRegister_Test() throws IamportResponseException ,IOException{ + public void portonePrePaymentRegister_Test() throws IamportResponseException, IOException { String merchantUid = "poomasi_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); BigDecimal amount = new BigDecimal("100"); PrepareData prepareData = new PrepareData(merchantUid, amount); - System.out.println(merchantUid); + // `iamportClient.postPrepare` 모킹 설정 + IamportResponse mockResponse = new IamportResponse<>(); + given(iamportClient.postPrepare(any(PrepareData.class))).willReturn(mockResponse); + + // 테스트 실행 IamportResponse prepareIamportResponse = iamportClient.postPrepare(prepareData); System.out.println("Response Code: " + prepareIamportResponse.getCode()); System.out.println("Response Message: " + prepareIamportResponse.getMessage()); - } - } diff --git a/src/test/java/poomasi/global/config/s3/S3PresignedUrlServiceTest.java b/src/test/java/poomasi/global/config/s3/S3PresignedUrlServiceTest.java index 7bf948ae..d98d9ea6 100644 --- a/src/test/java/poomasi/global/config/s3/S3PresignedUrlServiceTest.java +++ b/src/test/java/poomasi/global/config/s3/S3PresignedUrlServiceTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; import poomasi.global.config.aws.AwsProperties; import poomasi.global.util.EncryptionUtil; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; @@ -18,7 +19,6 @@ @SpringBootTest public class S3PresignedUrlServiceTest { - private S3PresignedUrlService s3PresignedUrlService; @Autowired diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml new file mode 100644 index 00000000..51b36f18 --- /dev/null +++ b/src/test/resources/application-test.yml @@ -0,0 +1,16 @@ +spring: + datasource: + url: jdbc:h2:mem:testdb + driver-class-name: org.h2.Driver + username: sa + password: + jpa: + hibernate: + ddl-auto: update + show-sql: true + +imp: + api: + key: ${IMP_API_KEY} + secretKey: ${IMP_SECRET_KEY} +