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);
+ }
+}