diff --git a/pom.xml b/pom.xml index 2adffac..1143015 100644 --- a/pom.xml +++ b/pom.xml @@ -1,115 +1,132 @@ - 4.0.0 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - com.dmdev - junit5-trainer - 1.0-SNAPSHOT + com.dmdev + junit5-trainer + 1.0-SNAPSHOT - - UTF-8 - 17 - 17 - 1.18.22 - + + UTF-8 + 17 + 17 + 1.18.22 + - - - org.apache.commons - commons-lang3 - 3.12.0 - + + + org.apache.commons + commons-lang3 + 3.12.0 + - - org.postgresql - postgresql - 42.3.3 - runtime - + + org.postgresql + postgresql + 42.7.2 + runtime + - - org.projectlombok - lombok - ${lombok.version} - provided - + + org.projectlombok + lombok + ${lombok.version} + provided + - - org.junit.jupiter - junit-jupiter-engine - 5.8.2 - test - - - com.h2database - h2 - 2.1.210 - test - - + + org.junit.jupiter + junit-jupiter-engine + 5.11.4 + test + + + org.junit.jupiter + junit-jupiter-params + 5.11.3 + test + + + org.assertj + assertj-core + 3.26.3 + + + org.mockito + mockito-junit-jupiter + 5.14.0 + test + + + com.h2database + h2 + 2.2.220 + test + + - - - - org.apache.maven.plugins - maven-wrapper-plugin - 3.1.0 - - - org.apache.maven.plugins - maven-failsafe-plugin - 3.0.0-M5 - - - - integration-test - verify - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.10.1 - - - - org.projectlombok - lombok - ${lombok.version} - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 3.0.0-M5 - - - org.jacoco - jacoco-maven-plugin - 0.8.7 - - - prepare-agent - - prepare-agent - - - - generate-jacoco-report - - report - - verify - - - - - + + + + org.apache.maven.plugins + maven-wrapper-plugin + 3.1.0 + + + org.apache.maven.plugins + maven-failsafe-plugin + 3.0.0-M5 + + + + integration-test + verify + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.10.1 + + + + org.projectlombok + lombok + ${lombok.version} + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + + + org.jacoco + jacoco-maven-plugin + 0.8.7 + + + prepare-agent + + prepare-agent + + + + generate-jacoco-report + + report + + verify + + + + + - \ No newline at end of file + 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..91d545f --- /dev/null +++ b/src/test/java/com/dmdev/dao/SubscriptionDaoIT.java @@ -0,0 +1,142 @@ +package com.dmdev.dao; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.dmdev.entity.Provider; +import com.dmdev.entity.Status; +import com.dmdev.entity.Subscription; +import com.dmdev.integration.IntegrationTestBase; +import java.time.Instant; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.Test; + +class SubscriptionDaoIT extends IntegrationTestBase { + + private final SubscriptionDao subscriptionDao = SubscriptionDao.getInstance(); + + @Test + void findAll() { + var subscriptionFirst = subscriptionDao.insert(getSubscription("first")); + var subscriptionSecond = subscriptionDao.insert(getSubscription("second ")); + var subscriptionThird = subscriptionDao.insert(getSubscription("third")); + + List result = subscriptionDao.findAll(); + + assertEquals(List.of(subscriptionFirst, subscriptionSecond, subscriptionThird), result); + + List ids = result.stream() + .map(Subscription::getId) + .toList(); + assertThat(ids).contains(subscriptionFirst.getId(), subscriptionSecond.getId(), + subscriptionThird.getId()); + } + + + @Test + void findByIdWhenIdExistsThenReturnSubscription() { + var subscriptionFirst = subscriptionDao.insert(getSubscription("first")); + + var result = subscriptionDao.findById(subscriptionFirst.getId()); + + assertNotNull(result); + assertEquals(Optional.of(subscriptionFirst), result); + } + + @Test + void findByIdWhenIdNotExistsThenReturnOptionalEmpty() { + var result = subscriptionDao.findById(3212); + + assertEquals(Optional.empty(), result); + } + + @Test + void deleteWhenIdExistsThenReturnTrue() { + var subscriptionFirst = subscriptionDao.insert(getSubscription("first")); + + var result = subscriptionDao.delete(subscriptionFirst.getId()); + + assertTrue(result); + + } + + @Test + void deleteWhenIdNotExistsThenReturnFalse() { + subscriptionDao.insert(getSubscription("first")); + + var result = subscriptionDao.delete(1234); + + assertFalse(result); + + } + + @Test + void update() { + var subscription = getSubscription("first"); + subscriptionDao.insert(subscription); + subscription.setName("second"); + subscription.setProvider(Provider.GOOGLE); + + var result = subscriptionDao.update(subscription); + + assertNotNull(result); + assertEquals(subscription, result); + + } + + @Test + void insert() { + var subscription = getSubscription("first"); + + var result = subscriptionDao.insert(subscription); + + assertNotNull(result); + } + + @Test + void findByUserIdWhenIdExistsThenReturnSubscriptions() { + var userid = 123; + var subscriptionFirst = subscriptionDao.insert(getSubscription("first").setUserId(userid)); + var subscriptionSecond = subscriptionDao.insert(getSubscription("second").setUserId(userid)); + + List result = subscriptionDao.findByUserId(userid); + + assertNotNull(result); + assertEquals(2, result.size()); + + List ids = result.stream() + .map(Subscription::getId) + .toList(); + assertThat(ids).contains(subscriptionFirst.getId(), subscriptionSecond.getId()); + + } + + @Test + void findByUserIdWhenIdNotExistsThenReturnEmptyList() { + var userid = 123; + subscriptionDao.insert(getSubscription("first").setUserId(userid)); + subscriptionDao.insert(getSubscription("second").setUserId(userid)); + + var result = subscriptionDao.findByUserId(3212); + + assertEquals(Collections.EMPTY_LIST, result); + } + + + private Subscription getSubscription(String name) { + return Subscription.builder() + .userId(123) + .name(name) + .provider(Provider.APPLE) + .status(Status.ACTIVE) + .expirationDate(Instant.parse("2025-11-12T10:00:00Z")) + .build(); + } + + +} 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..fe9e9e2 --- /dev/null +++ b/src/test/java/com/dmdev/mapper/CreateSubscriptionMapperTest.java @@ -0,0 +1,46 @@ +package com.dmdev.mapper; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import com.dmdev.dto.CreateSubscriptionDto; +import com.dmdev.entity.Provider; +import com.dmdev.entity.Status; +import com.dmdev.entity.Subscription; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneOffset; +import org.junit.jupiter.api.Test; + +class CreateSubscriptionMapperTest { + + CreateSubscriptionMapper mapper = CreateSubscriptionMapper.getInstance(); + + @Test + void map() { + Instant fixedInstant = LocalDate.of(2026, 11, 12) + .atStartOfDay(ZoneOffset.UTC) + .toInstant(); + + CreateSubscriptionDto dto = CreateSubscriptionDto.builder() + .name("dummy") + .userId(123) + .expirationDate(fixedInstant) + .provider(Provider.APPLE.name()) + .build(); + + Subscription expectedSubscription = Subscription.builder() + .name("dummy") + .userId(123) + .expirationDate(fixedInstant) + .provider(Provider.APPLE) + .status(Status.ACTIVE) + .build(); + + Subscription resultSubscription = mapper.map(dto); + + assertNotNull(resultSubscription); + assertEquals(expectedSubscription, resultSubscription); + + } +} 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..3dbeb94 --- /dev/null +++ b/src/test/java/com/dmdev/service/SubscriptionServiceTest.java @@ -0,0 +1,214 @@ +package com.dmdev.service; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +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 java.time.Clock; +import java.time.Instant; +import java.util.Collections; +import java.util.Optional; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class SubscriptionServiceTest { + + @Mock + private SubscriptionDao subscriptionDao; + + @Mock + private CreateSubscriptionMapper createSubscriptionMapper; + + @Mock + private CreateSubscriptionValidator createSubscriptionValidator; + + @Mock + private Clock clock; + + + @InjectMocks + private SubscriptionService subscriptionService; + + @Test + void whenCallingUpsertMethodWithInvalidDtoThenThrowValidationException() { + var dto = Mockito.mock(CreateSubscriptionDto.class); + var error = Error.of(100, "userId is invalid"); + var validationResult = new ValidationResult(); + validationResult.add(error); + + when(createSubscriptionValidator.validate(any())).thenReturn(validationResult); + + var resultException = assertThrows(ValidationException.class, + () -> subscriptionService.upsert(dto)); + + assertEquals(1, resultException.getErrors().size()); + assertEquals(100, resultException.getErrors().get(0).getCode()); + + } + + @Test + void whenCallingUpsertMethodWithNonExistingSubscriptionThenCreateANewOne() { + var dto = CreateSubscriptionDto.builder() + .name("dummy") + .userId(123) + .provider(Provider.APPLE.name()) + .build(); + var newSubscription = getSubscription(); + + when(createSubscriptionValidator.validate(any())).thenReturn(new ValidationResult()); + when(subscriptionDao.findByUserId(any())).thenReturn(Collections.emptyList()); + when(createSubscriptionMapper.map(dto)).thenReturn(newSubscription); + when(subscriptionDao.upsert(newSubscription)).thenReturn(newSubscription); + + var resultSubscription = subscriptionService.upsert(dto); + + assertNotNull(resultSubscription); + assertEquals(newSubscription, resultSubscription); + verify(createSubscriptionMapper).map(dto); + } + + @Test + void whenCallingUpsertMethodWithAnExistingSubscriptionThenUpdateIt() { + Instant expirationDate = Instant.parse("2025-11-12T10:00:00Z"); + var dto = CreateSubscriptionDto.builder() + .name("dummy") + .userId(123) + .provider(Provider.APPLE.name()) + .expirationDate(expirationDate) + .build(); + + var existingSubscription = getSubscription(); + var expectedSubscription = getSubscription().setExpirationDate(expirationDate); + + when(createSubscriptionValidator.validate(any())).thenReturn(new ValidationResult()); + when(subscriptionDao.findByUserId(123)).thenReturn( + Collections.singletonList(existingSubscription)); + when(subscriptionDao.upsert(any())).thenReturn(expectedSubscription); + + var resultSubscription = subscriptionService.upsert(dto); + + assertNotNull(resultSubscription); + assertEquals(expectedSubscription, resultSubscription); + verify(subscriptionDao).findByUserId(123); + verify(subscriptionDao).upsert(expectedSubscription); + verify(createSubscriptionMapper, never()).map(dto); + + } + + @Test + void happyFloWhenCallingCancelMethodItUpdatesExistingSubscription() { + var existingSubscription = getSubscription(); + var updatedSubscription = getSubscription().setStatus(Status.CANCELED); + + when(subscriptionDao.findById(any())).thenReturn(Optional.of(existingSubscription)); + when(subscriptionDao.update(any())).thenReturn(updatedSubscription); + + subscriptionService.cancel(any()); + + verify(subscriptionDao, times(1)).update(updatedSubscription); + verify(subscriptionDao, times(1)).findById(any()); + } + + @Test + void whenCallingCancelMethodWithNonExistingSubscriptionIdThenThrowIllegalArgumentException() { + + when(subscriptionDao.findById(any())).thenThrow(new IllegalArgumentException()); + + assertThrows(IllegalArgumentException.class, + () -> subscriptionService.cancel(123)); + + verify(subscriptionDao, never()).update(any()); + } + + @Test + void whenCallingCancelMethodWithNonActiveSubscriptionThenThrowSubscriptionException() { + + int id = 123; + Subscription subscription = getSubscription().setStatus(Status.EXPIRED); + + when(subscriptionDao.findById(id)).thenReturn(Optional.ofNullable(subscription)); + + Exception resultException = assertThrows(SubscriptionException.class, + () -> subscriptionService.cancel(id)); + + assertEquals("Only active subscription 123 can be canceled", resultException.getMessage()); + verify(subscriptionDao, never()).update(any()); + } + + @Test + void whenCallingExpireMethodWithNonExistingSubscriptionIdThenThrowIllegalArgumentException() { + + when(subscriptionDao.findById(any())).thenThrow(new IllegalArgumentException()); + + assertThrows(IllegalArgumentException.class, + () -> subscriptionService.expire(123)); + + verify(subscriptionDao, never()).update(any()); + } + + @Test + void whenCallingExpireMethodWithExpiredSubscriptionThenThrowSubscriptionException() { + int id = 123; + Subscription subscription = Subscription.builder() + .id(id) + .status(Status.EXPIRED) + .build(); + + when(subscriptionDao.findById(id)).thenReturn(Optional.ofNullable(subscription)); + + Exception resultException = assertThrows(SubscriptionException.class, + () -> subscriptionService.expire(id)); + + assertEquals("Subscription 123 has already expired", resultException.getMessage()); + verify(subscriptionDao, never()).update(any()); + } + + @Test + void happyFloWhenCallingExpireMethodItUpdatesExistingSubscription() { + + int id = 123; + Instant expirationDate = Instant.parse("2025-11-12T10:00:00Z"); + Subscription givenSubscription = getSubscription(); + Subscription expectedSubscription = getSubscription().setStatus(Status.EXPIRED) + .setExpirationDate(expirationDate); + + when(subscriptionDao.findById(id)).thenReturn(Optional.ofNullable(givenSubscription)); + when(subscriptionDao.update(expectedSubscription)).thenReturn(expectedSubscription); + when(clock.instant()).thenReturn(expirationDate); + + subscriptionService.expire(id); + + verify(subscriptionDao).update(expectedSubscription); + verify(subscriptionDao).findById(id); + } + + Subscription getSubscription() { + return Subscription.builder() + .name("dummy") + .status(Status.ACTIVE) + .userId(123) + .provider(Provider.APPLE) + .build(); + } +} 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..d6a4f96 --- /dev/null +++ b/src/test/java/com/dmdev/util/PropertiesUtilTest.java @@ -0,0 +1,29 @@ +package com.dmdev.util; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +class PropertiesUtilTest { + + + @ParameterizedTest + @MethodSource("getPropertyArguments") + void checkGet(String key, String expectedValue) { + String actualResult = PropertiesUtil.get(key); + + assertThat(actualResult).isEqualTo(expectedValue); + } + + static Stream getPropertyArguments() { + + return Stream.of( + Arguments.of("db.url", "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1"), + Arguments.of("db.user", "sa"), + Arguments.of("db.password", "") + ); + } +} 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..f2dda7e --- /dev/null +++ b/src/test/java/com/dmdev/validator/CreateSubscriptionValidatorTest.java @@ -0,0 +1,130 @@ +package com.dmdev.validator; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.dmdev.dto.CreateSubscriptionDto; +import java.time.ZonedDateTime; +import org.junit.jupiter.api.Test; + +class CreateSubscriptionValidatorTest { + + private final CreateSubscriptionValidator validator = CreateSubscriptionValidator.getInstance(); + + @Test + void happyFlowPassesValidation() { + CreateSubscriptionDto dto = CreateSubscriptionDto.builder() + .name("dummy") + .userId(123) + .expirationDate(ZonedDateTime.now().plusDays(1).toInstant()) + .provider("APPLE") + .build(); + + ValidationResult result = validator.validate(dto); + + assertFalse(result.hasErrors()); + + } + + @Test + void invalidName() { + CreateSubscriptionDto dto = CreateSubscriptionDto.builder() + .name("") + .userId(123) + .expirationDate(ZonedDateTime.now().plusDays(1).toInstant()) + .provider("APPLE") + .build(); + + ValidationResult result = validator.validate(dto); + + assertTrue(result.hasErrors()); + assertEquals(1, result.getErrors().size()); + assertEquals("name is invalid", result.getErrors().get(0).getMessage()); + + } + + @Test + void invalidUserId() { + CreateSubscriptionDto dto = CreateSubscriptionDto.builder() + .name("dummy") + .expirationDate(ZonedDateTime.now().plusDays(1).toInstant()) + .provider("APPLE") + .build(); + + ValidationResult result = validator.validate(dto); + + assertTrue(result.hasErrors()); + assertEquals(1, result.getErrors().size()); + assertEquals("userId is invalid", result.getErrors().get(0).getMessage()); + + } + + @Test + void invalidProvider() { + CreateSubscriptionDto dto = CreateSubscriptionDto.builder() + .name("dummy") + .userId(123) + .expirationDate(ZonedDateTime.now().plusDays(1).toInstant()) + .provider("PLUM") + .build(); + + ValidationResult result = validator.validate(dto); + + assertTrue(result.hasErrors()); + assertEquals(1, result.getErrors().size()); + assertEquals("provider is invalid", result.getErrors().get(0).getMessage()); + + } + + @Test + void invalidExpirationDate() { + CreateSubscriptionDto dto = CreateSubscriptionDto.builder() + .name("dummy") + .userId(123) + .expirationDate(ZonedDateTime.now().minusDays(1L).toInstant()) + .provider("APPLE") + .build(); + + ValidationResult result = validator.validate(dto); + + assertTrue(result.hasErrors()); + assertEquals(1, result.getErrors().size()); + assertEquals("expirationDate is invalid", result.getErrors().get(0).getMessage()); + + } + + @Test + void expirationDateIsNull() { + CreateSubscriptionDto dto = CreateSubscriptionDto.builder() + .name("dummy") + .userId(123) + .provider("APPLE") + .build(); + + ValidationResult result = validator.validate(dto); + + assertTrue(result.hasErrors()); + assertEquals(1, result.getErrors().size()); + assertEquals("expirationDate is invalid", result.getErrors().get(0).getMessage()); + + } + + @Test + void invalidExpirationDateAndName() { + CreateSubscriptionDto dto = CreateSubscriptionDto.builder() + .name("") + .userId(123) + .expirationDate(ZonedDateTime.now().minusDays(1).toInstant()) + .provider("APPLE") + .build(); + + ValidationResult result = validator.validate(dto); + + assertTrue(result.hasErrors()); + assertEquals(2, result.getErrors().size()); + assertEquals("name is invalid", result.getErrors().get(0).getMessage()); + assertEquals("expirationDate is invalid", result.getErrors().get(1).getMessage()); + + } +}