From 7709437f436b7eaedb04ea0d94c12e80c4a63114 Mon Sep 17 00:00:00 2001 From: Vilem Gottwald <xvigo.dev@gmail.com> Date: Tue, 23 Apr 2024 20:36:32 +0200 Subject: [PATCH] add integration tests --- .../src/main/resources/application.yml | 3 +- .../src/main/resources/application.yml | 1 + docker-compose.yml | 6 +- readme.md | 18 +- .../src/main/resources/application.yml | 4 +- user-service/pom.xml | 10 + .../fi/obs/controller/UserController.java | 13 +- .../obs/controller/UserControllerAdvice.java | 6 + .../obs/data/repository/UserRepository.java | 2 +- .../exceptions/ClientConnectionException.java | 8 + .../fi/obs/http/TransactionServiceClient.java | 9 +- .../fi/obs/service/UserAccountService.java | 27 +- .../src/main/resources/application.yml | 4 +- .../db/migration/V0__initialize_database.sql | 2 + .../ControllerIntegrationTest.java | 32 ++ .../rest/UserControllerIntegrationTest.java | 345 ++++++++++++++++++ .../fi/obs/repository/UserRepositoryTest.java | 150 ++++++++ .../muni/fi/obs/service/UserServiceTest.java | 59 ++- .../src/test/resources/application-test.yml | 10 + user-service/src/test/resources/drop_all.sql | 3 + .../src/test/resources/initialize_db.sql | 10 + 21 files changed, 676 insertions(+), 46 deletions(-) create mode 100644 user-service/src/main/java/cz/muni/fi/obs/exceptions/ClientConnectionException.java create mode 100644 user-service/src/test/java/cz/muni/fi/obs/integration/ControllerIntegrationTest.java create mode 100644 user-service/src/test/java/cz/muni/fi/obs/integration/rest/UserControllerIntegrationTest.java create mode 100644 user-service/src/test/java/cz/muni/fi/obs/repository/UserRepositoryTest.java create mode 100644 user-service/src/test/resources/application-test.yml create mode 100644 user-service/src/test/resources/drop_all.sql create mode 100644 user-service/src/test/resources/initialize_db.sql diff --git a/analytics-service/src/main/resources/application.yml b/analytics-service/src/main/resources/application.yml index 676b724..4b7dd3f 100644 --- a/analytics-service/src/main/resources/application.yml +++ b/analytics-service/src/main/resources/application.yml @@ -1,3 +1,4 @@ server: servlet: - context-path: '/api/analytics-service' \ No newline at end of file + context-path: '/api/analytics-service' + port: 8080 diff --git a/currency-service/src/main/resources/application.yml b/currency-service/src/main/resources/application.yml index 6a8974c..3c9f20c 100644 --- a/currency-service/src/main/resources/application.yml +++ b/currency-service/src/main/resources/application.yml @@ -1,6 +1,7 @@ server: servlet: context-path: '/api/currency-service' + port: 8081 currency: auto-update: diff --git a/docker-compose.yml b/docker-compose.yml index adedaba..3734575 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -31,7 +31,7 @@ services: environment: - SPRING_DATASOURCE_URL=jdbc:postgresql://currency-db:5432/currency_db ports: - - 8081:8080 + - 8081:8081 currency-db: container_name: currency-db image: postgres:latest @@ -53,7 +53,7 @@ services: environment: - SPRING_DATASOURCE_URL=jdbc:postgresql://transaction-db:5432/transaction_db ports: - - 8082:8080 + - 8082:8082 transaction-db: container_name: transaction-db image: postgres:latest @@ -75,7 +75,7 @@ services: environment: - SPRING_DATASOURCE_URL=jdbc:postgresql://user-db:5432/user_db ports: - - 8083:8080 + - 8083:8083 user-db: container_name: user-db image: postgres:latest diff --git a/readme.md b/readme.md index b9e5e39..ff413cc 100644 --- a/readme.md +++ b/readme.md @@ -36,4 +36,20 @@ In M2 we need to implement creation and execution of scheduled payments. ## Currency-service Service handles all currency related operation, it manages currencies so exchange rates are always up-to-date and -provides needed services to the rest of the system. \ No newline at end of file +provides needed services to the rest of the system. + +### Swagger Links + +- [Analytics-service](http://localhost:8080/api/analytics-service/swagger-ui/index.html) +- [User-service](http://localhost:8083/api/user-service/swagger-ui/index.html) +- [Transaction-service](http://localhost:8082/api/transaction-service/swagger-ui/index.html) +- [Currency-service](http://localhost:8081/api/currency-service/swagger-ui/index.html) + +### Adminer + +Password: `changemelater` + +- [Analytics-service](http://localhost:8084/?pgsql=analytics-db&username=analytics_service&db=analytics_db&) +- [User-service](http://localhost:8084/?pgsql=user-db&username=user_service&db=user_db&) +- [Transaction-service](http://localhost:8084/?pgsql=transaction-db&username=transaction_service&db=transaction_db&) +- [Currency-service](http://localhost:8084/?pgsql=currency-db&username=currency_service&db=currency_db&) \ No newline at end of file diff --git a/transaction-service/src/main/resources/application.yml b/transaction-service/src/main/resources/application.yml index 18bb727..a77cb25 100644 --- a/transaction-service/src/main/resources/application.yml +++ b/transaction-service/src/main/resources/application.yml @@ -1,6 +1,7 @@ server: servlet: context-path: '/api/transaction-service' + port: 8082 spring: application: @@ -35,4 +36,5 @@ resilience4j: clients: currency-service: - url: 'http://localhost:8080/api/currency-service' + # url: 'http://localhost:8081/api/currency-service' + url: 'http://currency-service:8081/api/currency-service' diff --git a/user-service/pom.xml b/user-service/pom.xml index 76f7c3f..6748a3c 100644 --- a/user-service/pom.xml +++ b/user-service/pom.xml @@ -87,6 +87,16 @@ <artifactId>spring-boot-starter-data-jpa</artifactId> <version>${spring.boot.version}</version> </dependency> + <dependency> + <groupId>com.h2database</groupId> + <artifactId>h2</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>io.rest-assured</groupId> + <artifactId>rest-assured</artifactId> + <scope>test</scope> + </dependency> </dependencies> </project> diff --git a/user-service/src/main/java/cz/muni/fi/obs/controller/UserController.java b/user-service/src/main/java/cz/muni/fi/obs/controller/UserController.java index 1f08a30..472e3f4 100644 --- a/user-service/src/main/java/cz/muni/fi/obs/controller/UserController.java +++ b/user-service/src/main/java/cz/muni/fi/obs/controller/UserController.java @@ -12,8 +12,6 @@ import io.swagger.v3.oas.annotations.info.License; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.servers.Server; -import io.swagger.v3.oas.annotations.servers.ServerVariable; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; @@ -45,16 +43,7 @@ import java.util.UUID; - getting user accounts by user ID """, contact = @Contact(name = "Vilem Gottwald", email = "553627@mail.muni.cz"), - license = @License(name = "Apache 2.0", url = "https://www.apache.org/licenses/LICENSE-2.0.html")), - servers = @Server( - description = "local server", - url = "{scheme}://{server}:{port}/api/user-service", - variables = { - @ServerVariable(name = "scheme", defaultValue = "http"), - @ServerVariable(name = "server", defaultValue = "localhost"), - @ServerVariable(name = "port", defaultValue = "8080"), - } - ) + license = @License(name = "Apache 2.0", url = "https://www.apache.org/licenses/LICENSE-2.0.html")) ) @Tag(name = "User management", description = "Microservice for managing users and their bank accounts") @RequestMapping(path = "/v1/users", produces = MediaType.APPLICATION_JSON_VALUE) diff --git a/user-service/src/main/java/cz/muni/fi/obs/controller/UserControllerAdvice.java b/user-service/src/main/java/cz/muni/fi/obs/controller/UserControllerAdvice.java index 10fca41..7545e85 100644 --- a/user-service/src/main/java/cz/muni/fi/obs/controller/UserControllerAdvice.java +++ b/user-service/src/main/java/cz/muni/fi/obs/controller/UserControllerAdvice.java @@ -3,6 +3,7 @@ package cz.muni.fi.obs.controller; import cz.muni.fi.obs.api.NotFoundResponse; import cz.muni.fi.obs.api.ValidationErrors; import cz.muni.fi.obs.api.ValidationFailedResponse; +import cz.muni.fi.obs.exceptions.ClientConnectionException; import cz.muni.fi.obs.exceptions.UserNotFoundException; import org.postgresql.util.PSQLException; import org.springframework.http.HttpStatus; @@ -102,6 +103,11 @@ public class UserControllerAdvice { } } + @ExceptionHandler(ClientConnectionException.class) + public ResponseEntity<String> handleClientConnectionExceptions(ClientConnectionException ex) { + return new ResponseEntity<>(ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + } + private String extractFieldName(String message) { String fieldName = null; if (message.contains("Key (")) { diff --git a/user-service/src/main/java/cz/muni/fi/obs/data/repository/UserRepository.java b/user-service/src/main/java/cz/muni/fi/obs/data/repository/UserRepository.java index 308fbb7..c91c06d 100644 --- a/user-service/src/main/java/cz/muni/fi/obs/data/repository/UserRepository.java +++ b/user-service/src/main/java/cz/muni/fi/obs/data/repository/UserRepository.java @@ -24,7 +24,7 @@ public interface UserRepository extends JpaRepository<User, UUID> { "(:lastName IS NULL OR u.lastName = :lastName) AND " + "(:phoneNumber IS NULL OR u.phoneNumber = :phoneNumber) AND " + "(:email IS NULL OR u.email = :email) AND " + - "(:birthDate IS NULL OR u.birthDate = :birthDate) AND " + + "(cast(:birthDate as date) IS NULL OR u.birthDate = cast(:birthDate as date)) AND " + "(:birthNumber IS NULL OR u.birthNumber = :birthNumber) AND " + "(:active IS NULL OR u.active = :active)") Page<User> findBySearchParams( diff --git a/user-service/src/main/java/cz/muni/fi/obs/exceptions/ClientConnectionException.java b/user-service/src/main/java/cz/muni/fi/obs/exceptions/ClientConnectionException.java new file mode 100644 index 0000000..6d65e8a --- /dev/null +++ b/user-service/src/main/java/cz/muni/fi/obs/exceptions/ClientConnectionException.java @@ -0,0 +1,8 @@ +package cz.muni.fi.obs.exceptions; + +public class ClientConnectionException extends RuntimeException { + + public ClientConnectionException(String message) { + super(message); + } +} diff --git a/user-service/src/main/java/cz/muni/fi/obs/http/TransactionServiceClient.java b/user-service/src/main/java/cz/muni/fi/obs/http/TransactionServiceClient.java index 5081f19..d5d7181 100644 --- a/user-service/src/main/java/cz/muni/fi/obs/http/TransactionServiceClient.java +++ b/user-service/src/main/java/cz/muni/fi/obs/http/TransactionServiceClient.java @@ -1,6 +1,7 @@ package cz.muni.fi.obs.http; import cz.muni.fi.obs.config.FeignClientConfiguration; +import cz.muni.fi.obs.exceptions.ClientConnectionException; import cz.muni.fi.obs.http.api.TSAccount; import cz.muni.fi.obs.http.api.TSAccountCreate; import lombok.extern.slf4j.Slf4j; @@ -31,14 +32,14 @@ public interface TransactionServiceClient { @Override public TSAccount createAccount(TSAccountCreate currencyExchangeRequest) { - log.error("Could not create account, returning null"); - return null; + log.error("Could not create account for customer id {}", currencyExchangeRequest.customerId()); + throw new ClientConnectionException("Could not create account"); } @Override public List<TSAccount> getAccountsByCustomerId(String customerId) { - log.error("Could not get accounts by customer id, returning null"); - return null; + log.error("Could not get accounts by customer id {}", customerId); + throw new ClientConnectionException("Could not get accounts by customer id"); } } } diff --git a/user-service/src/main/java/cz/muni/fi/obs/service/UserAccountService.java b/user-service/src/main/java/cz/muni/fi/obs/service/UserAccountService.java index 964f4bc..bdcc66b 100644 --- a/user-service/src/main/java/cz/muni/fi/obs/service/UserAccountService.java +++ b/user-service/src/main/java/cz/muni/fi/obs/service/UserAccountService.java @@ -5,9 +5,11 @@ import cz.muni.fi.obs.api.AccountDto; import cz.muni.fi.obs.http.TransactionServiceClient; import cz.muni.fi.obs.http.api.TSAccount; import cz.muni.fi.obs.http.api.TSAccountCreate; +import feign.FeignException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.Collections; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @@ -30,9 +32,6 @@ public class UserAccountService { accountCreateDto.accountNumber() ); TSAccount tsAccount = transactionServiceClient.createAccount(tsAccountCreate); - if (tsAccount == null) { - return null; - } return new AccountDto( UUID.fromString(tsAccount.id()), tsAccount.accountNumber(), @@ -41,17 +40,17 @@ public class UserAccountService { } public List<AccountDto> getUserAccounts(UUID userId) { - List<TSAccount> tsAccounts = transactionServiceClient.getAccountsByCustomerId(userId.toString()); - if (tsAccounts == null) { - return null; + try { + List<TSAccount> tsAccounts = transactionServiceClient.getAccountsByCustomerId(userId.toString()); + return tsAccounts.stream() + .map(tsAccount -> new AccountDto( + UUID.fromString(tsAccount.id()), + tsAccount.accountNumber(), + tsAccount.currencyCode() + )) + .collect(Collectors.toList()); + } catch (FeignException.NotFound e) { + return Collections.emptyList(); } - - return tsAccounts.stream() - .map(tsAccount -> new AccountDto( - UUID.fromString(tsAccount.id()), - tsAccount.accountNumber(), - tsAccount.currencyCode() - )) - .collect(Collectors.toList()); } } diff --git a/user-service/src/main/resources/application.yml b/user-service/src/main/resources/application.yml index 71d955f..f4df708 100644 --- a/user-service/src/main/resources/application.yml +++ b/user-service/src/main/resources/application.yml @@ -1,6 +1,7 @@ server: servlet: context-path: '/api/user-service' + port: 8083 spring: datasource: @@ -16,4 +17,5 @@ spring: clients: transaction-service: - url: 'http://localhost:8080/api/transaction-service' \ No newline at end of file + # url: 'http://localhost:8082/api/transaction-service' + url: 'http://transaction-service:8082/api/transaction-service' \ No newline at end of file diff --git a/user-service/src/main/resources/db/migration/V0__initialize_database.sql b/user-service/src/main/resources/db/migration/V0__initialize_database.sql index aae645d..4b801e4 100644 --- a/user-service/src/main/resources/db/migration/V0__initialize_database.sql +++ b/user-service/src/main/resources/db/migration/V0__initialize_database.sql @@ -10,3 +10,5 @@ CREATE TABLE us_user birth_number varchar(20) not null unique, active boolean not null default true ); + +CREATE INDEX us_user_id_index ON us_user (id); diff --git a/user-service/src/test/java/cz/muni/fi/obs/integration/ControllerIntegrationTest.java b/user-service/src/test/java/cz/muni/fi/obs/integration/ControllerIntegrationTest.java new file mode 100644 index 0000000..82ee287 --- /dev/null +++ b/user-service/src/test/java/cz/muni/fi/obs/integration/ControllerIntegrationTest.java @@ -0,0 +1,32 @@ +package cz.muni.fi.obs.integration; + +import io.restassured.RestAssured; +import io.restassured.specification.RequestSpecification; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; +import org.springframework.web.util.UriComponents; + +import static io.restassured.RestAssured.given; +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; + +@ActiveProfiles("test") +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@Sql(value = {"/initialize_db.sql"}, executionPhase = BEFORE_TEST_CLASS) +public abstract class ControllerIntegrationTest { + + @LocalServerPort + private int port; + + public static RequestSpecification requestSpecification(UriComponents uri) { + return given().basePath(uri.getPath()) + .queryParams(uri.getQueryParams()); + } + + @BeforeEach + void setup() { + RestAssured.port = port; + } +} diff --git a/user-service/src/test/java/cz/muni/fi/obs/integration/rest/UserControllerIntegrationTest.java b/user-service/src/test/java/cz/muni/fi/obs/integration/rest/UserControllerIntegrationTest.java new file mode 100644 index 0000000..84cce53 --- /dev/null +++ b/user-service/src/test/java/cz/muni/fi/obs/integration/rest/UserControllerIntegrationTest.java @@ -0,0 +1,345 @@ +package cz.muni.fi.obs.integration.rest; + +import cz.muni.fi.obs.api.*; +import cz.muni.fi.obs.data.dbo.User; +import cz.muni.fi.obs.data.enums.Nationality; +import cz.muni.fi.obs.data.repository.UserRepository; +import cz.muni.fi.obs.integration.ControllerIntegrationTest; +import io.restassured.http.ContentType; +import org.apache.http.HttpStatus; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +import java.time.LocalDate; +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +@TestMethodOrder(OrderAnnotation.class) +class UserControllerIntegrationTest extends ControllerIntegrationTest { + + private static final String USER_CONTROLLER_PATH = "/api/user-service/v1/users"; + + @Autowired + private UserRepository userRepository; + + @Test + public void createUser_newUser_createsUser() { + UriComponents components = UriComponentsBuilder. + fromPath(USER_CONTROLLER_PATH + "/create") + .build(); + + UserCreateDto userCreateDto = new UserCreateDto("Joe", + "Doe", + "123456789", + "test@gmail.com", + LocalDate.of(2001, 4, 13), + Nationality.SK, + "010413/2215" + ); + + UserDto userDto = requestSpecification(components) + .contentType(ContentType.JSON) + .body(userCreateDto) + .post() + .then() + .statusCode(HttpStatus.SC_CREATED) + .extract() + .as(UserDto.class); + + + Optional<User> createdUser = userRepository.findById(userDto.id()); + assertThat(createdUser).isPresent(); + + User user = createdUser.get(); + userRepository.delete(user); + + assertThat(user) + .returns(userCreateDto.firstName(), User::getFirstName) + .returns(userCreateDto.lastName(), User::getLastName) + .returns(userCreateDto.phoneNumber(), User::getPhoneNumber) + .returns(userCreateDto.email(), User::getEmail) + .returns(userCreateDto.birthDate(), User::getBirthDate) + .returns(userCreateDto.nationality(), User::getNationality) + .returns(userCreateDto.birthNumber(), User::getBirthNumber) + .returns(true, User::isActive); + } + + @Test + public void createUser_invalidRequest_returnsValidationError() { + + UriComponents components = UriComponentsBuilder + .fromPath(USER_CONTROLLER_PATH + "/create") + .build(); + + ValidationFailedResponse response = requestSpecification(components) + .contentType(ContentType.JSON) + .body(new UserCreateDto("", "", "", "", LocalDate.now(), null, "")) + .post() + .then() + .statusCode(HttpStatus.SC_BAD_REQUEST) + .extract() + .as(ValidationFailedResponse.class); + + assertThat(response.message()).isEqualTo("Validation failed"); + } + + @Test + public void getUserById_userExists_returnsUser() { + UUID userId = UUID.fromString("5e4b3326-38b5-4484-8034-33d81f34bec2"); + + UriComponents components = UriComponentsBuilder + .fromPath(USER_CONTROLLER_PATH + "/" + userId) + .build(); + + UserDto userDto = requestSpecification(components) + .get() + .then() + .statusCode(HttpStatus.SC_OK) + .extract() + .as(UserDto.class); + + assertThat(userDto.id()).isEqualTo(userId); + } + + @Test + public void getUserById_invalidRequest_returnsValidationError() { + + UriComponents components = UriComponentsBuilder + .fromPath(USER_CONTROLLER_PATH + "/not-a-uuid") + .build(); + + ValidationFailedResponse response = requestSpecification(components) + .get() + .then() + .statusCode(HttpStatus.SC_BAD_REQUEST) + .extract() + .as(ValidationFailedResponse.class); + + assertThat(response.message()).isEqualTo("Validation failed"); + } + + @Test + public void getUserById_userNotExists_returnsNotFoundError() { + UUID userId = UUID.fromString("5e4b3326-38b5-4484-8034-33d81f34bed4"); + + UriComponents components = UriComponentsBuilder + .fromPath(USER_CONTROLLER_PATH + "/" + userId) + .build(); + + NotFoundResponse response = requestSpecification(components) + .get() + .then() + .statusCode(HttpStatus.SC_NOT_FOUND) + .extract() + .as(NotFoundResponse.class); + + assertThat(response.message()).isEqualTo("User with id " + userId + " not found"); + } + + @Test + public void updateUser_userExists_returnsUser() { + UUID userId = UUID.fromString("5e4b3326-38b5-4484-8034-33d81f34bec2"); + User origUser = userRepository.findById(userId).orElseThrow(); + + UriComponents components = UriComponentsBuilder + .fromPath(USER_CONTROLLER_PATH + "/" + userId) + .build(); + + UserUpdateDto userUpdateDto = new UserUpdateDto( + Optional.of("NewName"), + Optional.of("NewSurname"), + Optional.of("987654321"), + Optional.of("newemail@email.cz") + ); + + + requestSpecification(components) + .contentType(ContentType.JSON) + .body(userUpdateDto) + .put() + .then() + .statusCode(HttpStatus.SC_OK) + .extract() + .as(UserDto.class); + + Optional<User> user = userRepository.findById(userId); + assertThat(user).isPresent(); + + User updatedUser = user.get(); + assertThat(updatedUser) + .returns("NewName", User::getFirstName) + .returns("NewSurname", User::getLastName) + .returns("987654321", User::getPhoneNumber) + .returns("newemail@email.cz", User::getEmail); + + userRepository.save(origUser); + } + + @Test + public void updateUser_userNotExists_returnsNotFoundError() { + UUID userId = UUID.fromString("5e4b3326-38b5-4484-8034-33d81f34bed4"); + assertThat(userRepository.findById(userId).isPresent()).isFalse(); + + UriComponents components = UriComponentsBuilder + .fromPath(USER_CONTROLLER_PATH + "/" + userId) + .build(); + + UserUpdateDto userUpdateDto = new UserUpdateDto( + Optional.of("NewName"), + Optional.of("NewSurname"), + Optional.of("987654321"), + Optional.of("newemail@email.cz") + ); + + requestSpecification(components) + .contentType(ContentType.JSON) + .body(userUpdateDto) + .put() + .then() + .statusCode(HttpStatus.SC_NOT_FOUND) + .extract() + .as(NotFoundResponse.class); + + assertThat(userRepository.findById(userId).isPresent()).isFalse(); + } + + @Test + public void deactivateUser_userNotExists_returnsNotFoundError() { + UUID userId = UUID.fromString("5e4b3326-38b5-4484-8034-33d81f34bed4"); + assertThat(userRepository.findById(userId).isPresent()).isFalse(); + + UriComponents components = UriComponentsBuilder + .fromPath(USER_CONTROLLER_PATH + "/" + userId + "/deactivate") + .build(); + + + requestSpecification(components) + .post() + .then() + .statusCode(HttpStatus.SC_NOT_FOUND) + .extract() + .as(NotFoundResponse.class); + + assertThat(userRepository.findById(userId).isPresent()).isFalse(); + } + + @Test + public void deactivateUser_deactivatedUser_doesNothing() { + UUID userId = UUID.fromString("5e4b3326-38b5-4484-8034-33d81f34bec5"); + User origUser = userRepository.findById(userId).orElseThrow(); + assertThat(origUser.isActive()).isFalse(); + + UriComponents components = UriComponentsBuilder + .fromPath(USER_CONTROLLER_PATH + "/" + userId + "/deactivate") + .build(); + + + UserDto userDto = requestSpecification(components) + .post() + .then() + .statusCode(HttpStatus.SC_OK) + .extract() + .as(UserDto.class); + + User updatedUser = userRepository.findById(userId).orElseThrow(); + assertThat(updatedUser).isEqualTo(origUser); + assertThat(userDto.active()).isFalse(); + } + + @Test + public void deactivateUser_activateUser_deactivatesUser() { + UUID userId = UUID.fromString("5e4b3326-38b5-4484-8034-33d81f34bec2"); + User origUser = userRepository.findById(userId).orElseThrow(); + assertThat(origUser.isActive()).isTrue(); + + UriComponents components = UriComponentsBuilder + .fromPath(USER_CONTROLLER_PATH + "/" + userId + "/deactivate") + .build(); + + + UserDto userDto = requestSpecification(components) + .post() + .then() + .statusCode(HttpStatus.SC_OK) + .extract() + .as(UserDto.class); + + User updatedUser = userRepository.findById(userId).orElseThrow(); + assertThat(updatedUser.isActive()).isFalse(); + assertThat(userDto.active()).isFalse(); + + userRepository.save(origUser); + } + + @Test + public void activateUser_userNotExists_returnsNotFoundError() { + UUID userId = UUID.fromString("5e4b3326-38b5-4484-8034-33d81f34bed4"); + assertThat(userRepository.findById(userId).isPresent()).isFalse(); + + UriComponents components = UriComponentsBuilder + .fromPath(USER_CONTROLLER_PATH + "/" + userId + "/activate") + .build(); + + + requestSpecification(components) + .post() + .then() + .statusCode(HttpStatus.SC_NOT_FOUND) + .extract() + .as(NotFoundResponse.class); + + assertThat(userRepository.findById(userId).isPresent()).isFalse(); + } + + @Test + public void activateUser_activatedUser_doesNothing() { + UUID userId = UUID.fromString("5e4b3326-38b5-4484-8034-33d81f34bec2"); + User origUser = userRepository.findById(userId).orElseThrow(); + assertThat(origUser.isActive()).isTrue(); + + UriComponents components = UriComponentsBuilder + .fromPath(USER_CONTROLLER_PATH + "/" + userId + "/activate") + .build(); + + UserDto userDto = requestSpecification(components) + .post() + .then() + .statusCode(HttpStatus.SC_OK) + .extract() + .as(UserDto.class); + + User updatedUser = userRepository.findById(userId).orElseThrow(); + assertThat(updatedUser).isEqualTo(origUser); + assertThat(userDto.active()).isTrue(); + } + + @Test + public void activateUser_deactivatedUser_activatesUser() { + UUID userId = UUID.fromString("5e4b3326-38b5-4484-8034-33d81f34bec5"); + User origUser = userRepository.findById(userId).orElseThrow(); + assertThat(origUser.isActive()).isFalse(); + + UriComponents components = UriComponentsBuilder + .fromPath(USER_CONTROLLER_PATH + "/" + userId + "/activate") + .build(); + + UserDto userDto = requestSpecification(components) + .post() + .then() + .statusCode(HttpStatus.SC_OK) + .extract() + .as(UserDto.class); + + User updatedUser = userRepository.findById(userId).orElseThrow(); + assertThat(updatedUser.isActive()).isTrue(); + assertThat(userDto.active()).isTrue(); + + userRepository.save(origUser); + } +} diff --git a/user-service/src/test/java/cz/muni/fi/obs/repository/UserRepositoryTest.java b/user-service/src/test/java/cz/muni/fi/obs/repository/UserRepositoryTest.java new file mode 100644 index 0000000..51168bb --- /dev/null +++ b/user-service/src/test/java/cz/muni/fi/obs/repository/UserRepositoryTest.java @@ -0,0 +1,150 @@ +package cz.muni.fi.obs.repository; + +import cz.muni.fi.obs.data.dbo.User; +import cz.muni.fi.obs.data.enums.Nationality; +import cz.muni.fi.obs.data.repository.UserRepository; +import cz.muni.fi.obs.exceptions.UserNotFoundException; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.jdbc.Sql; + +import java.time.LocalDate; +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.AFTER_TEST_CLASS; +import static org.springframework.test.context.jdbc.Sql.ExecutionPhase.BEFORE_TEST_CLASS; + +@Sql(value = {"/initialize_db.sql"}, executionPhase = BEFORE_TEST_CLASS) +@Sql(value = {"/drop_all.sql"}, executionPhase = AFTER_TEST_CLASS) +@DataJpaTest +@ActiveProfiles("test") +public class UserRepositoryTest { + + @Autowired + private UserRepository userRepository; + + @Test + public void findByIdOrThrow_UserFound_ReturnsUser() { + User user = userRepository.findByIdOrThrow(UUID.fromString("5e4b3326-38b5-4484-8034-33d81f34bec2")); + + assertThat(user) + .returns(UUID.fromString("5e4b3326-38b5-4484-8034-33d81f34bec2"), User::getId) + .returns("John", User::getFirstName) + .returns("Doe", User::getLastName) + .returns("9707178239", User::getPhoneNumber) + .returns("example1@domain.com", User::getEmail) + .returns("1990-01-01", u -> u.getBirthDate().toString()) + .returns(Nationality.CZ, User::getNationality) + .returns("900101/1234", User::getBirthNumber) + .returns(true, User::isActive); + } + + @Test + public void findByIdOrThrow_UserNotFound_ThrowsException() { + assertThatThrownBy(() -> userRepository.findByIdOrThrow(UUID.fromString("5e4b3326-38b5-4484-8034-33d81f34bec8" + ))) + .isInstanceOf(UserNotFoundException.class) + .hasMessage("User with id 5e4b3326-38b5-4484-8034-33d81f34bec8 not found"); + } + + + @Test + public void findBySearchParams_UsersNotFound_returnEmptyList() { + Page<User> users = userRepository.findBySearchParams( + Optional.of("non-existing"), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Pageable.unpaged() + ); + assertThat(users).isEmpty(); + } + + @Test + public void findBySearchParams_NoParams_returnAll() { + Page<User> users = userRepository.findBySearchParams( + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Pageable.unpaged() + ); + + assertThat(users).hasSize(4); + } + + @Test + public void findBySearchParams_WithPagination_ReturnsPaginated() { + int pageSize = 2; // Assuming a page size of 2 for testing purposes + + // Fetch the first page + Page<User> firstPage = userRepository.findBySearchParams( + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + PageRequest.of(0, pageSize) + ); + + assertThat(firstPage).hasSize(2); + assertThat(firstPage.getTotalElements()).isEqualTo(4); // Assuming there are at least 4 users + + // Fetch the second page + Page<User> secondPage = userRepository.findBySearchParams( + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + PageRequest.of(1, pageSize) + ); + + assertThat(secondPage).hasSize(2); + assertThat(secondPage.getTotalElements()).isEqualTo(4); // Assuming there are at least 4 users + } + + @Test + public void findBySearchParams_AllParams_returnSingle() { + Page<User> users = userRepository.findBySearchParams( + Optional.of("John"), + Optional.of("Doe"), + Optional.of("9707178239"), + Optional.of("example1@domain.com"), + Optional.of(LocalDate.of(1990, 1, 1)), + Optional.of("900101/1234"), + Optional.of(true), + Pageable.unpaged() + ); + + assertThat(users).hasSize(1); + assertThat(users.getContent().getFirst()) + .returns(UUID.fromString("5e4b3326-38b5-4484-8034-33d81f34bec2"), User::getId) + .returns("John", User::getFirstName) + .returns("Doe", User::getLastName) + .returns("9707178239", User::getPhoneNumber) + .returns("example1@domain.com", User::getEmail) + .returns("1990-01-01", u -> u.getBirthDate().toString()) + .returns(Nationality.CZ, User::getNationality) + .returns("900101/1234", User::getBirthNumber) + .returns(true, User::isActive); + } +} diff --git a/user-service/src/test/java/cz/muni/fi/obs/service/UserServiceTest.java b/user-service/src/test/java/cz/muni/fi/obs/service/UserServiceTest.java index 2c1979b..21ce721 100644 --- a/user-service/src/test/java/cz/muni/fi/obs/service/UserServiceTest.java +++ b/user-service/src/test/java/cz/muni/fi/obs/service/UserServiceTest.java @@ -15,8 +15,7 @@ import java.time.LocalDate; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class UserServiceTest { @@ -126,7 +125,7 @@ class UserServiceTest { } @Test - void deactivateUser_userDeactivated_returnsUser() { + void activateUser_userActivated_returnsUser() { User user = new User("Joe", "Doe", "123456789", @@ -136,17 +135,39 @@ class UserServiceTest { "900101" + "/123", true ); + when(userRepository.findByIdOrThrow(user.getId())).thenReturn(user); - when(userRepository.save(user)).thenReturn(user); + when(userRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); + + User response = userService.activateUser(user.getId()); + + verify(userRepository).findByIdOrThrow(user.getId()); + assertThat(response.isActive()).isEqualTo(true); + } + + @Test + void activateUser_userDeactivated_returnsUser() { + User user = new User("Joe", + "Doe", + "123456789", + "test@gmail.com", + LocalDate.now(), + Nationality.CZ, + "900101" + "/123", + true + ); + + when(userRepository.findByIdOrThrow(user.getId())).thenReturn(user); + when(userRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); User response = userService.deactivateUser(user.getId()); verify(userRepository).findByIdOrThrow(user.getId()); - assertThat(response).isEqualTo(user); + assertThat(response.isActive()).isEqualTo(false); } @Test - void activateUser_userActivated_returnsUser() { + void deactivateUser_userActivated_returnsUser() { User user = new User("Joe", "Doe", "123456789", @@ -156,12 +177,34 @@ class UserServiceTest { "900101" + "/123", true ); + when(userRepository.findByIdOrThrow(user.getId())).thenReturn(user); - when(userRepository.save(user)).thenReturn(user); + when(userRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); User response = userService.activateUser(user.getId()); verify(userRepository).findByIdOrThrow(user.getId()); - assertThat(response).isEqualTo(user); + assertThat(response.isActive()).isEqualTo(true); + } + + @Test + void deactivateUser_userDeactivated_returnsUser() { + User user = new User("Joe", + "Doe", + "123456789", + "test@gmail.com", + LocalDate.now(), + Nationality.CZ, + "900101" + "/123", + true + ); + + when(userRepository.findByIdOrThrow(user.getId())).thenReturn(user); + when(userRepository.save(any())).thenAnswer(invocation -> invocation.getArgument(0)); + + User response = userService.deactivateUser(user.getId()); + + verify(userRepository).findByIdOrThrow(user.getId()); + assertThat(response.isActive()).isEqualTo(false); } } diff --git a/user-service/src/test/resources/application-test.yml b/user-service/src/test/resources/application-test.yml new file mode 100644 index 0000000..2139754 --- /dev/null +++ b/user-service/src/test/resources/application-test.yml @@ -0,0 +1,10 @@ +spring: + datasource: + driver-class-name: org.h2.Driver + url: jdbc:h2:mem:testdb + username: sa + password: password + jpa: + hibernate: + ddl-auto: create-drop + database-platform: org.hibernate.dialect.H2Dialect diff --git a/user-service/src/test/resources/drop_all.sql b/user-service/src/test/resources/drop_all.sql new file mode 100644 index 0000000..66e6df9 --- /dev/null +++ b/user-service/src/test/resources/drop_all.sql @@ -0,0 +1,3 @@ +DELETE +FROM us_user u +where u.id = u.id;; diff --git a/user-service/src/test/resources/initialize_db.sql b/user-service/src/test/resources/initialize_db.sql new file mode 100644 index 0000000..6178244 --- /dev/null +++ b/user-service/src/test/resources/initialize_db.sql @@ -0,0 +1,10 @@ +INSERT INTO us_user(id, first_name, last_name, phone_number, email, birth_date, nationality, birth_number, active) +VALUES ('5e4b3326-38b5-4484-8034-33d81f34bec2', 'John', 'Doe', '9707178239', 'example1@domain.com', '1990-01-01', 'CZ', + '900101/1234', true), + ('5e4b3326-38b5-4484-8034-33d81f34bec3', 'Jane', 'Doe', '9707178238', 'example2@domain.com', '1991-02-02', 'SK', + '931028/4632', true), + ('5e4b3326-38b5-4484-8034-33d81f34bec4', 'Pete', 'Hel', '9707178237', 'example3@domain.com', '1992-03-03', 'CZ', + '761110/2983', false), + ('5e4b3326-38b5-4484-8034-33d81f34bec5', 'John', 'Doe', '9707178236', 'example4@domain.com', '1993-04-04', 'SK', + '580516/1472', false); + -- GitLab