diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5eac309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ \ No newline at end of file diff --git a/pom.xml b/pom.xml index 2ca15ec..4235c1f 100644 --- a/pom.xml +++ b/pom.xml @@ -7,6 +7,44 @@ ch.engenius accounts 1.0-SNAPSHOT + + + org.projectlombok + lombok + 1.18.22 + provided + + + + org.springframework + spring-context + 5.3.16 + + + junit + junit + 4.10 + test + + + org.mockito + mockito-junit-jupiter + 3.6.0 + test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + \ No newline at end of file diff --git a/src/main/java/ch/engenius/bank/Account.java b/src/main/java/ch/engenius/bank/Account.java deleted file mode 100644 index b9979cb..0000000 --- a/src/main/java/ch/engenius/bank/Account.java +++ /dev/null @@ -1,31 +0,0 @@ -package ch.engenius.bank; - -import java.math.BigDecimal; - -public class Account { - private double money; - - public void withdraw(double amount) { - if ((money - amount) < 0) { - throw new IllegalStateException("not enough credits on account"); - } - setMoney(money - amount); - - } - - public void deposit(double amount) { - setMoney(money + amount); - } - - public double getMoney() { - return money; - } - - public void setMoney(double money) { - this.money = money; - } - - public BigDecimal getMoneyAsBigDecimal() { - return BigDecimal.valueOf(money); - } -} diff --git a/src/main/java/ch/engenius/bank/Application.java b/src/main/java/ch/engenius/bank/Application.java new file mode 100644 index 0000000..38da260 --- /dev/null +++ b/src/main/java/ch/engenius/bank/Application.java @@ -0,0 +1,12 @@ +package ch.engenius.bank; + +import ch.engenius.bank.exception.AccountNotFoundException; +import ch.engenius.bank.runner.BankRunner; + +public class Application { + + public static void main(String[] args) throws AccountNotFoundException, InterruptedException { + BankRunner runner = new BankRunner(); + runner.run(); + } +} diff --git a/src/main/java/ch/engenius/bank/Bank.java b/src/main/java/ch/engenius/bank/Bank.java deleted file mode 100644 index 571ebc7..0000000 --- a/src/main/java/ch/engenius/bank/Bank.java +++ /dev/null @@ -1,18 +0,0 @@ -package ch.engenius.bank; - -import java.util.HashMap; - -public class Bank { - private HashMap accounts = new HashMap<>(); - - public Account registerAccount(int accountNumber, int amount) { - Account account = new Account(); - account.setMoney(amount); - accounts.put(accountNumber, account); - return account; - } - - public Account getAccount( int number) { - return accounts.get(number); - } -} diff --git a/src/main/java/ch/engenius/bank/BankRunner.java b/src/main/java/ch/engenius/bank/BankRunner.java deleted file mode 100644 index 10b30fd..0000000 --- a/src/main/java/ch/engenius/bank/BankRunner.java +++ /dev/null @@ -1,71 +0,0 @@ -package ch.engenius.bank; - -import java.math.BigDecimal; -import java.util.Random; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import java.util.stream.IntStream; - -public class BankRunner { - - private static final ExecutorService executor = Executors.newFixedThreadPool(8); - - private final Random random = new Random(43); - private final Bank bank = new Bank(); - - - public static void main(String[] args) { - BankRunner runner = new BankRunner(); - int accounts = 100; - int defaultDeposit = 1000; - int iterations = 10000; - runner.registerAccounts(accounts, defaultDeposit); - runner.sanityCheck(accounts, accounts*defaultDeposit); - runner.runBank(iterations, accounts); - runner.sanityCheck(accounts, accounts*defaultDeposit); - - } - - private void runBank(int iterations, int maxAccount) { - for (int i =0; i< iterations; i++ ) { - executor.submit( ()-> runRandomOperation(maxAccount)); - } - try { - executor.shutdown(); - executor.awaitTermination(100,TimeUnit.SECONDS); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - - private void runRandomOperation(int maxAccount) { - double transfer = random.nextDouble()*100.0; - int accountInNumber = random.nextInt(maxAccount); - int accountOutNumber = random.nextInt(maxAccount); - Account accIn =bank.getAccount(accountInNumber); - Account accOut =bank.getAccount(accountOutNumber); - accIn.deposit(transfer); - accOut.withdraw(transfer); - } - - private void registerAccounts(int number, int defaultMoney) { - for ( int i = 0; i < number; i++) { - bank.registerAccount(i, defaultMoney); - } - } - - private void sanityCheck( int accountMaxNumber, int totalExpectedMoney) { - BigDecimal sum = IntStream.range(0, accountMaxNumber) - .mapToObj( bank::getAccount) - .map ( Account::getMoneyAsBigDecimal) - .reduce( BigDecimal.ZERO, BigDecimal::add); - - if ( sum.intValue() != totalExpectedMoney) { - throw new IllegalStateException("we got "+ sum + " != " + totalExpectedMoney +" (expected)"); - } - System.out.println("sanity check OK"); - } - - -} diff --git a/src/main/java/ch/engenius/bank/context/DataContext.java b/src/main/java/ch/engenius/bank/context/DataContext.java new file mode 100644 index 0000000..b498e40 --- /dev/null +++ b/src/main/java/ch/engenius/bank/context/DataContext.java @@ -0,0 +1,31 @@ +package ch.engenius.bank.context; + +import ch.engenius.bank.exception.AccountNotFoundException; +import ch.engenius.bank.model.Account; +import ch.engenius.bank.model.Bank; +import lombok.Getter; + +import java.util.HashMap; + +@Getter +public class DataContext { + private final Bank bank; + + + public DataContext() { + bank = new Bank(); + } + + public Account getAccount(int number) throws AccountNotFoundException { + return bank.getAccount(number); + } + + public HashMap getAllAccounts() { + return bank.getAllAccounts(); + } + + public Account saveAccount(int number, Account account) { + bank.getAccounts().put(number, account); + return account; + } +} diff --git a/src/main/java/ch/engenius/bank/exception/AccountNotFoundException.java b/src/main/java/ch/engenius/bank/exception/AccountNotFoundException.java new file mode 100644 index 0000000..0137fa3 --- /dev/null +++ b/src/main/java/ch/engenius/bank/exception/AccountNotFoundException.java @@ -0,0 +1,7 @@ +package ch.engenius.bank.exception; + +public class AccountNotFoundException extends RuntimeException { + public AccountNotFoundException(int accountNumber) { + super(String.format("Account with id %d cannot be found!", accountNumber)); + } +} diff --git a/src/main/java/ch/engenius/bank/model/Account.java b/src/main/java/ch/engenius/bank/model/Account.java new file mode 100644 index 0000000..fc66294 --- /dev/null +++ b/src/main/java/ch/engenius/bank/model/Account.java @@ -0,0 +1,14 @@ +package ch.engenius.bank.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +import java.math.BigDecimal; + +@AllArgsConstructor +@Getter +@Setter +public class Account { + private BigDecimal money; +} diff --git a/src/main/java/ch/engenius/bank/model/Bank.java b/src/main/java/ch/engenius/bank/model/Bank.java new file mode 100644 index 0000000..6211e15 --- /dev/null +++ b/src/main/java/ch/engenius/bank/model/Bank.java @@ -0,0 +1,26 @@ +package ch.engenius.bank.model; + +import ch.engenius.bank.exception.AccountNotFoundException; +import lombok.Getter; + +import java.util.HashMap; + +@Getter +public class Bank { + private HashMap accounts; + + public Bank() { + accounts = new HashMap<>(); + } + + public Account getAccount( int number) throws AccountNotFoundException { + Account account = accounts.get(number); + if(account == null) + throw new AccountNotFoundException(number); + return account; + } + + public HashMap getAllAccounts() { + return accounts; + } +} diff --git a/src/main/java/ch/engenius/bank/repository/account/AccountRepository.java b/src/main/java/ch/engenius/bank/repository/account/AccountRepository.java new file mode 100644 index 0000000..7f04da2 --- /dev/null +++ b/src/main/java/ch/engenius/bank/repository/account/AccountRepository.java @@ -0,0 +1,11 @@ +package ch.engenius.bank.repository.account; + +import ch.engenius.bank.exception.AccountNotFoundException; +import ch.engenius.bank.model.Account; + +import java.util.HashMap; + +public interface AccountRepository { + Account getAccount(int accountNumber) throws AccountNotFoundException; + HashMap getAllAccounts(); +} diff --git a/src/main/java/ch/engenius/bank/repository/account/AccountRepositoryImpl.java b/src/main/java/ch/engenius/bank/repository/account/AccountRepositoryImpl.java new file mode 100644 index 0000000..815454f --- /dev/null +++ b/src/main/java/ch/engenius/bank/repository/account/AccountRepositoryImpl.java @@ -0,0 +1,23 @@ +package ch.engenius.bank.repository.account; + +import ch.engenius.bank.context.DataContext; +import ch.engenius.bank.exception.AccountNotFoundException; +import ch.engenius.bank.model.Account; + +import java.util.HashMap; + +public class AccountRepositoryImpl implements AccountRepository { + private DataContext dataContext; + + public AccountRepositoryImpl(DataContext dataContext) { + this.dataContext = dataContext; + } + + public Account getAccount(int accountNumber) throws AccountNotFoundException { + return dataContext.getAccount(accountNumber); + } + + public HashMap getAllAccounts() { + return dataContext.getAllAccounts(); + } +} diff --git a/src/main/java/ch/engenius/bank/repository/bank/BankRepository.java b/src/main/java/ch/engenius/bank/repository/bank/BankRepository.java new file mode 100644 index 0000000..64882f0 --- /dev/null +++ b/src/main/java/ch/engenius/bank/repository/bank/BankRepository.java @@ -0,0 +1,7 @@ +package ch.engenius.bank.repository.bank; + +import ch.engenius.bank.model.Account; + +public interface BankRepository { + Account saveAccount(int accountNumber, Account account); +} diff --git a/src/main/java/ch/engenius/bank/repository/bank/BankRepositoryImpl.java b/src/main/java/ch/engenius/bank/repository/bank/BankRepositoryImpl.java new file mode 100644 index 0000000..aa5b581 --- /dev/null +++ b/src/main/java/ch/engenius/bank/repository/bank/BankRepositoryImpl.java @@ -0,0 +1,18 @@ +package ch.engenius.bank.repository.bank; + +import ch.engenius.bank.context.DataContext; +import ch.engenius.bank.model.Account; + +public class BankRepositoryImpl implements BankRepository { + private DataContext dataContext; + + public BankRepositoryImpl(DataContext dataContext) { + this.dataContext = dataContext; + } + + public Account saveAccount(int accountNumber, Account account) { + Account savedAccount = dataContext.saveAccount(accountNumber, account); + return savedAccount; + } + +} diff --git a/src/main/java/ch/engenius/bank/runner/BankRunner.java b/src/main/java/ch/engenius/bank/runner/BankRunner.java new file mode 100644 index 0000000..7ab9e66 --- /dev/null +++ b/src/main/java/ch/engenius/bank/runner/BankRunner.java @@ -0,0 +1,48 @@ +package ch.engenius.bank.runner; + +import ch.engenius.bank.context.DataContext; +import ch.engenius.bank.exception.AccountNotFoundException; +import ch.engenius.bank.runner.runner_helper.BankRunnerAccountHelper; +import ch.engenius.bank.runner.runner_helper.BankRunnerSanityChecker; + +import java.math.BigDecimal; + +public class BankRunner { + private final BankRunnerAccountHelper bankRunnerAccountHelper; + private final BankRunnerSanityChecker sanityChecker; + + private final DataContext dataContext; + + private final int ACCOUNTS_NUM = 100; + private final int ITERATIONS = 10000; + private final int DEFAULT_DEPOSIT = 1000; + + public BankRunner() { + dataContext = new DataContext(); + bankRunnerAccountHelper = new BankRunnerAccountHelper(dataContext); + sanityChecker = new BankRunnerSanityChecker(dataContext); + } + + public void run() throws AccountNotFoundException, InterruptedException { + BigDecimal defaultDeposit = BigDecimal.valueOf(DEFAULT_DEPOSIT); + BigDecimal totalDeposit = defaultDeposit.multiply(BigDecimal.valueOf(ACCOUNTS_NUM)); + + registerAccounts(defaultDeposit); + sanityCheck(totalDeposit); + runBank(); + sanityCheck(totalDeposit); + } + + private void runBank() throws InterruptedException { + bankRunnerAccountHelper.runBank(ITERATIONS, ACCOUNTS_NUM); + } + + private void registerAccounts(BigDecimal defaultAccountMoney) { + bankRunnerAccountHelper.registerAccounts(ACCOUNTS_NUM, defaultAccountMoney); + } + + private void sanityCheck(BigDecimal totalExpectedSum) throws AccountNotFoundException { + sanityChecker.sanityCheck(ACCOUNTS_NUM, totalExpectedSum); + } + +} diff --git a/src/main/java/ch/engenius/bank/runner/runner_helper/BankRunnerAccountHelper.java b/src/main/java/ch/engenius/bank/runner/runner_helper/BankRunnerAccountHelper.java new file mode 100644 index 0000000..1df2126 --- /dev/null +++ b/src/main/java/ch/engenius/bank/runner/runner_helper/BankRunnerAccountHelper.java @@ -0,0 +1,39 @@ +package ch.engenius.bank.runner.runner_helper; + +import ch.engenius.bank.context.DataContext; +import ch.engenius.bank.service.account.AccountService; +import ch.engenius.bank.service.account.AccountServiceImpl; +import ch.engenius.bank.service.bank.BankService; +import ch.engenius.bank.service.bank.BankServiceImpl; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.*; + +public class BankRunnerAccountHelper { + private final BankService bankService; + private final AccountService accountService; + + private static final ExecutorService executor = Executors.newFixedThreadPool(8); + + public BankRunnerAccountHelper(DataContext dataContext) { + this.bankService = new BankServiceImpl(dataContext); + this.accountService = new AccountServiceImpl(dataContext); + } + + public void runBank(int iterations, int maxAccount) throws InterruptedException { + List> tasks = new ArrayList<>(); + for (int i = 0; i < iterations; i++) { + tasks.add(new BankRunnerTask(accountService, maxAccount)); + } + executor.invokeAll(tasks); + executor.shutdown(); + } + + + public void registerAccounts(int number, BigDecimal defaultMoney) { + bankService.registerAccounts(number, defaultMoney); + } + +} diff --git a/src/main/java/ch/engenius/bank/runner/runner_helper/BankRunnerSanityChecker.java b/src/main/java/ch/engenius/bank/runner/runner_helper/BankRunnerSanityChecker.java new file mode 100644 index 0000000..93e4575 --- /dev/null +++ b/src/main/java/ch/engenius/bank/runner/runner_helper/BankRunnerSanityChecker.java @@ -0,0 +1,47 @@ +package ch.engenius.bank.runner.runner_helper; + +import ch.engenius.bank.context.DataContext; +import ch.engenius.bank.exception.AccountNotFoundException; +import ch.engenius.bank.model.Account; +import ch.engenius.bank.service.account.AccountService; +import ch.engenius.bank.service.account.AccountServiceImpl; + +import java.math.BigDecimal; +import java.util.stream.IntStream; + +public class BankRunnerSanityChecker { + private final AccountService accountService; + + public BankRunnerSanityChecker( + DataContext dataContext) { + this.accountService = new AccountServiceImpl(dataContext); + } + + public void sanityCheck(int accountMaxNumber, BigDecimal totalExpectedSum) throws AccountNotFoundException { + BigDecimal totalSum = calculateAccountsMoneySum(accountMaxNumber); + checkIfSumAsExpected(totalSum, totalExpectedSum); + System.out.println("sanity check OK"); + } + + private void checkIfSumAsExpected(BigDecimal sum, BigDecimal totalExpectedMoney) { + if (sum.compareTo(totalExpectedMoney) != 0) { + throw new IllegalStateException("we got "+ sum + " != " + totalExpectedMoney +" (expected)"); + } + } + + private BigDecimal calculateAccountsMoneySum(int accountMaxNumber) throws AccountNotFoundException { + return IntStream.range(0, accountMaxNumber) + .mapToObj(this::getAccount) + .map ( Account::getMoney) + .reduce( BigDecimal.ZERO, BigDecimal::add); + } + + private Account getAccount(int accountNumber) { + try { + return accountService.getAccount(accountNumber); + } catch (AccountNotFoundException e) { + e.printStackTrace(); + } + return new Account(null); + } +} diff --git a/src/main/java/ch/engenius/bank/runner/runner_helper/BankRunnerTask.java b/src/main/java/ch/engenius/bank/runner/runner_helper/BankRunnerTask.java new file mode 100644 index 0000000..6cbd0bf --- /dev/null +++ b/src/main/java/ch/engenius/bank/runner/runner_helper/BankRunnerTask.java @@ -0,0 +1,42 @@ +package ch.engenius.bank.runner.runner_helper; + +import ch.engenius.bank.exception.AccountNotFoundException; +import ch.engenius.bank.service.account.AccountService; + +import java.math.BigDecimal; +import java.util.Random; +import java.util.concurrent.Callable; + + +public class BankRunnerTask implements Callable { + private final AccountService accountService; + + private final Random random; + private int maxAccount; + + + public BankRunnerTask( + AccountService accountService, + int maxAccount) { + this.random = new Random(43); + this.accountService = accountService; + this.maxAccount = maxAccount; + } + + @Override + public Void call() throws Exception { + runRandomOperation(this.maxAccount); + return null; + } + + private void runRandomOperation(int maxAccount) throws InterruptedException, AccountNotFoundException { + BigDecimal transferAmount = BigDecimal.valueOf(random.nextDouble()*100.0); + int accountInNumber = random.nextInt(maxAccount); + int accountOutNumber = random.nextInt(maxAccount); + makeTransaction(accountInNumber, accountOutNumber, transferAmount); + } + + private void makeTransaction(int accountInNumber, int accountOutNumber, BigDecimal transferAmount) throws InterruptedException, AccountNotFoundException { + accountService.makeTransaction(accountInNumber, accountOutNumber, transferAmount); + } +} diff --git a/src/main/java/ch/engenius/bank/service/account/AccountService.java b/src/main/java/ch/engenius/bank/service/account/AccountService.java new file mode 100644 index 0000000..593a5ff --- /dev/null +++ b/src/main/java/ch/engenius/bank/service/account/AccountService.java @@ -0,0 +1,13 @@ +package ch.engenius.bank.service.account; + +import ch.engenius.bank.exception.AccountNotFoundException; +import ch.engenius.bank.model.Account; + +import java.math.BigDecimal; +import java.util.HashMap; + +public interface AccountService { + Account getAccount(int accountNumber) throws AccountNotFoundException; + void makeTransaction(int accountIn, int accountOut, BigDecimal amount) throws AccountNotFoundException, InterruptedException; + HashMap getAllAccounts(); +} diff --git a/src/main/java/ch/engenius/bank/service/account/AccountServiceImpl.java b/src/main/java/ch/engenius/bank/service/account/AccountServiceImpl.java new file mode 100644 index 0000000..3d563f0 --- /dev/null +++ b/src/main/java/ch/engenius/bank/service/account/AccountServiceImpl.java @@ -0,0 +1,70 @@ +package ch.engenius.bank.service.account; + +import ch.engenius.bank.context.DataContext; +import ch.engenius.bank.exception.AccountNotFoundException; +import ch.engenius.bank.model.Account; +import ch.engenius.bank.repository.account.AccountRepository; +import ch.engenius.bank.repository.account.AccountRepositoryImpl; + +import java.math.BigDecimal; +import java.util.HashMap; +import java.util.concurrent.locks.ReentrantLock; + +public class AccountServiceImpl implements AccountService { + private final AccountRepository accountRepository; + + private final ReentrantLock lock; + + public AccountServiceImpl(DataContext dataContext) { + accountRepository = new AccountRepositoryImpl(dataContext); + lock = new ReentrantLock(); + } + + public void makeTransaction(int accountIn, int accountOut, BigDecimal amount) throws AccountNotFoundException { + lock.lock(); + try { + withdraw(accountOut, amount); + deposit(accountIn, amount); + } finally { + lock.unlock(); + } + } + + private void withdraw(int accountNumber, BigDecimal withdrawAmount) throws AccountNotFoundException { + checkIfHasEnoughMoney(accountNumber, withdrawAmount); + Account account = getAccount(accountNumber); + BigDecimal newAmount = account.getMoney().subtract(withdrawAmount); + setMoney(accountNumber, newAmount); + } + + private void deposit(int accountNumber, BigDecimal depositAmount) throws AccountNotFoundException { + Account account = getAccount(accountNumber); + BigDecimal newAmount = account.getMoney().add(depositAmount); + setMoney(accountNumber, newAmount); + } + + public Account getAccount(int number) throws AccountNotFoundException { + return accountRepository.getAccount(number); + } + + public HashMap getAllAccounts() { + return accountRepository.getAllAccounts(); + } + + public void setMoney(int accountNumber, BigDecimal amount) throws AccountNotFoundException { + Account account = getAccount(accountNumber); + account.setMoney(amount); + } + + private boolean hasEnoughMoney(int accountNumber, BigDecimal withdrawAmount) throws AccountNotFoundException { + Account account = getAccount(accountNumber); + BigDecimal difference = account.getMoney().subtract(withdrawAmount); + return difference.compareTo(BigDecimal.ZERO) > 0; + } + + private void checkIfHasEnoughMoney(int accountNumber, BigDecimal withdrawAmount) throws AccountNotFoundException { + if (!hasEnoughMoney(accountNumber, withdrawAmount)) { + throw new IllegalStateException("not enough credits on account"); + } + } +} diff --git a/src/main/java/ch/engenius/bank/service/bank/BankService.java b/src/main/java/ch/engenius/bank/service/bank/BankService.java new file mode 100644 index 0000000..a5a070e --- /dev/null +++ b/src/main/java/ch/engenius/bank/service/bank/BankService.java @@ -0,0 +1,9 @@ +package ch.engenius.bank.service.bank; + +import java.math.BigDecimal; + + +public interface BankService { + void registerAccounts(int number, BigDecimal defaultMoney); + +} diff --git a/src/main/java/ch/engenius/bank/service/bank/BankServiceImpl.java b/src/main/java/ch/engenius/bank/service/bank/BankServiceImpl.java new file mode 100644 index 0000000..3025214 --- /dev/null +++ b/src/main/java/ch/engenius/bank/service/bank/BankServiceImpl.java @@ -0,0 +1,34 @@ +package ch.engenius.bank.service.bank; + +import ch.engenius.bank.context.DataContext; +import ch.engenius.bank.model.Account; +import ch.engenius.bank.repository.bank.BankRepository; +import ch.engenius.bank.repository.bank.BankRepositoryImpl; + +import java.math.BigDecimal; + +public class BankServiceImpl implements BankService { + private final BankRepository bankRepository; + + + public BankServiceImpl(DataContext dataContext) { + bankRepository = new BankRepositoryImpl(dataContext); + } + + public void registerAccounts(int number, BigDecimal defaultMoney) { + for ( int i = 0; i < number; i++) { + registerAccount(i, defaultMoney); + } + } + + private Account registerAccount(int accountNumber, BigDecimal amount) { + Account account = new Account(amount); + saveAccount(accountNumber, account); + return account; + } + + private void saveAccount(int accountNumber, Account account) { + bankRepository.saveAccount(accountNumber, account); + } + +} diff --git a/src/test/java/ch/engenius/bank/TestData.java b/src/test/java/ch/engenius/bank/TestData.java new file mode 100644 index 0000000..0cc0cdd --- /dev/null +++ b/src/test/java/ch/engenius/bank/TestData.java @@ -0,0 +1,11 @@ +package ch.engenius.bank; + +public class TestData { + public static final int ACCOUNTS_NUM = 100; + public static int DEFAULT_DEPOSIT = 1000; + + public static final int EXISTING_ACCOUNT_NUMBER_1 = 1; + public static final int EXISTING_ACCOUNT_NUMBER_2 = 2; + public static final int NON_EXISTING_ACCOUNT_NUMBER = 200; + public static final int VALID_ACCOUNT_NUMBER = 10; +} diff --git a/src/test/java/ch/engenius/bank/bank/BankUnitTest.java b/src/test/java/ch/engenius/bank/bank/BankUnitTest.java new file mode 100644 index 0000000..a13037c --- /dev/null +++ b/src/test/java/ch/engenius/bank/bank/BankUnitTest.java @@ -0,0 +1,75 @@ +package ch.engenius.bank.bank; + +import ch.engenius.bank.TestData; +import ch.engenius.bank.exception.AccountNotFoundException; +import ch.engenius.bank.model.Account; +import ch.engenius.bank.model.Bank; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.math.BigDecimal; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.BDDMockito.given; + +public class BankUnitTest { + private final Bank bank = + Mockito.mock(Bank.class); + + + private HashMap getBankAccounts() { + HashMap accounts = new HashMap<>(); + for (int i = 0; i < TestData.ACCOUNTS_NUM; i++) { + accounts.put(i, new Account(BigDecimal.valueOf(TestData.DEFAULT_DEPOSIT))); + } + return accounts; + } + + @Test + public void givenEmptyBankAccountsThenGetAccountsWillReturnEmptyMap() { + given(bank.getAccounts()).willReturn(new HashMap<>()); + + HashMap accounts = bank.getAccounts(); + + assertEquals(0, accounts.size()); + } + + @Test + public void givenBankAccountsThenGetAccountsWillReturnMap() { + given(bank.getAccounts()).willReturn(getBankAccounts()); + + HashMap accounts = bank.getAccounts(); + + assertEquals(TestData.ACCOUNTS_NUM, accounts.size()); + } + + @Test + public void givenInvalidAccountNumberThenGetAccountWillThrowException() throws AccountNotFoundException { + given(bank.getAccount(TestData.NON_EXISTING_ACCOUNT_NUMBER)).willThrow(AccountNotFoundException.class); + + assertThrows(AccountNotFoundException.class, () -> bank.getAccount(TestData.NON_EXISTING_ACCOUNT_NUMBER)); + } + + @Test + public void givenValidAccountNumberThenGetAccountWillReturnAccount() throws AccountNotFoundException { + Account account = new Account(BigDecimal.valueOf(100)); + given(bank.getAccount(TestData.EXISTING_ACCOUNT_NUMBER_1)).willReturn(account); + + Account result = bank.getAccount(TestData.EXISTING_ACCOUNT_NUMBER_1); + + assertNotNull(account); + assertEquals(result.getMoney(), account.getMoney()); + } + + @Test + public void givenGetAllAccountsThenWillReturnAllAccounts() { + HashMap accounts = getBankAccounts(); + given(bank.getAllAccounts()).willReturn(accounts); + + HashMap result = bank.getAllAccounts(); + + assertEquals(accounts.size(), result.size()); + } +} diff --git a/src/test/java/ch/engenius/bank/context/DataContextUnitTest.java b/src/test/java/ch/engenius/bank/context/DataContextUnitTest.java new file mode 100644 index 0000000..44c774a --- /dev/null +++ b/src/test/java/ch/engenius/bank/context/DataContextUnitTest.java @@ -0,0 +1,89 @@ +package ch.engenius.bank.context; + +import ch.engenius.bank.TestData; +import ch.engenius.bank.exception.AccountNotFoundException; +import ch.engenius.bank.model.Account; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.math.BigDecimal; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; + +public class DataContextUnitTest { + private final DataContext dataContext = + Mockito.mock(DataContext.class); + + + private HashMap getBankAccounts() { + HashMap accounts = new HashMap<>(); + for (int i = 0; i < TestData.ACCOUNTS_NUM; i++) { + accounts.put(i, new Account(BigDecimal.valueOf(TestData.DEFAULT_DEPOSIT))); + } + return accounts; + } + + @Test + public void givenEmptyBankAccountsThenGetAccountsWillReturnEmptyMap() { + given(dataContext.getAllAccounts()).willReturn(new HashMap<>()); + + HashMap accounts = dataContext.getAllAccounts(); + + assertEquals(0, accounts.size()); + } + + @Test + public void givenBankAccountsThenGetAccountsWillReturnMap() { + given(dataContext.getAllAccounts()).willReturn(getBankAccounts()); + + HashMap accounts = dataContext.getAllAccounts(); + + assertEquals(TestData.ACCOUNTS_NUM, accounts.size()); + } + + @Test + public void givenInvalidAccountNumberThenGetAccountWillThrowException() throws AccountNotFoundException { + given(dataContext.getAccount(TestData.NON_EXISTING_ACCOUNT_NUMBER)).willThrow(AccountNotFoundException.class); + + assertThrows(AccountNotFoundException.class, () -> dataContext.getAccount(TestData.NON_EXISTING_ACCOUNT_NUMBER)); + } + + @Test + public void givenValidAccountNumberThenGetAccountWillReturnAccount() throws AccountNotFoundException { + Account account = new Account(BigDecimal.valueOf(100)); + given(dataContext.getAccount(TestData.EXISTING_ACCOUNT_NUMBER_1)).willReturn(account); + + Account result = dataContext.getAccount(TestData.EXISTING_ACCOUNT_NUMBER_1); + + assertNotNull(account); + assertEquals(result.getMoney(), account.getMoney()); + } + + @Test + public void givenGetAllAccountsThenWillReturnAllAccounts() { + HashMap accounts = getBankAccounts(); + given(dataContext.getAllAccounts()).willReturn(accounts); + + HashMap result = dataContext.getAllAccounts(); + + assertEquals(accounts.size(), result.size()); + } + + @Test + public void givenSaveAccountThenWillReturnSavedAccount() { + Account account = new Account(BigDecimal.valueOf(TestData.DEFAULT_DEPOSIT)); + given(dataContext.saveAccount(TestData.VALID_ACCOUNT_NUMBER, account)).willReturn(account); + + Account result = dataContext.saveAccount(TestData.VALID_ACCOUNT_NUMBER, account); + + then(dataContext) + .should() + .saveAccount(TestData.VALID_ACCOUNT_NUMBER, account); + assertNotNull(result); + } + +} diff --git a/src/test/java/ch/engenius/bank/repository/AccountRepositoryUnitTest.java b/src/test/java/ch/engenius/bank/repository/AccountRepositoryUnitTest.java new file mode 100644 index 0000000..55bfb7f --- /dev/null +++ b/src/test/java/ch/engenius/bank/repository/AccountRepositoryUnitTest.java @@ -0,0 +1,55 @@ +package ch.engenius.bank.repository; + +import ch.engenius.bank.TestData; +import ch.engenius.bank.exception.AccountNotFoundException; +import ch.engenius.bank.model.Account; +import ch.engenius.bank.repository.account.AccountRepositoryImpl; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.math.BigDecimal; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.given; + +public class AccountRepositoryUnitTest { + private AccountRepositoryImpl accountRepository = + Mockito.mock(AccountRepositoryImpl.class); + + private HashMap getBankAccounts() { + HashMap accounts = new HashMap<>(); + for (int i = 0; i < TestData.ACCOUNTS_NUM; i++) { + accounts.put(i, new Account(BigDecimal.valueOf(TestData.DEFAULT_DEPOSIT))); + } + return accounts; + } + + @Test + public void givenInvalidAccountNumberThenGetAccountWillThrowException() throws AccountNotFoundException { + given(accountRepository.getAccount(TestData.NON_EXISTING_ACCOUNT_NUMBER)).willThrow(AccountNotFoundException.class); + + assertThrows(AccountNotFoundException.class, () -> accountRepository.getAccount(TestData.NON_EXISTING_ACCOUNT_NUMBER)); + } + + @Test + public void givenValidAccountNumberThenGetAccountWillReturnAccount() throws AccountNotFoundException { + Account account = new Account(BigDecimal.valueOf(100)); + given(accountRepository.getAccount(TestData.EXISTING_ACCOUNT_NUMBER_1)).willReturn(account); + + Account result = accountRepository.getAccount(TestData.EXISTING_ACCOUNT_NUMBER_1); + + assertNotNull(account); + assertEquals(result.getMoney(), account.getMoney()); + } + + @Test + public void givenGetAllAccountsThenWillReturnAllAccounts() { + HashMap accounts = getBankAccounts(); + given(accountRepository.getAllAccounts()).willReturn(accounts); + + HashMap result = accountRepository.getAllAccounts(); + + assertEquals(accounts.size(), result.size()); + } +} diff --git a/src/test/java/ch/engenius/bank/repository/BankRepositoryUnitTest.java b/src/test/java/ch/engenius/bank/repository/BankRepositoryUnitTest.java new file mode 100644 index 0000000..a82a700 --- /dev/null +++ b/src/test/java/ch/engenius/bank/repository/BankRepositoryUnitTest.java @@ -0,0 +1,33 @@ +package ch.engenius.bank.repository; + +import ch.engenius.bank.TestData; +import ch.engenius.bank.model.Account; +import ch.engenius.bank.repository.bank.BankRepositoryImpl; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + + +import java.math.BigDecimal; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; + +public class BankRepositoryUnitTest { + private BankRepositoryImpl bankRepository = + Mockito.mock(BankRepositoryImpl.class); + + + @Test + public void givenSaveAccountThenWillReturnSavedAccount() { + Account account = new Account(BigDecimal.valueOf(TestData.DEFAULT_DEPOSIT)); + given(bankRepository.saveAccount(TestData.VALID_ACCOUNT_NUMBER, account)).willReturn(account); + + Account result = bankRepository.saveAccount(TestData.VALID_ACCOUNT_NUMBER, account); + + then(bankRepository) + .should() + .saveAccount(TestData.VALID_ACCOUNT_NUMBER, account); + assertNotNull(result); + } +} diff --git a/src/test/java/ch/engenius/bank/runner/runner_helper/BankRunnerAccountHelperUnitTest.java b/src/test/java/ch/engenius/bank/runner/runner_helper/BankRunnerAccountHelperUnitTest.java new file mode 100644 index 0000000..47decf4 --- /dev/null +++ b/src/test/java/ch/engenius/bank/runner/runner_helper/BankRunnerAccountHelperUnitTest.java @@ -0,0 +1,43 @@ +package ch.engenius.bank.runner.runner_helper; + +import ch.engenius.bank.TestData; +import ch.engenius.bank.model.Account; +import ch.engenius.bank.service.account.AccountService; +import ch.engenius.bank.service.account.AccountServiceImpl; +import ch.engenius.bank.service.bank.BankService; +import ch.engenius.bank.service.bank.BankServiceImpl; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.math.BigDecimal; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.BDDMockito.given; + +public class BankRunnerAccountHelperUnitTest { + private final AccountService accountService = + Mockito.mock(AccountServiceImpl.class); + + private final BankService bankService = + Mockito.mock(BankServiceImpl.class); + + private HashMap getBankAccounts() { + HashMap accounts = new HashMap<>(); + for (int i = 0; i < TestData.ACCOUNTS_NUM; i++) { + accounts.put(i, new Account(BigDecimal.valueOf(TestData.DEFAULT_DEPOSIT))); + } + return accounts; + } + + @Test + public void givenRegisterAccountsThenAccountsWillBeFull() { + BigDecimal defaultDeposit = BigDecimal.valueOf(TestData.DEFAULT_DEPOSIT); + given(accountService.getAllAccounts()).willReturn(getBankAccounts()); + + bankService.registerAccounts(TestData.ACCOUNTS_NUM, defaultDeposit); + HashMap accounts = accountService.getAllAccounts(); + + assertEquals(accounts.size(), TestData.ACCOUNTS_NUM); + } +} diff --git a/src/test/java/ch/engenius/bank/service/AccountServiceUnitTest.java b/src/test/java/ch/engenius/bank/service/AccountServiceUnitTest.java new file mode 100644 index 0000000..0621e2b --- /dev/null +++ b/src/test/java/ch/engenius/bank/service/AccountServiceUnitTest.java @@ -0,0 +1,90 @@ +package ch.engenius.bank.service; + +import ch.engenius.bank.TestData; +import ch.engenius.bank.exception.AccountNotFoundException; +import ch.engenius.bank.model.Account; +import ch.engenius.bank.repository.account.AccountRepository; +import ch.engenius.bank.repository.account.AccountRepositoryImpl; +import ch.engenius.bank.service.account.AccountService; +import ch.engenius.bank.service.account.AccountServiceImpl; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.math.BigDecimal; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class AccountServiceUnitTest { + private AccountService accountService = + Mockito.mock(AccountServiceImpl.class); + + private AccountRepository accountRepository = + Mockito.mock(AccountRepositoryImpl.class); + + private HashMap getBankAccounts() { + HashMap accounts = new HashMap<>(); + for (int i = 0; i < TestData.ACCOUNTS_NUM; i++) { + accounts.put(i, new Account(BigDecimal.valueOf(TestData.DEFAULT_DEPOSIT))); + } + return accounts; + } + + @Test + public void givenInvalidAccountNumberThenGetAccountWillThrowException() throws AccountNotFoundException { + given(accountService.getAccount(TestData.NON_EXISTING_ACCOUNT_NUMBER)).willThrow(AccountNotFoundException.class); + + assertThrows(AccountNotFoundException.class, () -> accountService.getAccount(TestData.NON_EXISTING_ACCOUNT_NUMBER)); + } + + @Test + public void givenValidAccountNumberThenGetAccountWillReturnAccount() throws AccountNotFoundException { + Account account = new Account(BigDecimal.valueOf(100)); + given(accountService.getAccount(TestData.EXISTING_ACCOUNT_NUMBER_1)).willReturn(account); + + Account result = accountService.getAccount(TestData.EXISTING_ACCOUNT_NUMBER_1); + + assertNotNull(account); + assertEquals(result.getMoney(), account.getMoney()); + } + + @Test + public void givenMakeTransactionWithInvalidAccountNumberWillThrowException() throws AccountNotFoundException, InterruptedException { + BigDecimal amount = BigDecimal.valueOf(100); + given(accountService.getAccount(TestData.NON_EXISTING_ACCOUNT_NUMBER)).willThrow(AccountNotFoundException.class); + + accountService.makeTransaction(TestData.NON_EXISTING_ACCOUNT_NUMBER, TestData.EXISTING_ACCOUNT_NUMBER_1, amount); + + assertThrows(AccountNotFoundException.class, () -> accountService.getAccount(TestData.NON_EXISTING_ACCOUNT_NUMBER)); + } + + @Test + public void givenMakeTransactionWithValidAccountNumberWillSucceed() throws AccountNotFoundException, InterruptedException { + BigDecimal amount = BigDecimal.valueOf(TestData.DEFAULT_DEPOSIT); + BigDecimal transactionAmount = BigDecimal.valueOf(100); + Account account1 = new Account(amount); + Account account2 = new Account(amount); + given(accountService.getAccount(TestData.EXISTING_ACCOUNT_NUMBER_1)).willReturn(account1); + given(accountService.getAccount(TestData.EXISTING_ACCOUNT_NUMBER_2)).willReturn(account2); + + accountService.makeTransaction(TestData.EXISTING_ACCOUNT_NUMBER_1, TestData.EXISTING_ACCOUNT_NUMBER_2, transactionAmount); + + verify(accountService, times(1)) + .makeTransaction(TestData.EXISTING_ACCOUNT_NUMBER_1, TestData.EXISTING_ACCOUNT_NUMBER_2, transactionAmount); + } + + @Test + public void givenGetAllAccountsThenWillReturnAllAccounts() { + HashMap accounts = getBankAccounts(); + given(accountService.getAllAccounts()).willReturn(accounts); + + HashMap result = accountService.getAllAccounts(); + + assertEquals(accounts.size(), result.size()); + } + +} diff --git a/src/test/java/ch/engenius/bank/service/BankServiceUnitTest.java b/src/test/java/ch/engenius/bank/service/BankServiceUnitTest.java new file mode 100644 index 0000000..17fbe7c --- /dev/null +++ b/src/test/java/ch/engenius/bank/service/BankServiceUnitTest.java @@ -0,0 +1,43 @@ +package ch.engenius.bank.service; + +import ch.engenius.bank.TestData; +import ch.engenius.bank.model.Account; +import ch.engenius.bank.service.account.AccountService; +import ch.engenius.bank.service.account.AccountServiceImpl; +import ch.engenius.bank.service.bank.BankService; +import ch.engenius.bank.service.bank.BankServiceImpl; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.math.BigDecimal; +import java.util.HashMap; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.BDDMockito.given; + +public class BankServiceUnitTest { + private final BankService bankService = + Mockito.mock(BankServiceImpl.class); + + private final AccountService accountService = + Mockito.mock(AccountServiceImpl.class); + + private HashMap getBankAccounts() { + HashMap accounts = new HashMap<>(); + for (int i = 0; i < TestData.ACCOUNTS_NUM; i++) { + accounts.put(i, new Account(BigDecimal.valueOf(TestData.DEFAULT_DEPOSIT))); + } + return accounts; + } + + @Test + public void givenRegisterAccountsThenAccountsWillBeFull() { + BigDecimal defaultDeposit = BigDecimal.valueOf(TestData.DEFAULT_DEPOSIT); + given(accountService.getAllAccounts()).willReturn(getBankAccounts()); + + bankService.registerAccounts(TestData.ACCOUNTS_NUM, defaultDeposit); + HashMap accounts = accountService.getAllAccounts(); + + assertEquals(accounts.size(), TestData.ACCOUNTS_NUM); + } +}