fixed the tests
This commit is contained in:
@@ -47,4 +47,14 @@ class AccountMapper {
|
|||||||
return new ActivityWindow(mappedActivities);
|
return new ActivityWindow(mappedActivities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ActivityJpaEntity mapToJpaEntity(Activity activity) {
|
||||||
|
return new ActivityJpaEntity(
|
||||||
|
activity.getId() == null ? null : activity.getId().getValue(),
|
||||||
|
activity.getTimestamp(),
|
||||||
|
activity.getOwnerAccountId().getValue(),
|
||||||
|
activity.getSourceAccountId().getValue(),
|
||||||
|
activity.getTargetAccountId().getValue(),
|
||||||
|
activity.getMoney().getAmount().longValue());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,18 +62,9 @@ class AccountPersistenceAdapter implements
|
|||||||
public void updateActivities(Account account) {
|
public void updateActivities(Account account) {
|
||||||
for (Activity activity : account.getActivityWindow().getActivities()) {
|
for (Activity activity : account.getActivityWindow().getActivities()) {
|
||||||
if (activity.getId() == null) {
|
if (activity.getId() == null) {
|
||||||
activityRepository.save(mapToJpa(activity));
|
activityRepository.save(accountMapper.mapToJpaEntity(activity));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ActivityJpaEntity mapToJpa(Activity activity) {
|
|
||||||
return new ActivityJpaEntity(
|
|
||||||
activity.getId() == null ? null : activity.getId().getValue(),
|
|
||||||
activity.getTimestamp(),
|
|
||||||
activity.getOwnerAccountId().getValue(),
|
|
||||||
activity.getSourceAccountId().getValue(),
|
|
||||||
activity.getTargetAccountId().getValue(),
|
|
||||||
activity.getMoney().getAmount().longValue());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package io.reflectoring.cashpal.adapter.web;
|
||||||
|
|
||||||
|
class AccountResource {
|
||||||
|
}
|
||||||
@@ -4,12 +4,9 @@ import io.reflectoring.cashpal.application.port.in.SendMoneyUseCase;
|
|||||||
import io.reflectoring.cashpal.application.port.in.SendMoneyUseCase.SendMoneyCommand;
|
import io.reflectoring.cashpal.application.port.in.SendMoneyUseCase.SendMoneyCommand;
|
||||||
import io.reflectoring.cashpal.domain.Account.AccountId;
|
import io.reflectoring.cashpal.domain.Account.AccountId;
|
||||||
import io.reflectoring.cashpal.domain.Money;
|
import io.reflectoring.cashpal.domain.Money;
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@@ -18,24 +15,18 @@ public class SendMoneyController {
|
|||||||
|
|
||||||
private final SendMoneyUseCase sendMoneyUseCase;
|
private final SendMoneyUseCase sendMoneyUseCase;
|
||||||
|
|
||||||
@PostMapping(path = "/sendMoney")
|
@PostMapping(path = "/accounts/sendMoney/{sourceAccountId}/{targetAccountId}/{amount}")
|
||||||
void sendMoney(@RequestBody SendMoneyForm form) {
|
void sendMoney(
|
||||||
|
@PathVariable("sourceAccountId") Long sourceAccountId,
|
||||||
|
@PathVariable("targetAccountId") Long targetAccountId,
|
||||||
|
@PathVariable("amount") Long amount) {
|
||||||
|
|
||||||
SendMoneyCommand command = new SendMoneyCommand(
|
SendMoneyCommand command = new SendMoneyCommand(
|
||||||
new AccountId(form.sourceAccountId),
|
new AccountId(sourceAccountId),
|
||||||
new AccountId(form.targetAccountId),
|
new AccountId(targetAccountId),
|
||||||
Money.of(form.amount));
|
Money.of(amount));
|
||||||
|
|
||||||
sendMoneyUseCase.sendMoney(command);
|
sendMoneyUseCase.sendMoney(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
public static class SendMoneyForm {
|
|
||||||
private Long sourceAccountId;
|
|
||||||
private Long targetAccountId;
|
|
||||||
private Long amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package io.reflectoring.cashpal.adapter.web;
|
package io.reflectoring.cashpal.adapter.web;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import io.reflectoring.cashpal.adapter.web.SendMoneyController.SendMoneyForm;
|
|
||||||
import io.reflectoring.cashpal.application.port.in.SendMoneyUseCase;
|
import io.reflectoring.cashpal.application.port.in.SendMoneyUseCase;
|
||||||
import io.reflectoring.cashpal.application.port.in.SendMoneyUseCase.SendMoneyCommand;
|
import io.reflectoring.cashpal.application.port.in.SendMoneyUseCase.SendMoneyCommand;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@@ -22,20 +20,11 @@ class SendMoneyControllerTest {
|
|||||||
@MockBean
|
@MockBean
|
||||||
private SendMoneyUseCase sendMoneyUseCase;
|
private SendMoneyUseCase sendMoneyUseCase;
|
||||||
|
|
||||||
private ObjectMapper jsonMapper = new ObjectMapper();
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSendMoney() throws Exception {
|
void testSendMoney() throws Exception {
|
||||||
|
|
||||||
SendMoneyForm form = new SendMoneyForm(
|
mockMvc.perform(post("/accounts/sendMoney/{sourceAccountId}/{targetAccountId}/{amount}", 41L, 42L, 500)
|
||||||
41L,
|
.header("Content-Type", "application/json"))
|
||||||
42L,
|
|
||||||
500L
|
|
||||||
);
|
|
||||||
|
|
||||||
mockMvc.perform(post("/sendMoney")
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.content(jsonMapper.writeValueAsString(form)))
|
|
||||||
.andExpect(status().isOk());
|
.andExpect(status().isOk());
|
||||||
|
|
||||||
then(sendMoneyUseCase).should().sendMoney(any(SendMoneyCommand.class));
|
then(sendMoneyUseCase).should().sendMoney(any(SendMoneyCommand.class));
|
||||||
|
|||||||
@@ -17,15 +17,18 @@ public interface SendMoneyUseCase {
|
|||||||
class SendMoneyCommand extends SelfValidating<SendMoneyCommand> {
|
class SendMoneyCommand extends SelfValidating<SendMoneyCommand> {
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private Account.AccountId sourceAccountId;
|
private final Account.AccountId sourceAccountId;
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private Account.AccountId targetAccountId;
|
private final Account.AccountId targetAccountId;
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private Money money;
|
private final Money money;
|
||||||
|
|
||||||
public SendMoneyCommand(Account.AccountId sourceAccountId, Account.AccountId targetAccountId, Money money) {
|
public SendMoneyCommand(
|
||||||
|
Account.AccountId sourceAccountId,
|
||||||
|
Account.AccountId targetAccountId,
|
||||||
|
Money money) {
|
||||||
this.sourceAccountId = sourceAccountId;
|
this.sourceAccountId = sourceAccountId;
|
||||||
this.targetAccountId = targetAccountId;
|
this.targetAccountId = targetAccountId;
|
||||||
this.money = money;
|
this.money = money;
|
||||||
|
|||||||
@@ -53,7 +53,9 @@ public class Account {
|
|||||||
* Calculates the total balance of the account by adding the activity values to the baseline balance.
|
* Calculates the total balance of the account by adding the activity values to the baseline balance.
|
||||||
*/
|
*/
|
||||||
public Money calculateBalance() {
|
public Money calculateBalance() {
|
||||||
return Money.add(this.activityWindow.calculateBalance(this.id), this.baselineBalance);
|
return Money.add(
|
||||||
|
this.baselineBalance,
|
||||||
|
this.activityWindow.calculateBalance(this.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,22 +65,39 @@ public class Account {
|
|||||||
*/
|
*/
|
||||||
public boolean withdraw(Money money, AccountId targetAccountId) {
|
public boolean withdraw(Money money, AccountId targetAccountId) {
|
||||||
|
|
||||||
if(Money.add(this.calculateBalance(), money.negate()).isNegative()){
|
if (!mayWithdraw(money)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Activity withdrawal = new Activity(null, this.id, this.id, targetAccountId, LocalDateTime.now(), money);
|
Activity withdrawal = new Activity(
|
||||||
|
this.id,
|
||||||
|
this.id,
|
||||||
|
targetAccountId,
|
||||||
|
LocalDateTime.now(),
|
||||||
|
money);
|
||||||
this.activityWindow.addActivity(withdrawal);
|
this.activityWindow.addActivity(withdrawal);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean mayWithdraw(Money money) {
|
||||||
|
return Money.add(
|
||||||
|
this.calculateBalance(),
|
||||||
|
money.negate())
|
||||||
|
.isPositiveOrZero();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to deposit a certain amount of money to this account.
|
* Tries to deposit a certain amount of money to this account.
|
||||||
* If sucessful, creates a new activity with a positive value.
|
* If sucessful, creates a new activity with a positive value.
|
||||||
* @return true if the deposit was successful, false if not.
|
* @return true if the deposit was successful, false if not.
|
||||||
*/
|
*/
|
||||||
public boolean deposit(Money money, AccountId sourceAccountId) {
|
public boolean deposit(Money money, AccountId sourceAccountId) {
|
||||||
Activity deposit = new Activity(null, this.id, sourceAccountId, this.id, LocalDateTime.now(), money);
|
Activity deposit = new Activity(
|
||||||
|
this.id,
|
||||||
|
sourceAccountId,
|
||||||
|
this.id,
|
||||||
|
LocalDateTime.now(),
|
||||||
|
money);
|
||||||
this.activityWindow.addActivity(deposit);
|
this.activityWindow.addActivity(deposit);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,18 @@ import java.time.LocalDateTime;
|
|||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A money transfer activity between {@link Account}s.
|
* A money transfer activity between {@link Account}s.
|
||||||
*/
|
*/
|
||||||
@Value
|
@Value
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class Activity {
|
public class Activity {
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
private final ActivityId id;
|
private ActivityId id;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The account that owns this activity.
|
* The account that owns this activity.
|
||||||
@@ -50,6 +52,20 @@ public class Activity {
|
|||||||
@NonNull
|
@NonNull
|
||||||
private final Money money;
|
private final Money money;
|
||||||
|
|
||||||
|
public Activity(
|
||||||
|
@NonNull Account.AccountId ownerAccountId,
|
||||||
|
@NonNull Account.AccountId sourceAccountId,
|
||||||
|
@NonNull Account.AccountId targetAccountId,
|
||||||
|
@NonNull LocalDateTime timestamp,
|
||||||
|
@NonNull Money money) {
|
||||||
|
this.id = null;
|
||||||
|
this.ownerAccountId = ownerAccountId;
|
||||||
|
this.sourceAccountId = sourceAccountId;
|
||||||
|
this.targetAccountId = targetAccountId;
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
this.money = money;
|
||||||
|
}
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
public static class ActivityId {
|
public static class ActivityId {
|
||||||
private final Long value;
|
private final Long value;
|
||||||
|
|||||||
@@ -37,6 +37,14 @@ public class Money {
|
|||||||
return new Money(a.amount.add(b.amount));
|
return new Money(a.amount.add(b.amount));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Money minus(Money money){
|
||||||
|
return new Money(this.amount.subtract(money.amount));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Money plus(Money money){
|
||||||
|
return new Money(this.amount.add(money.amount));
|
||||||
|
}
|
||||||
|
|
||||||
public static Money subtract(Money a, Money b) {
|
public static Money subtract(Money a, Money b) {
|
||||||
return new Money(a.amount.subtract(b.amount));
|
return new Money(a.amount.subtract(b.amount));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,11 +60,8 @@ class SendMoneyServiceTest {
|
|||||||
@Test
|
@Test
|
||||||
void transactionSucceeds() {
|
void transactionSucceeds() {
|
||||||
|
|
||||||
AccountId sourceAccountId = new AccountId(41L);
|
Account sourceAccount = givenSourceAccount();
|
||||||
Account sourceAccount = givenAnAccountWithId(sourceAccountId);
|
Account targetAccount = givenTargetAccount();
|
||||||
|
|
||||||
AccountId targetAccountId = new AccountId(42L);
|
|
||||||
Account targetAccount = givenAnAccountWithId(targetAccountId);
|
|
||||||
|
|
||||||
givenWithdrawalWillSucceed(sourceAccount);
|
givenWithdrawalWillSucceed(sourceAccount);
|
||||||
givenDepositWillSucceed(targetAccount);
|
givenDepositWillSucceed(targetAccount);
|
||||||
@@ -72,14 +69,17 @@ class SendMoneyServiceTest {
|
|||||||
Money money = Money.of(500L);
|
Money money = Money.of(500L);
|
||||||
|
|
||||||
SendMoneyCommand command = new SendMoneyCommand(
|
SendMoneyCommand command = new SendMoneyCommand(
|
||||||
sourceAccountId,
|
sourceAccount.getId(),
|
||||||
targetAccountId,
|
targetAccount.getId(),
|
||||||
money);
|
money);
|
||||||
|
|
||||||
boolean success = sendMoneyService.sendMoney(command);
|
boolean success = sendMoneyService.sendMoney(command);
|
||||||
|
|
||||||
assertThat(success).isTrue();
|
assertThat(success).isTrue();
|
||||||
|
|
||||||
|
AccountId sourceAccountId = sourceAccount.getId();
|
||||||
|
AccountId targetAccountId = targetAccount.getId();
|
||||||
|
|
||||||
then(accountLock).should().lockAccount(eq(sourceAccountId));
|
then(accountLock).should().lockAccount(eq(sourceAccountId));
|
||||||
then(sourceAccount).should().withdraw(eq(money), eq(targetAccountId));
|
then(sourceAccount).should().withdraw(eq(money), eq(targetAccountId));
|
||||||
then(accountLock).should().releaseAccount(eq(sourceAccountId));
|
then(accountLock).should().releaseAccount(eq(sourceAccountId));
|
||||||
@@ -121,6 +121,14 @@ class SendMoneyServiceTest {
|
|||||||
.willReturn(true);
|
.willReturn(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Account givenTargetAccount(){
|
||||||
|
return givenAnAccountWithId(new AccountId(42L));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Account givenSourceAccount(){
|
||||||
|
return givenAnAccountWithId(new AccountId(41L));
|
||||||
|
}
|
||||||
|
|
||||||
private Account givenAnAccountWithId(AccountId id) {
|
private Account givenAnAccountWithId(AccountId id) {
|
||||||
Account account = Mockito.mock(Account.class);
|
Account account = Mockito.mock(Account.class);
|
||||||
given(account.getId())
|
given(account.getId())
|
||||||
|
|||||||
Reference in New Issue
Block a user