From 95ed5f6ae73c2a53fb301a7ce31b90170d8a72d3 Mon Sep 17 00:00:00 2001 From: Piotr Dec Date: Sat, 25 May 2024 00:33:33 +0200 Subject: [PATCH] feat: ExchangeController & tests --- .../exception/UserNotFoundException.java | 11 +++++ .../ztsh/wymiana/service/CurrencyService.java | 3 +- .../eu/ztsh/wymiana/service/UserService.java | 2 + .../web/controller/ExchangeController.java | 42 +++++++++++++++++++ .../web/controller/UserController.java | 9 +++- .../java/eu/ztsh/wymiana/EntityCreator.java | 2 +- .../wymiana/service/CurrencyServiceTest.java | 3 +- .../web/ApplicationIntegrationTests.java | 34 +++++++-------- 8 files changed, 85 insertions(+), 21 deletions(-) create mode 100644 src/main/java/eu/ztsh/wymiana/exception/UserNotFoundException.java create mode 100644 src/main/java/eu/ztsh/wymiana/web/controller/ExchangeController.java diff --git a/src/main/java/eu/ztsh/wymiana/exception/UserNotFoundException.java b/src/main/java/eu/ztsh/wymiana/exception/UserNotFoundException.java new file mode 100644 index 0000000..7390eb3 --- /dev/null +++ b/src/main/java/eu/ztsh/wymiana/exception/UserNotFoundException.java @@ -0,0 +1,11 @@ +package eu.ztsh.wymiana.exception; + +import eu.ztsh.wymiana.web.model.CurrencyExchangeRequest; + +public class UserNotFoundException extends RuntimeException { + + public UserNotFoundException(CurrencyExchangeRequest entity) { + super("User with PESEL %s not found".formatted(entity.pesel())); + } + +} diff --git a/src/main/java/eu/ztsh/wymiana/service/CurrencyService.java b/src/main/java/eu/ztsh/wymiana/service/CurrencyService.java index b00f761..401626a 100644 --- a/src/main/java/eu/ztsh/wymiana/service/CurrencyService.java +++ b/src/main/java/eu/ztsh/wymiana/service/CurrencyService.java @@ -2,6 +2,7 @@ package eu.ztsh.wymiana.service; import eu.ztsh.wymiana.exception.ExchangeFailedException; import eu.ztsh.wymiana.exception.InsufficientFundsException; +import eu.ztsh.wymiana.exception.UserNotFoundException; import eu.ztsh.wymiana.model.Currency; import eu.ztsh.wymiana.model.User; import eu.ztsh.wymiana.validation.InstanceValidator; @@ -49,7 +50,7 @@ public class CurrencyService { user.currencies().putAll(exchanged); return userService.update(user); }) - .orElseThrow(ExchangeFailedException::new); + .orElseThrow(() -> new UserNotFoundException(request)); } private Currency create(String symbol) { diff --git a/src/main/java/eu/ztsh/wymiana/service/UserService.java b/src/main/java/eu/ztsh/wymiana/service/UserService.java index 0bb7945..857bc0f 100644 --- a/src/main/java/eu/ztsh/wymiana/service/UserService.java +++ b/src/main/java/eu/ztsh/wymiana/service/UserService.java @@ -9,9 +9,11 @@ import eu.ztsh.wymiana.web.model.UserCreateRequest; import lombok.RequiredArgsConstructor; import org.hibernate.validator.constraints.pl.PESEL; import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; import java.util.Optional; +@Validated @RequiredArgsConstructor @Service public class UserService { diff --git a/src/main/java/eu/ztsh/wymiana/web/controller/ExchangeController.java b/src/main/java/eu/ztsh/wymiana/web/controller/ExchangeController.java new file mode 100644 index 0000000..ae666ef --- /dev/null +++ b/src/main/java/eu/ztsh/wymiana/web/controller/ExchangeController.java @@ -0,0 +1,42 @@ +package eu.ztsh.wymiana.web.controller; + +import eu.ztsh.wymiana.exception.InsufficientFundsException; +import eu.ztsh.wymiana.exception.UserAlreadyExistsException; +import eu.ztsh.wymiana.exception.UserNotFoundException; +import eu.ztsh.wymiana.service.CurrencyService; +import eu.ztsh.wymiana.validation.ValidationFailedException; +import eu.ztsh.wymiana.web.model.CurrencyExchangeRequest; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@Validated +@RestController +@RequestMapping(path = "/api/exchange", produces = "application/json") +public class ExchangeController { + + private final CurrencyService currencyService; + + @PostMapping + public ResponseEntity exchange(@Valid @RequestBody CurrencyExchangeRequest request) { + try { + return ResponseEntity.status(200).body(currencyService.exchange(request)); + } catch (Exception e) { + var status = switch (e) { + case InsufficientFundsException ignored -> HttpStatus.BAD_REQUEST; + case UserNotFoundException ignored -> HttpStatus.NOT_FOUND; + default -> HttpStatus.INTERNAL_SERVER_ERROR; + }; + return ResponseEntity.status(status).body(e.getMessage()); + } + } + +} diff --git a/src/main/java/eu/ztsh/wymiana/web/controller/UserController.java b/src/main/java/eu/ztsh/wymiana/web/controller/UserController.java index da83862..319d93a 100644 --- a/src/main/java/eu/ztsh/wymiana/web/controller/UserController.java +++ b/src/main/java/eu/ztsh/wymiana/web/controller/UserController.java @@ -6,6 +6,7 @@ import eu.ztsh.wymiana.service.UserService; import eu.ztsh.wymiana.validation.ValidationFailedException; import eu.ztsh.wymiana.web.model.UserCreateRequest; import jakarta.validation.Valid; +import jakarta.validation.ValidationException; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -27,7 +28,13 @@ public class UserController { @GetMapping("{pesel}") public ResponseEntity get(@PathVariable("pesel") String pesel) { - return ResponseEntity.of(userService.get(pesel)); + try { + return userService.get(pesel) + .map(ResponseEntity::ok) + .orElse(ResponseEntity.notFound().build()); + } catch (ValidationException e) { + return ResponseEntity.badRequest().build(); + } } @PostMapping diff --git a/src/test/java/eu/ztsh/wymiana/EntityCreator.java b/src/test/java/eu/ztsh/wymiana/EntityCreator.java index 5cb9498..617cbfa 100644 --- a/src/test/java/eu/ztsh/wymiana/EntityCreator.java +++ b/src/test/java/eu/ztsh/wymiana/EntityCreator.java @@ -48,7 +48,7 @@ public class EntityCreator { currencies.put("PLN", new Currency("PLN", pln)); } if (usd > 0) { - currencies.put("USD", new Currency("USD", pln)); + currencies.put("USD", new Currency("USD", usd)); } return new User(Constants.NAME, Constants.SURNAME, Constants.PESEL, currencies); } diff --git a/src/test/java/eu/ztsh/wymiana/service/CurrencyServiceTest.java b/src/test/java/eu/ztsh/wymiana/service/CurrencyServiceTest.java index 9603e00..c44625e 100644 --- a/src/test/java/eu/ztsh/wymiana/service/CurrencyServiceTest.java +++ b/src/test/java/eu/ztsh/wymiana/service/CurrencyServiceTest.java @@ -5,6 +5,7 @@ import eu.ztsh.wymiana.RepositoryBasedTest; import eu.ztsh.wymiana.data.repository.UserRepository; import eu.ztsh.wymiana.exception.ExchangeFailedException; import eu.ztsh.wymiana.exception.InsufficientFundsException; +import eu.ztsh.wymiana.exception.UserNotFoundException; import eu.ztsh.wymiana.validation.InstanceValidator; import eu.ztsh.wymiana.validation.ValidationFailedException; import jakarta.transaction.Transactional; @@ -175,7 +176,7 @@ class CurrencyServiceTest extends RepositoryBasedTest { .toSell(USD_SELL) .build(); assertThatThrownBy(() -> currencyService.exchange(entity)) - .isInstanceOf(ExchangeFailedException.class); + .isInstanceOf(UserNotFoundException.class); } @Test diff --git a/src/test/java/eu/ztsh/wymiana/web/ApplicationIntegrationTests.java b/src/test/java/eu/ztsh/wymiana/web/ApplicationIntegrationTests.java index 38fdfae..816cc99 100644 --- a/src/test/java/eu/ztsh/wymiana/web/ApplicationIntegrationTests.java +++ b/src/test/java/eu/ztsh/wymiana/web/ApplicationIntegrationTests.java @@ -4,7 +4,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import eu.ztsh.wymiana.EntityCreator; import eu.ztsh.wymiana.WireMockExtension; -import eu.ztsh.wymiana.model.User; import eu.ztsh.wymiana.util.UserMapper; import org.junit.jupiter.api.ClassOrderer; import org.junit.jupiter.api.DisplayName; @@ -83,7 +82,7 @@ class ApplicationIntegrationTests { .uri(endpoint) .bodyValue(EntityCreator.userRequest().pln(100).build()) .exchange() - .expectStatus().is2xxSuccessful(); + .expectStatus().isNoContent(); } @Test @@ -94,7 +93,7 @@ class ApplicationIntegrationTests { .bodyValue(EntityCreator.userRequest().pesel(INVALID_PESEL).build()) .exchange() .expectStatus() - .is4xxClientError(); + .isBadRequest(); } @Test @@ -104,7 +103,7 @@ class ApplicationIntegrationTests { .uri(endpoint) .bodyValue(EntityCreator.userRequest().build()) .exchange() - .expectStatus().is4xxClientError() + .expectStatus().isEqualTo(409) .expectBody(String.class).isEqualTo("User with PESEL %s already exists".formatted(PESEL)); } @@ -114,7 +113,7 @@ class ApplicationIntegrationTests { webTestClient.get() .uri(endpoint.concat("/").concat(PESEL)) .exchange() - .expectStatus().is2xxSuccessful() + .expectStatus().isOk() .expectBody().json(asJson(UserMapper.entityToPojo(EntityCreator.userEntity().pln(100).build()))); } @@ -124,7 +123,7 @@ class ApplicationIntegrationTests { webTestClient.get() .uri(endpoint.concat("/").concat(ANOTHER_PESEL)) .exchange() - .expectStatus().is4xxClientError(); + .expectStatus().isNotFound(); } @Test @@ -133,7 +132,7 @@ class ApplicationIntegrationTests { webTestClient.get() .uri(endpoint.concat("/").concat(INVALID_PESEL)) .exchange() - .expectStatus().is4xxClientError(); + .expectStatus().isBadRequest(); } } @@ -158,18 +157,19 @@ class ApplicationIntegrationTests { .toSell(PLN) .build()) .exchange() - .expectStatus().is5xxServerError(); + .expectStatus().isEqualTo(500); } @Test @DisplayName("03.2: Perform valid money exchange") - void exchangeTest() throws JsonProcessingException { + void exchangeTest() { var date = getTodayOrLastFriday(); WireMockExtension.response( URI_PATTERN.formatted(date), 200, - new ObjectMapper().writeValueAsString(EntityCreator.rates(date)) + asJson(EntityCreator.rates(date)) ); + var expected = asJson(EntityCreator.user(100 - PLN, USD_BUY)); webTestClient.post() .uri(endpoint) .bodyValue(EntityCreator.exchangeRequest() @@ -178,8 +178,9 @@ class ApplicationIntegrationTests { .toSell(PLN) .build()) .exchange() - .expectStatus().is2xxSuccessful() - .expectBody(User.class).isEqualTo(EntityCreator.user(100 - PLN, USD_BUY)); + .expectStatus().isOk() + .expectBody().json(expected); + WireMockExtension.verifyGet(2, URI_PATTERN.formatted(date)); } @Test @@ -194,8 +195,8 @@ class ApplicationIntegrationTests { .toSell(PLN) .build()) .exchange() - .expectStatus().is4xxClientError() - .expectBody(String.class).isEqualTo("An exchange error has occurred"); + .expectStatus().isNotFound() + .expectBody(String.class).isEqualTo("User with PESEL %s not found".formatted(ANOTHER_PESEL)); } @Test @@ -210,7 +211,7 @@ class ApplicationIntegrationTests { .toSell(PLN) .build()) .exchange() - .expectStatus().is4xxClientError(); + .expectStatus().isBadRequest(); } @Test @@ -219,13 +220,12 @@ class ApplicationIntegrationTests { webTestClient.post() .uri(endpoint) .bodyValue(EntityCreator.exchangeRequest() - .pesel(ANOTHER_PESEL) .from(USD_SYMBOL) .to(PLN_SYMBOL) .toBuy(PLN) .build()) .exchange() - .expectStatus().is4xxClientError(); + .expectStatus().isBadRequest(); } private String getTodayOrLastFriday() {