Commit eea7e3f9 authored by Julien SADAOUI's avatar Julien SADAOUI
Browse files

feat: spring-reactor wording

parent 0526bdc6
......@@ -13,12 +13,14 @@ public interface AccountService {
Mono<Void> closeAccount(String accountNumber);
Flux<Account> getAccounts();
Mono<Account> getAccount(String accountNumber);
Mono<Transaction> creditAccount(String customerId, String accountNumber, BigDecimal amount);
Mono<Transaction> debitAccount(String customerId, String accountNumber, BigDecimal amount);
Flux<Transaction> getTransactionsByAccount(String accountNumber);
Flux<Account> getAccountsByCustomer(String email);
Flux<Transaction> getTransactions(String customerId, String accountNumber);
}
......@@ -3,20 +3,19 @@ package fr.ippon.codingdojo.service.impl;
import fr.ippon.codingdojo.domain.Account;
import fr.ippon.codingdojo.domain.Operation;
import fr.ippon.codingdojo.domain.Transaction;
import fr.ippon.codingdojo.domain.User;
import fr.ippon.codingdojo.repository.AccountRepository;
import fr.ippon.codingdojo.service.AccountService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.math.BigDecimal;
import java.time.LocalDate;
@Service
@Slf4j
// TODO Toutes les méthodes de cette classe sont sécurisées avec le rôle adéquates
// https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#el-pre-post-annotations
public class AccountServiceImpl implements AccountService {
private final AccountRepository accountRepository;
......@@ -26,58 +25,46 @@ public class AccountServiceImpl implements AccountService {
}
@Override
@PreAuthorize("hasRole('EMPLOYEE')")
public Mono<Account> openAccount(Account account) {
return accountRepository.save(account);
}
@Override
@PreAuthorize("hasRole('EMPLOYEE')")
public Mono<Void> closeAccount(String accountNumber) {
return accountRepository.findById(accountNumber)
.doOnNext(account -> log.info(account.toString()))
.flatMap(accountRepository::delete);
// TODO Supprimez le compte associé au numéro de compte donné en paramètre
return Mono.empty();
}
@Override
public Flux<Account> getAccounts() {
return accountRepository.findAll();
}
@Override
public Mono<Account> getAccount(String accountNumber) {
return accountRepository.findById(accountNumber);
}
@Override
@PreAuthorize("hasRole('CUSTOMER')")
public Mono<Transaction> creditAccount(String customerId, String accountNumber, BigDecimal amount) {
return createTransaction(customerId, accountNumber, Operation.CREDIT, amount);
}
@Override
@PreAuthorize("hasRole('CUSTOMER')")
public Mono<Transaction> debitAccount(String customerId, String accountNumber, BigDecimal amount) {
return createTransaction(customerId, accountNumber, Operation.DEBIT, amount);
}
private Mono<Transaction> createTransaction(String customerId, String accountNumber, Operation operation, BigDecimal amount) {
return accountRepository.findById(accountNumber)
.filter(account -> account.getCustomerId().equals(customerId))
.flatMap(account -> {
Transaction transaction = Transaction.builder()
.operation(operation)
.amount(amount)
.date(LocalDate.now())
.build();
BigDecimal newBalance = operation.compute(account.getBalance(), amount);
account.setBalance(newBalance);
account.getTransactions().add(transaction);
return accountRepository.save(account)
.then(Mono.just(transaction));
});
// TODO Ajoutez une nouvelle transaction de Débit ou Crédit à un compte.
// Note: La transaction affecte le montant du compte débité ou crédité.
return Mono.empty();
}
@Override
@PreAuthorize("hasAnyRole('EMPLOYEE','CUSTOMER')")
public Flux<Transaction> getTransactionsByAccount(String accountNumber) {
return accountRepository.findById(accountNumber)
.flatMapMany(account -> Flux.fromIterable(account.getTransactions()));
}
@PreAuthorize("hasAnyRole('EMPLOYEE','CUSTOMER')")
public Flux<Account> getAccountsByCustomer(String customerId) {
return accountRepository.findByCustomerId(customerId);
public Flux<Transaction> getTransactions(String customerId, String accountNumber) {
// TODO récupérez la liste des Transactions associées à un compte
return Flux.empty();
}
}
......@@ -3,9 +3,6 @@ package fr.ippon.codingdojo.service.impl;
import fr.ippon.codingdojo.domain.User;
import fr.ippon.codingdojo.repository.UserRepository;
import fr.ippon.codingdojo.service.UserService;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Mono;
......@@ -25,10 +22,9 @@ public class UserServiceImpl implements UserService {
@Override
public Mono<User> getCurrentUser() {
return ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.map(Authentication::getName)
.flatMap(this::getUser);
// TODO Utilisez la classe ReactiveSecurityContextHolder pour récupérer le nom de l'utilisateur connecté
// https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#jc-erms
return Mono.empty();
}
}
package fr.ippon.codingdojo.web;
import fr.ippon.codingdojo.domain.Account;
import fr.ippon.codingdojo.service.AccountService;
import fr.ippon.codingdojo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@Slf4j
public class AccountResource {
private final UserService userService;
private final AccountService accountService;
public AccountResource(UserService userService, AccountService accountService) {
this.userService = userService;
this.accountService = accountService;
}
// TODO Mappez l'URL '/account' avec la méthode 'GET'
// en retournant un flux de type application/json+stream
public Flux<Account> getAll() {
return accountService.getAccounts();
}
// TODO Mappez l'URL '/account/{number}' avec la méthode 'GET'
public Mono<ResponseEntity<Account>> get(String accountNumber) {
log.debug("REST request to get Account : {}", accountNumber);
// TODO Renvoyez le compte associé au numéro de compte donné en paramètre.
// Si le compte n'est pas trouvé, retournez une réponse HTTP avec le code 404
return Mono.empty();
}
// TODO Mappez l'URL '/account' avec la méthode 'POST'
public Mono<ResponseEntity<Account>> create(UriComponentsBuilder uriComponentsBuilder, Account account) {
log.debug("REST request to open Account : {}", account);
// TODO Créez l'ouverture d'une nouveau compte et renvoyez une réponse HTTP avec l'URL de la ressource, le compte créé
// et le status 201. Renvoyez une réponse HTTP avec le status 404 si le client associé au compte n'existe pas.
return Mono.empty();
}
// TODO Mappez l'URL '/account/{number}' avec la méthode 'DELETE'
public Mono<ResponseEntity<Void>> delete(String accountNumber) {
log.debug("REST request to close Account : {}", accountNumber);
// TODO Supprimez le compte associé au numéro de compte donné en paramètre
// et renvoyez une réponse HTTP avec le code 204.
return Mono.empty();
}
}
\ No newline at end of file
package fr.ippon.codingdojo.web;
import fr.ippon.codingdojo.domain.Transaction;
import fr.ippon.codingdojo.service.AccountService;
import fr.ippon.codingdojo.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.math.BigDecimal;
@RestController
@Slf4j
public class TransactionResource {
private final AccountService accountService;
private final UserService userService;
public TransactionResource(AccountService accountService, UserService userService) {
this.accountService = accountService;
this.userService = userService;
}
// TODO Mappez l'URL '/account/{number}/transaction/' avec la méthode 'GET'
// en retournant un flux de type application/json+stream
public Flux<Transaction> getAll(String accountNumber) {
return Flux.empty();
}
// TODO Mappez l'URL '/account/{number}/transaction/credit' avec la méthode 'POST'
public Mono<ResponseEntity<Transaction>> credit(String accountNumber, BigDecimal amount) {
log.debug("REST request to create Credit Operation : (Account Number={}/Amount={})", accountNumber, amount);
// TODO Créez une transaction de type Crédit pour l'utilisateur courant et renvoyez une réponse HTTP avec la transaction créée
// et le status 201. Renvoyez une réponse HTTP avec le status 404 si l'utilisateur n'est pas présent.
return Mono.empty();
}
// TODO Mappez l'URL '/account/{number}/transaction/debit' avec la méthode 'POST'
public Mono<ResponseEntity<Transaction>> debit(String accountNumber, BigDecimal amount) {
log.debug("REST request to create Debit Operation : (Account Number={}/Amount={})", accountNumber, amount);
// TODO Créez une transaction de type Crédit pour l'utilisateur courant et renvoyez une réponse HTTP avec la transaction créée
// et le status 201. Renvoyez une réponse HTTP avec le status 404 si l'utilisateur n'est pas présent.
return Mono.empty();
}
}
spring:
application:
name: spring-reactor-mongo
data:
mongodb:
database: spring-reactor-mongomongo
port: 27017
reactive-repositories:
enabled: true
logging:
level:
root: WARN
com:
mongodb: WARN
springframework:
data: WARN
web:
reactive: WARN
fr:
ippon:
codingdojo: DEBUG
io:
reactor:
core: WARN
\ No newline at end of file
......@@ -8,20 +8,15 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.reactive.server.FluxExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.math.BigDecimal;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
public class AccountControllerTest {
// TODO Utilisez l'annotation permettant de lancer le test avec un utilisateur de type 'EMPLOYEE'
// https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#test-method-withmockuser
public class AccountResourceTest {
@Autowired
private ApplicationContext applicationContext;
......@@ -35,74 +30,54 @@ public class AccountControllerTest {
public void setUp() {
mongoTemplate.dropCollection(Account.class);
client = WebTestClient
.bindToApplicationContext(applicationContext)
.apply(springSecurity())
.configureClient()
.build();
// TODO Configurez l'utilitaire WebTestClient
// https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#webtestclient
client = null;
}
@Test
@WithMockUser(username = "employee@email.org", roles = {"EMPLOYEE"})
@DisplayName("Should open account for given user")
@DisplayName("Should open account for given customer")
public void should_open_new_account() {
createAccount("customer@email.org", "Checking Account", BigDecimal.valueOf(2000L));
}
@Test
@WithMockUser(username = "employee@email.org", roles = {"EMPLOYEE"})
@DisplayName("Should close account for given user")
@DisplayName("Should close account for given customer")
public void should_close_account() {
Account account = createAccount("customer@email.org", "Checking Account", BigDecimal.TEN);
this.client
.mutateWith(csrf())
.delete()
.uri("/account/{number}", account.getNumber())
.exchange()
.expectStatus().isOk();
// TODO Utilisez WebTestClient pour récupérer le compte crée ci-dessus
// https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#webtestclient-tests
// TODO vérifiez le flux de données avec la classe StepVerifier
// https://projectreactor.io/docs/core/release/reference/index.html#_testing_a_scenario_with_code_stepverifier_code
}
@Test
@WithMockUser(value = "customer@email.org", roles = {"EMPLOYEE", "CUSTOMER"})
@DisplayName("Should get all accounts for current user")
public void should_get_all_accounts_for_current_user() {
Account account1 = createAccount("customer@email.org", "Savings Account 1", BigDecimal.valueOf(1000L));
Account account2 = createAccount("customer@email.org", "Savings Account 2", BigDecimal.valueOf(2500L));
Account account3 = createAccount("customer@email.org", "Savings Account 3", BigDecimal.valueOf(500L));
FluxExchangeResult<Account> result = this.client
.mutateWith(csrf())
.get()
.uri("/account")
.exchange()
.expectStatus().isOk()
.returnResult(Account.class);
StepVerifier.create(result.getResponseBody())
.expectNext(account1, account2, account3)
.expectComplete()
.verify();
@DisplayName("Should get all accounts")
public void should_get_all_accounts() {
Account account1 = createAccount("customer@email.org", "Checking Account 1", BigDecimal.valueOf(1000L));
Account account2 = createAccount("customer@email.org", "Checking Account 2", BigDecimal.valueOf(2500L));
Account account3 = createAccount("customer@email.org", "Checking Account 3", BigDecimal.valueOf(500L));
// TODO Utilisez WebTestClient pour récupérer l'ensemble des comptes des clients
// https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#webtestclient-tests
// TODO vérifiez le flux de données avec la classe StepVerifier
// https://projectreactor.io/docs/core/release/reference/index.html#_testing_a_scenario_with_code_stepverifier_code
}
@Test
@WithMockUser(value = "customer@email.org", roles = {"EMPLOYEE", "CUSTOMER"})
@DisplayName("Should get given account for current user")
public void should_get_account_for_current_user() {
@DisplayName("Should get account")
public void should_get_account() {
Account account = createAccount("customer@email.org", "Checking Account", BigDecimal.valueOf(1000L));
FluxExchangeResult<Account> result = this.client
.mutateWith(csrf())
.get()
.uri("/account/{number}", account.getNumber())
.exchange()
.expectStatus().isOk()
.returnResult(Account.class);
StepVerifier.create(result.getResponseBody())
.expectNext(account)
.expectComplete()
.verify();
// TODO Utilisez WebTestClient pour créer un compte
// https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#webtestclient-tests
FluxExchangeResult<Account> result = null;
// TODO vérifiez le flux de données avec la classe StepVerifier
// https://projectreactor.io/docs/core/release/reference/index.html#_testing_a_scenario_with_code_stepverifier_code
}
private Account createAccount(String customerId, String description, BigDecimal balance) {
......@@ -112,16 +87,8 @@ public class AccountControllerTest {
.balance(balance)
.build();
return this.client
.mutateWith(csrf())
.post()
.uri("/account")
.contentType(MediaType.APPLICATION_JSON)
.body(Mono.just(account), Account.class)
.exchange()
.expectStatus().isCreated()
.returnResult(Account.class)
.getResponseBody()
.blockFirst();
// TODO Utilisez WebTestClient pour créer un compte et récupérez le compte créé
// https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#webtestclient-tests
return null;
}
}
......@@ -11,20 +11,17 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.reactive.server.FluxExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import java.math.BigDecimal;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
public class TransactionControllerTest {
// TODO Utilisez l'annotation permettant de lancer le test avec un utilisateur de type 'CUSTOMER'
// https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#test-method-withmockuser
public class TransactionResourceTest {
@Autowired
private ApplicationContext applicationContext;
......@@ -53,63 +50,45 @@ public class TransactionControllerTest {
assertThat(account).as("Account created ?").isNotNull();
accountNumber = account.getNumber();
client = WebTestClient
.bindToApplicationContext(applicationContext)
.apply(springSecurity())
.configureClient()
.build();
// TODO Configurez l'utilitaire WebTestClient
// https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#webtestclient
client = null;
}
@Test
@WithMockUser(value = "customer@email.org", roles = {"CUSTOMER"})
@DisplayName("Should create credit transaction with current user")
public void should_create_credit_transaction() {
createPost(Operation.CREDIT, BigDecimal.valueOf(210L));
createTransaction(Operation.CREDIT, BigDecimal.valueOf(210L));
}
@Test
@WithMockUser(value = "customer@email.org", roles = {"CUSTOMER"})
@DisplayName("Should create new debit transaction with current user")
public void should_create_debit_transaction() {
createPost(Operation.DEBIT, BigDecimal.valueOf(150L));
createTransaction(Operation.DEBIT, BigDecimal.valueOf(150L));
}
@Test
@WithMockUser(value = "customer@email.org", roles = {"CUSTOMER"})
@DisplayName("Should get all transactions for current user")
public void should_get_all_transactions() {
Transaction transaction1 = createPost(Operation.CREDIT, BigDecimal.valueOf(20L));
Transaction transaction2 = createPost(Operation.DEBIT, BigDecimal.valueOf(30L));
FluxExchangeResult<Transaction> result = this.client
.mutateWith(csrf())
.get()
.uri("/account/{number}/transaction", accountNumber)
.exchange()
.expectStatus().isOk()
.returnResult(Transaction.class);
StepVerifier.create(result.getResponseBody())
.expectNext(transaction1, transaction2)
.expectComplete()
.verify();
Transaction transaction1 = createTransaction(Operation.CREDIT, BigDecimal.valueOf(20L));
Transaction transaction2 = createTransaction(Operation.DEBIT, BigDecimal.valueOf(30L));
// TODO Utilisez WebTestClient pour créer une transaction
// https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#webtestclient-tests
FluxExchangeResult<Transaction> result = null;
// TODO vérifiez le flux de données avec la classe StepVerifier
// https://projectreactor.io/docs/core/release/reference/index.html#_testing_a_scenario_with_code_stepverifier_code
}
private Transaction createPost(Operation operation, BigDecimal amount) {
private Transaction createTransaction(Operation operation, BigDecimal amount) {
String uri = (operation == Operation.DEBIT)
? "/account/{number}/transaction/debit" : "/account/{number}/transaction/credit";
return this.client
.mutateWith(csrf())
.post()
.uri(uri, accountNumber)
.body(Mono.just(amount), BigDecimal.class)
.exchange()
.expectStatus().isCreated()
.returnResult(Transaction.class)
.getResponseBody()
.blockFirst();
// TODO Utilisez WebTestClient pour créer une transaction et récupérez la transaction créée
// https://docs.spring.io/spring/docs/current/spring-framework-reference/testing.html#webtestclient-tests
return null;
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment