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