feat: ExchangeController & tests
This commit is contained in:
parent
720937bd6c
commit
95ed5f6ae7
8 changed files with 85 additions and 21 deletions
|
@ -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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package eu.ztsh.wymiana.service;
|
||||||
|
|
||||||
import eu.ztsh.wymiana.exception.ExchangeFailedException;
|
import eu.ztsh.wymiana.exception.ExchangeFailedException;
|
||||||
import eu.ztsh.wymiana.exception.InsufficientFundsException;
|
import eu.ztsh.wymiana.exception.InsufficientFundsException;
|
||||||
|
import eu.ztsh.wymiana.exception.UserNotFoundException;
|
||||||
import eu.ztsh.wymiana.model.Currency;
|
import eu.ztsh.wymiana.model.Currency;
|
||||||
import eu.ztsh.wymiana.model.User;
|
import eu.ztsh.wymiana.model.User;
|
||||||
import eu.ztsh.wymiana.validation.InstanceValidator;
|
import eu.ztsh.wymiana.validation.InstanceValidator;
|
||||||
|
@ -49,7 +50,7 @@ public class CurrencyService {
|
||||||
user.currencies().putAll(exchanged);
|
user.currencies().putAll(exchanged);
|
||||||
return userService.update(user);
|
return userService.update(user);
|
||||||
})
|
})
|
||||||
.orElseThrow(ExchangeFailedException::new);
|
.orElseThrow(() -> new UserNotFoundException(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Currency create(String symbol) {
|
private Currency create(String symbol) {
|
||||||
|
|
|
@ -9,9 +9,11 @@ import eu.ztsh.wymiana.web.model.UserCreateRequest;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.hibernate.validator.constraints.pl.PESEL;
|
import org.hibernate.validator.constraints.pl.PESEL;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Validated
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Service
|
@Service
|
||||||
public class UserService {
|
public class UserService {
|
||||||
|
|
|
@ -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<Object> 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ import eu.ztsh.wymiana.service.UserService;
|
||||||
import eu.ztsh.wymiana.validation.ValidationFailedException;
|
import eu.ztsh.wymiana.validation.ValidationFailedException;
|
||||||
import eu.ztsh.wymiana.web.model.UserCreateRequest;
|
import eu.ztsh.wymiana.web.model.UserCreateRequest;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
|
import jakarta.validation.ValidationException;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
|
@ -27,7 +28,13 @@ public class UserController {
|
||||||
|
|
||||||
@GetMapping("{pesel}")
|
@GetMapping("{pesel}")
|
||||||
public ResponseEntity<User> get(@PathVariable("pesel") String pesel) {
|
public ResponseEntity<User> 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
|
@PostMapping
|
||||||
|
|
|
@ -48,7 +48,7 @@ public class EntityCreator {
|
||||||
currencies.put("PLN", new Currency("PLN", pln));
|
currencies.put("PLN", new Currency("PLN", pln));
|
||||||
}
|
}
|
||||||
if (usd > 0) {
|
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);
|
return new User(Constants.NAME, Constants.SURNAME, Constants.PESEL, currencies);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import eu.ztsh.wymiana.RepositoryBasedTest;
|
||||||
import eu.ztsh.wymiana.data.repository.UserRepository;
|
import eu.ztsh.wymiana.data.repository.UserRepository;
|
||||||
import eu.ztsh.wymiana.exception.ExchangeFailedException;
|
import eu.ztsh.wymiana.exception.ExchangeFailedException;
|
||||||
import eu.ztsh.wymiana.exception.InsufficientFundsException;
|
import eu.ztsh.wymiana.exception.InsufficientFundsException;
|
||||||
|
import eu.ztsh.wymiana.exception.UserNotFoundException;
|
||||||
import eu.ztsh.wymiana.validation.InstanceValidator;
|
import eu.ztsh.wymiana.validation.InstanceValidator;
|
||||||
import eu.ztsh.wymiana.validation.ValidationFailedException;
|
import eu.ztsh.wymiana.validation.ValidationFailedException;
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
@ -175,7 +176,7 @@ class CurrencyServiceTest extends RepositoryBasedTest {
|
||||||
.toSell(USD_SELL)
|
.toSell(USD_SELL)
|
||||||
.build();
|
.build();
|
||||||
assertThatThrownBy(() -> currencyService.exchange(entity))
|
assertThatThrownBy(() -> currencyService.exchange(entity))
|
||||||
.isInstanceOf(ExchangeFailedException.class);
|
.isInstanceOf(UserNotFoundException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -4,7 +4,6 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import eu.ztsh.wymiana.EntityCreator;
|
import eu.ztsh.wymiana.EntityCreator;
|
||||||
import eu.ztsh.wymiana.WireMockExtension;
|
import eu.ztsh.wymiana.WireMockExtension;
|
||||||
import eu.ztsh.wymiana.model.User;
|
|
||||||
import eu.ztsh.wymiana.util.UserMapper;
|
import eu.ztsh.wymiana.util.UserMapper;
|
||||||
import org.junit.jupiter.api.ClassOrderer;
|
import org.junit.jupiter.api.ClassOrderer;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
|
@ -83,7 +82,7 @@ class ApplicationIntegrationTests {
|
||||||
.uri(endpoint)
|
.uri(endpoint)
|
||||||
.bodyValue(EntityCreator.userRequest().pln(100).build())
|
.bodyValue(EntityCreator.userRequest().pln(100).build())
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().is2xxSuccessful();
|
.expectStatus().isNoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -94,7 +93,7 @@ class ApplicationIntegrationTests {
|
||||||
.bodyValue(EntityCreator.userRequest().pesel(INVALID_PESEL).build())
|
.bodyValue(EntityCreator.userRequest().pesel(INVALID_PESEL).build())
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus()
|
.expectStatus()
|
||||||
.is4xxClientError();
|
.isBadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -104,7 +103,7 @@ class ApplicationIntegrationTests {
|
||||||
.uri(endpoint)
|
.uri(endpoint)
|
||||||
.bodyValue(EntityCreator.userRequest().build())
|
.bodyValue(EntityCreator.userRequest().build())
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().is4xxClientError()
|
.expectStatus().isEqualTo(409)
|
||||||
.expectBody(String.class).isEqualTo("User with PESEL %s already exists".formatted(PESEL));
|
.expectBody(String.class).isEqualTo("User with PESEL %s already exists".formatted(PESEL));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +113,7 @@ class ApplicationIntegrationTests {
|
||||||
webTestClient.get()
|
webTestClient.get()
|
||||||
.uri(endpoint.concat("/").concat(PESEL))
|
.uri(endpoint.concat("/").concat(PESEL))
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().is2xxSuccessful()
|
.expectStatus().isOk()
|
||||||
.expectBody().json(asJson(UserMapper.entityToPojo(EntityCreator.userEntity().pln(100).build())));
|
.expectBody().json(asJson(UserMapper.entityToPojo(EntityCreator.userEntity().pln(100).build())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,7 +123,7 @@ class ApplicationIntegrationTests {
|
||||||
webTestClient.get()
|
webTestClient.get()
|
||||||
.uri(endpoint.concat("/").concat(ANOTHER_PESEL))
|
.uri(endpoint.concat("/").concat(ANOTHER_PESEL))
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().is4xxClientError();
|
.expectStatus().isNotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -133,7 +132,7 @@ class ApplicationIntegrationTests {
|
||||||
webTestClient.get()
|
webTestClient.get()
|
||||||
.uri(endpoint.concat("/").concat(INVALID_PESEL))
|
.uri(endpoint.concat("/").concat(INVALID_PESEL))
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().is4xxClientError();
|
.expectStatus().isBadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -158,18 +157,19 @@ class ApplicationIntegrationTests {
|
||||||
.toSell(PLN)
|
.toSell(PLN)
|
||||||
.build())
|
.build())
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().is5xxServerError();
|
.expectStatus().isEqualTo(500);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("03.2: Perform valid money exchange")
|
@DisplayName("03.2: Perform valid money exchange")
|
||||||
void exchangeTest() throws JsonProcessingException {
|
void exchangeTest() {
|
||||||
var date = getTodayOrLastFriday();
|
var date = getTodayOrLastFriday();
|
||||||
WireMockExtension.response(
|
WireMockExtension.response(
|
||||||
URI_PATTERN.formatted(date),
|
URI_PATTERN.formatted(date),
|
||||||
200,
|
200,
|
||||||
new ObjectMapper().writeValueAsString(EntityCreator.rates(date))
|
asJson(EntityCreator.rates(date))
|
||||||
);
|
);
|
||||||
|
var expected = asJson(EntityCreator.user(100 - PLN, USD_BUY));
|
||||||
webTestClient.post()
|
webTestClient.post()
|
||||||
.uri(endpoint)
|
.uri(endpoint)
|
||||||
.bodyValue(EntityCreator.exchangeRequest()
|
.bodyValue(EntityCreator.exchangeRequest()
|
||||||
|
@ -178,8 +178,9 @@ class ApplicationIntegrationTests {
|
||||||
.toSell(PLN)
|
.toSell(PLN)
|
||||||
.build())
|
.build())
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().is2xxSuccessful()
|
.expectStatus().isOk()
|
||||||
.expectBody(User.class).isEqualTo(EntityCreator.user(100 - PLN, USD_BUY));
|
.expectBody().json(expected);
|
||||||
|
WireMockExtension.verifyGet(2, URI_PATTERN.formatted(date));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -194,8 +195,8 @@ class ApplicationIntegrationTests {
|
||||||
.toSell(PLN)
|
.toSell(PLN)
|
||||||
.build())
|
.build())
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().is4xxClientError()
|
.expectStatus().isNotFound()
|
||||||
.expectBody(String.class).isEqualTo("An exchange error has occurred");
|
.expectBody(String.class).isEqualTo("User with PESEL %s not found".formatted(ANOTHER_PESEL));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -210,7 +211,7 @@ class ApplicationIntegrationTests {
|
||||||
.toSell(PLN)
|
.toSell(PLN)
|
||||||
.build())
|
.build())
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().is4xxClientError();
|
.expectStatus().isBadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -219,13 +220,12 @@ class ApplicationIntegrationTests {
|
||||||
webTestClient.post()
|
webTestClient.post()
|
||||||
.uri(endpoint)
|
.uri(endpoint)
|
||||||
.bodyValue(EntityCreator.exchangeRequest()
|
.bodyValue(EntityCreator.exchangeRequest()
|
||||||
.pesel(ANOTHER_PESEL)
|
|
||||||
.from(USD_SYMBOL)
|
.from(USD_SYMBOL)
|
||||||
.to(PLN_SYMBOL)
|
.to(PLN_SYMBOL)
|
||||||
.toBuy(PLN)
|
.toBuy(PLN)
|
||||||
.build())
|
.build())
|
||||||
.exchange()
|
.exchange()
|
||||||
.expectStatus().is4xxClientError();
|
.expectStatus().isBadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getTodayOrLastFriday() {
|
private String getTodayOrLastFriday() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue