diff --git a/pom.xml b/pom.xml index 2adffac..ea2387a 100644 --- a/pom.xml +++ b/pom.xml @@ -13,6 +13,7 @@ 17 17 1.18.22 + 5.10.1 @@ -25,7 +26,7 @@ org.postgresql postgresql - 42.3.3 + 42.5.1 runtime @@ -39,13 +40,32 @@ org.junit.jupiter junit-jupiter-engine - 5.8.2 + ${junit5.version} + test + + + + org.assertj + assertj-core + 3.25.2 + test + + + org.junit.jupiter + junit-jupiter-params + ${junit5.version} test com.h2database h2 - 2.1.210 + 2.2.220 + test + + + org.mockito + mockito-junit-jupiter + 4.11.0 test diff --git a/src/test/java/com/dmdev/dao/SubscriptionDaoIT.java b/src/test/java/com/dmdev/dao/SubscriptionDaoIT.java new file mode 100644 index 0000000..3133e46 --- /dev/null +++ b/src/test/java/com/dmdev/dao/SubscriptionDaoIT.java @@ -0,0 +1,97 @@ +package com.dmdev.dao; + +import com.dmdev.entity.Provider; +import com.dmdev.entity.Status; +import com.dmdev.entity.Subscription; +import com.dmdev.integration.IntegrationTestBase; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Optional; + +import static com.dmdev.util.DateUtil.getExpirationDate; +import static org.assertj.core.api.Assertions.assertThat; + +class SubscriptionDaoIT extends IntegrationTestBase { + + private final SubscriptionDao subDao = SubscriptionDao.getInstance(); + + @Test + void whenFindAll_thenAllSubsFound() { + Subscription sub1 = subDao.insert(getSubscription("User 1", 1)); + Subscription sub2 = subDao.insert(getSubscription("User 2", 2)); + Subscription sub3 = subDao.insert(getSubscription("User 3", 3)); + + List actualSub = subDao.findAll(); + List userIds = actualSub.stream().map(Subscription::getUserId).toList(); + + assertThat(userIds).containsExactly(sub1.getUserId(), sub2.getUserId(), sub3.getUserId()); + } + + @Test + void whenSubExists_thenItIsFindById() { + Subscription expectedSub = subDao.insert(getSubscription("User 1", 1)); + + Optional actualSub = subDao.findById(expectedSub.getId()); + + assertThat(actualSub).isPresent(); + assertThat(actualSub.get()).isEqualTo(expectedSub); + } + + @Test + void whenSubExists_thenItIsDeleted() { + Subscription savedSub = subDao.insert(getSubscription("User 1", 1)); + boolean isDeleted = subDao.delete(savedSub.getId()); + + assertThat(isDeleted).isTrue(); + } + + @Test + void whenSubDoesNotExist_thenItIsNotDeleted() { + subDao.insert(getSubscription("User 1", 1)); + boolean isDeleted = subDao.delete(11); + + assertThat(isDeleted).isFalse(); + } + + @Test + void update() { + Subscription insertedSub = subDao.insert(getSubscription("User 1", 1)); + insertedSub.setStatus(Status.CANCELED); + insertedSub.setExpirationDate(getExpirationDate("13:50:00, 15.03.2024", "H:m:s, d.M.y")); + subDao.update(insertedSub); + + Optional foundSub = subDao.findById(insertedSub.getId()); + + assertThat(insertedSub).isEqualTo(foundSub.get()); + + } + + @Test + void insert() { + Subscription expectedSub = getSubscription("User 1", 1); + Subscription actualSub = subDao.insert(expectedSub); + + assertThat(actualSub.getId()).isNotNull(); + } + + @Test + void findByUserId() { + Subscription expectedSub = subDao.insert(getSubscription("User 1", 1)); + + List actualSubs = subDao.findByUserId(expectedSub.getUserId()); + + assertThat(actualSubs.size()).isEqualTo(1); + assertThat(actualSubs).containsExactly(expectedSub); + } + + private Subscription getSubscription(String userName, int userId) { + return Subscription.builder() + .userId(userId) + .name(userName) + .provider(Provider.APPLE) + .expirationDate(getExpirationDate("23:59:59, 31.03.2024", "H:m:s, d.M.y")) + .status(Status.ACTIVE) + .build(); + } +} \ No newline at end of file diff --git a/src/test/java/com/dmdev/mapper/CreateSubscriptionMapperTest.java b/src/test/java/com/dmdev/mapper/CreateSubscriptionMapperTest.java new file mode 100644 index 0000000..d6104bf --- /dev/null +++ b/src/test/java/com/dmdev/mapper/CreateSubscriptionMapperTest.java @@ -0,0 +1,39 @@ +package com.dmdev.mapper; + +import com.dmdev.dto.CreateSubscriptionDto; +import com.dmdev.entity.Provider; +import com.dmdev.entity.Status; +import com.dmdev.entity.Subscription; +import org.junit.jupiter.api.Test; + +import static com.dmdev.util.DateUtil.getExpirationDate; +import static org.assertj.core.api.Assertions.assertThat; + +class CreateSubscriptionMapperTest { + + private final CreateSubscriptionMapper mapper = CreateSubscriptionMapper.getInstance(); + + @Test + void whenValidDto_thenValidSubscriptionCreated() { + String date = "21:00:00, 30.04.2024"; + String datePattern = "H:m:s, d.M.y"; + CreateSubscriptionDto validDto = CreateSubscriptionDto.builder() + .userId(1) + .name("Test Name") + .provider(Provider.GOOGLE.name()) + .expirationDate(getExpirationDate(date, datePattern)) + .build(); + + Subscription actualSub = mapper.map(validDto); + Subscription expectedSub = Subscription.builder() + .userId(1) + .name("Test Name") + .provider(Provider.GOOGLE) + .expirationDate(getExpirationDate(date, datePattern)) + .status(Status.ACTIVE) + .build(); + + assertThat(actualSub).isEqualTo(expectedSub); + + } +} \ No newline at end of file diff --git a/src/test/java/com/dmdev/service/SubscriptionServiceTest.java b/src/test/java/com/dmdev/service/SubscriptionServiceTest.java new file mode 100644 index 0000000..9db57e1 --- /dev/null +++ b/src/test/java/com/dmdev/service/SubscriptionServiceTest.java @@ -0,0 +1,149 @@ +package com.dmdev.service; + +import com.dmdev.dao.SubscriptionDao; +import com.dmdev.dto.CreateSubscriptionDto; +import com.dmdev.entity.Provider; +import com.dmdev.entity.Status; +import com.dmdev.entity.Subscription; +import com.dmdev.exception.SubscriptionException; +import com.dmdev.exception.ValidationException; +import com.dmdev.mapper.CreateSubscriptionMapper; +import com.dmdev.validator.CreateSubscriptionValidator; +import com.dmdev.validator.Error; +import com.dmdev.validator.ValidationResult; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.AdditionalAnswers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; +import java.util.Collections; +import java.util.Optional; + +import static com.dmdev.util.DateUtil.getExpirationDate; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class SubscriptionServiceTest { + + @Mock + private SubscriptionDao subDao; + @Mock + private CreateSubscriptionMapper createSubMapper; + @Mock + private CreateSubscriptionValidator createSubValidator; + @Mock + private Clock clock = Clock.fixed(Instant.now(), ZoneId.systemDefault()); + + @InjectMocks + private SubscriptionService subService; // ); + + @Test + void whenSubscriptionExists_thenUpdateAndInsert() { + CreateSubscriptionDto dto = CreateSubscriptionDto.builder() + .userId(23) + .name("Test Name") + .provider(Provider.APPLE.name()) + .expirationDate(getExpirationDate("21:00:00, 30.04.2024", "H:m:s, d.M.y")) + .build(); + + Subscription existingSub = Subscription.builder() + .userId(23) + .name("Test Name") + .provider(Provider.APPLE) + .expirationDate(getExpirationDate("23:59:59, 31.03.2024", "H:m:s, d.M.y")) + .build(); + + Subscription updatedSub = Subscription.builder() + .userId(23) + .name("Test Name") + .provider(Provider.APPLE) + .expirationDate(dto.getExpirationDate()) + .status(Status.ACTIVE) + .build(); + + doReturn(new ValidationResult()).when(createSubValidator).validate(dto); + doReturn(Collections.singletonList(existingSub)).when(subDao).findByUserId(dto.getUserId()); + + // Ask stub DAO.upsert() method to return whichever argument it receives + when(subDao.upsert(any(Subscription.class))).then(AdditionalAnswers.returnsFirstArg()); + + assertThat(subService.upsert(dto)).isEqualTo(updatedSub); + verify(subDao).upsert(existingSub); + + } + + @Test + void whenInvalidDto_thenExceptionThrown() { + CreateSubscriptionDto invalidDto = CreateSubscriptionDto.builder().build(); + ValidationResult invalidResult = new ValidationResult(); + invalidResult.add(Error.of(100, "userId is invalid")); + doReturn(invalidResult).when(createSubValidator).validate(invalidDto); + + assertThrows(ValidationException.class, () -> subService.upsert(invalidDto)); + verifyNoInteractions(subDao, createSubMapper); + } + + @Test + void whenActiveSubscriptionExists_thenCancelIt() { + Subscription existingSub = Subscription.builder() + .id(243) + .userId(23) + .name("Test Name") + .provider(Provider.APPLE) + .expirationDate(getExpirationDate("23:59:59, 31.03.2024", "H:m:s, d.M.y")) + .status(Status.ACTIVE) + .build(); + + doReturn(Optional.of(existingSub)).when(subDao).findById(any()); + // Ask stub DAO.update() method to return whichever argument it receives + when(subDao.update(any(Subscription.class))).then(AdditionalAnswers.returnsFirstArg()); + + subService.cancel(existingSub.getId()); + + assertThat(existingSub.getStatus()).isEqualTo(Status.CANCELED); + verify(subDao).update(existingSub); + } + + @Test + void whenSubIdNotExists_thenExceptionThrown() { + doReturn(Optional.empty()).when(subDao).findById(anyInt()); + assertThrows(IllegalArgumentException.class, () -> subService.cancel(anyInt())); + } + + @Test + void whenSubIsActive_thenExceptionThrown() { + doReturn(Optional.of(Subscription.builder().status(Status.EXPIRED).build())) + .when(subDao).findById(anyInt()); + assertThrows(SubscriptionException.class, () -> subService.cancel(anyInt())); + } + + @Test + void whenNonExpiredSubscriptionExists_thenExpireIt() { + Subscription existingSub = Subscription.builder() + .id(243) + .userId(23) + .name("Test Name") + .provider(Provider.APPLE) + .expirationDate(getExpirationDate("23:59:59, 31.03.2024", "H:m:s, d.M.y")) + .status(Status.ACTIVE) + .build(); + + doReturn(Optional.of(existingSub)).when(subDao).findById(any()); + // Ask stub DAO.update() method to return whichever argument it receives + when(subDao.update(any(Subscription.class))).then(AdditionalAnswers.returnsFirstArg()); + + subService.expire(existingSub.getId()); + + assertThat(existingSub.getStatus()).isEqualTo(Status.EXPIRED); + assertThat(existingSub.getExpirationDate()).isEqualTo(Instant.now(clock)); + verify(subDao).update(existingSub); + } +} \ No newline at end of file diff --git a/src/test/java/com/dmdev/util/DateUtil.java b/src/test/java/com/dmdev/util/DateUtil.java new file mode 100644 index 0000000..85934b7 --- /dev/null +++ b/src/test/java/com/dmdev/util/DateUtil.java @@ -0,0 +1,18 @@ +package com.dmdev.util; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +public class DateUtil { + public static Instant getExpirationDate(String stringDate, String pattern) { + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(pattern, Locale.getDefault()); + LocalDateTime localDateTime = LocalDateTime.parse(stringDate, dateTimeFormatter); + ZoneId zoneId = ZoneId.systemDefault(); + ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId); + return zonedDateTime.toInstant(); + } +} diff --git a/src/test/java/com/dmdev/util/PropertiesUtilTest.java b/src/test/java/com/dmdev/util/PropertiesUtilTest.java new file mode 100644 index 0000000..1cb1a99 --- /dev/null +++ b/src/test/java/com/dmdev/util/PropertiesUtilTest.java @@ -0,0 +1,29 @@ +package com.dmdev.util; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; + +class PropertiesUtilTest { + + public static Stream propertiesProvider() { + return Stream.of( + arguments("db.url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"), + arguments("db.user", "sa"), + arguments("db.driver", "org.h2.Driver") + ); + } + + @ParameterizedTest + @MethodSource("propertiesProvider") + void whenPropertiesProvided_thenPropertiesSet(String key, String expectedValue) { + String actualValue = PropertiesUtil.get(key); + assertThat(actualValue).isEqualTo(expectedValue); + } + +} \ No newline at end of file diff --git a/src/test/java/com/dmdev/validator/CreateSubscriptionValidatorTest.java b/src/test/java/com/dmdev/validator/CreateSubscriptionValidatorTest.java new file mode 100644 index 0000000..111c3ad --- /dev/null +++ b/src/test/java/com/dmdev/validator/CreateSubscriptionValidatorTest.java @@ -0,0 +1,128 @@ +package com.dmdev.validator; + +import com.dmdev.dto.CreateSubscriptionDto; +import com.dmdev.entity.Provider; +import org.junit.jupiter.api.Test; + +import java.util.stream.Collectors; + +import static com.dmdev.util.DateUtil.getExpirationDate; +import static org.assertj.core.api.Assertions.assertThat; + +class CreateSubscriptionValidatorTest { + + private final CreateSubscriptionValidator validator = CreateSubscriptionValidator.getInstance(); + + @Test + void whenValidDto_thenValidationSuccessful() { + CreateSubscriptionDto validDto = CreateSubscriptionDto.builder() + .userId(1) + .name("Test Name") + .provider(Provider.APPLE.name()) + .expirationDate(getExpirationDate("21:00:00, 30.04.2024", "H:m:s, d.M.y")) + .build(); + + ValidationResult actualResult = validator.validate(validDto); + + assertThat(actualResult.hasErrors()).isFalse(); + + } + + @Test + void whenUserIdNull_thenValidationFails() { + CreateSubscriptionDto invalidDto = CreateSubscriptionDto.builder() + .name("Test Name") + .provider(Provider.APPLE.name()) + .expirationDate(getExpirationDate("21:00:00, 30.04.2024", "H:m:s, d.M.y")) + .build(); + + ValidationResult actualResult = validator.validate(invalidDto); + + assertThat(actualResult.hasErrors()).isTrue(); + assertThat(actualResult.getErrors().size()).isEqualTo(1); + assertThat(actualResult.getErrors().get(0).getCode()).isEqualTo(100); + assertThat(actualResult.getErrors().get(0).getMessage()).isEqualTo("userId is invalid"); + } + + @Test + void whenNameIsBlank_thenValidationFails() { + CreateSubscriptionDto invalidDto = CreateSubscriptionDto.builder() + .userId(1) + .provider(Provider.APPLE.name()) + .expirationDate(getExpirationDate("21:00:00, 30.04.2024", "H:m:s, d.M.y")) + .build(); + + ValidationResult actualResult = validator.validate(invalidDto); + + assertThat(actualResult.hasErrors()).isTrue(); + assertThat(actualResult.getErrors().size()).isEqualTo(1); + assertThat(actualResult.getErrors().get(0).getCode()).isEqualTo(101); + assertThat(actualResult.getErrors().get(0).getMessage()).isEqualTo("name is invalid"); + } + + @Test + void whenProviderIsEmpty_thenValidationFails() { + CreateSubscriptionDto invalidDto = CreateSubscriptionDto.builder() + .userId(1) + .name("Test Name") + .expirationDate(getExpirationDate("21:00:00, 30.04.2024", "H:m:s, d.M.y")) + .build(); + + ValidationResult actualResult = validator.validate(invalidDto); + + assertThat(actualResult.hasErrors()).isTrue(); + assertThat(actualResult.getErrors().size()).isEqualTo(1); + assertThat(actualResult.getErrors().get(0).getCode()).isEqualTo(102); + assertThat(actualResult.getErrors().get(0).getMessage()).isEqualTo("provider is invalid"); + } + + @Test + void whenExpirationDateIsAbsent_thenValidationFails() { + CreateSubscriptionDto invalidDto = CreateSubscriptionDto.builder() + .userId(1) + .name("Test Name") + .provider(Provider.APPLE.name()) + .build(); + + ValidationResult actualResult = validator.validate(invalidDto); + + assertThat(actualResult.hasErrors()).isTrue(); + assertThat(actualResult.getErrors().size()).isEqualTo(1); + assertThat(actualResult.getErrors().get(0).getCode()).isEqualTo(103); + assertThat(actualResult.getErrors().get(0).getMessage()).isEqualTo("expirationDate is invalid"); + } + + @Test + void whenExpirationDateIsInPast_thenValidationFails() { + CreateSubscriptionDto invalidDto = CreateSubscriptionDto.builder() + .userId(1) + .name("Test Name") + .provider(Provider.APPLE.name()) + .expirationDate(getExpirationDate("21:00:00, 30.04.2023", "H:m:s, d.M.y")) + .build(); + + ValidationResult actualResult = validator.validate(invalidDto); + + assertThat(actualResult.hasErrors()).isTrue(); + assertThat(actualResult.getErrors().size()).isEqualTo(1); + assertThat(actualResult.getErrors().get(0).getCode()).isEqualTo(103); + assertThat(actualResult.getErrors().get(0).getMessage()).isEqualTo("expirationDate is invalid"); + } + + @Test + void whenAllCheckPointsFail_thenValidationFails() { + CreateSubscriptionDto invalidDto = CreateSubscriptionDto.builder() + .expirationDate(getExpirationDate("21:00:00, 30.04.2023", "H:m:s, d.M.y")) + .build(); + + ValidationResult actualResult = validator.validate(invalidDto); + + assertThat(actualResult.hasErrors()).isTrue(); + assertThat(actualResult.getErrors().size()).isEqualTo(4); + assertThat(actualResult.getErrors().stream().map(Error::getCode).collect(Collectors.toList())) + .containsExactly(100, 101, 102, 103); + assertThat(actualResult.getErrors().stream().map(Error::getMessage).collect(Collectors.toList())) + .containsExactly("userId is invalid", "name is invalid", "provider is invalid", "expirationDate is invalid"); + } + +} \ No newline at end of file