From dfe1e08dc7570de1d8a6d3b9168a0ea73748d978 Mon Sep 17 00:00:00 2001 From: MK13 Date: Sun, 17 Feb 2019 23:42:24 +0100 Subject: [PATCH] - Add reference from Transaction to the RecurringTransaction that created the transaction - Add BANK as valid booking target of a BANK account - Remove EXPENSE as valid booking target of a LIABILITY account - Fix a bug in the getMultiplierFromAccount and getMultiplierToAccount methods that caused the result to always be the default case - Adjust init SQL script to reflect the changes done - Add a sample integration test - Add unit tests for the RuleService - Configure surefire plugin - Add a profile for integration test running --- pom.xml | 40 ++- .../java/de/financer/model/Transaction.java | 10 + .../java/de/financer/service/RuleService.java | 26 +- .../database/hsqldb/V1_0_0__init.sql | 36 +-- .../AccountControllerIntegrationTest.java | 47 ++++ ...eService_getMultiplierFromAccountTest.java | 71 ++++++ ...uleService_getMultiplierToAccountTest.java | 71 ++++++ .../RuleService_isValidBookingTest.java | 233 ++++++++++++++++++ .../application-integrationtest.properties | 17 ++ 9 files changed, 522 insertions(+), 29 deletions(-) create mode 100644 src/test/java/de/financer/controller/integration/AccountControllerIntegrationTest.java create mode 100644 src/test/java/de/financer/service/RuleService_getMultiplierFromAccountTest.java create mode 100644 src/test/java/de/financer/service/RuleService_getMultiplierToAccountTest.java create mode 100644 src/test/java/de/financer/service/RuleService_isValidBookingTest.java create mode 100644 src/test/resources/application-integrationtest.properties diff --git a/pom.xml b/pom.xml index 5ff5359..af9934f 100644 --- a/pom.xml +++ b/pom.xml @@ -75,7 +75,7 @@ junit junit - 4.11 + 4.12 test @@ -87,6 +87,44 @@ org.springframework.boot spring-boot-maven-plugin + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*IntegrationTest + + + + + + + integration-tests + + + + maven-surefire-plugin + + + integration-test + + test + + + + none + + + **/*IntegrationTest + + + + + + + + + diff --git a/src/main/java/de/financer/model/Transaction.java b/src/main/java/de/financer/model/Transaction.java index 06eff5d..e4297f8 100644 --- a/src/main/java/de/financer/model/Transaction.java +++ b/src/main/java/de/financer/model/Transaction.java @@ -18,6 +18,8 @@ public class Transaction { private Date date; private String description; private Long amount; + @OneToOne(fetch = FetchType.EAGER) + private RecurringTransaction recurringTransaction; public Long getId() { return id; @@ -62,4 +64,12 @@ public class Transaction { public void setAmount(Long amount) { this.amount = amount; } + + public RecurringTransaction getRecurringTransaction() { + return recurringTransaction; + } + + public void setRecurringTransaction(RecurringTransaction recurringTransaction) { + this.recurringTransaction = recurringTransaction; + } } diff --git a/src/main/java/de/financer/service/RuleService.java b/src/main/java/de/financer/service/RuleService.java index adb0705..5819b49 100644 --- a/src/main/java/de/financer/service/RuleService.java +++ b/src/main/java/de/financer/service/RuleService.java @@ -31,10 +31,10 @@ public class RuleService implements InitializingBean { // The key is the from account and the value is a list of valid // to accounts for this from account this.bookingRules.put(INCOME, Arrays.asList(BANK, CASH)); - this.bookingRules.put(BANK, Arrays.asList(CASH, EXPENSE, LIABILITY)); + this.bookingRules.put(BANK, Arrays.asList(BANK, CASH, EXPENSE, LIABILITY)); this.bookingRules.put(CASH, Arrays.asList(BANK, EXPENSE, LIABILITY)); this.bookingRules.put(EXPENSE, Collections.emptyList()); - this.bookingRules.put(LIABILITY, Arrays.asList(BANK, CASH, EXPENSE)); + this.bookingRules.put(LIABILITY, Arrays.asList(BANK, CASH)); this.bookingRules.put(START, Arrays.asList(BANK, CASH, LIABILITY)); } @@ -51,19 +51,21 @@ public class RuleService implements InitializingBean { // There is no multiplier if the from account is an EXPENSE account because // it's not a valid from account type - if (INCOME.equals(fromAccount)) { + final AccountType accountType = fromAccount.getType(); + + if (INCOME.equals(accountType)) { return 1L; } - else if (BANK.equals(fromAccount)) { + else if (BANK.equals(accountType)) { return -1L; } - else if (CASH.equals(fromAccount)) { + else if (CASH.equals(accountType)) { return -1L; } - else if (LIABILITY.equals(fromAccount)) { + else if (LIABILITY.equals(accountType)) { return 1L; } - else if (START.equals(fromAccount)) { + else if (START.equals(accountType)) { return 1L; } @@ -83,16 +85,18 @@ public class RuleService implements InitializingBean { // There are no multipliers for INCOME and START accounts // because they are not valid to account types - if (BANK.equals(toAccount)) { + final AccountType accountType = toAccount.getType(); + + if (BANK.equals(accountType)) { return 1L; } - else if (CASH.equals(toAccount)) { + else if (CASH.equals(accountType)) { return 1L; } - else if (LIABILITY.equals(toAccount)) { + else if (LIABILITY.equals(accountType)) { return -1L; } - else if (EXPENSE.equals(toAccount)) { + else if (EXPENSE.equals(accountType)) { return 1L; } diff --git a/src/main/resources/database/hsqldb/V1_0_0__init.sql b/src/main/resources/database/hsqldb/V1_0_0__init.sql index 7f16c26..16070f2 100644 --- a/src/main/resources/database/hsqldb/V1_0_0__init.sql +++ b/src/main/resources/database/hsqldb/V1_0_0__init.sql @@ -10,7 +10,7 @@ CREATE TABLE account ( status VARCHAR(255) NOT NULL, current_balance BIGINT NOT NULL, - CONSTRAINT c_u_name_key UNIQUE ("key") + CONSTRAINT un_account_name_key UNIQUE ("key") ); INSERT INTO account ("key", type, status, current_balance) @@ -25,20 +25,7 @@ VALUES ('accounts.cash', 'CASH', 'OPEN', 0); INSERT INTO account ("key", type, status, current_balance) VALUES ('accounts.start', 'START', 'OPEN', 0); --- Transaction table -CREATE TABLE "transaction" ( --escape keyword "transaction" - id BIGINT NOT NULL PRIMARY KEY IDENTITY, - from_account_id BIGINT NOT NULL, - to_account_id BIGINT NOT NULL, - "date" DATE NOT NULL, --escape keyword "date" - description VARCHAR(1000), - amount BIGINT NOT NULL, - - CONSTRAINT fk_from_account FOREIGN KEY (from_account_id) REFERENCES account (id), - CONSTRAINT fk_to_account FOREIGN KEY (to_account_id) REFERENCES account (id) -); - --- Transaction table +-- Recurring transaction table CREATE TABLE recurring_transaction ( id BIGINT NOT NULL PRIMARY KEY IDENTITY, from_account_id BIGINT NOT NULL, @@ -50,6 +37,21 @@ CREATE TABLE recurring_transaction ( last_occurrence DATE, holiday_weekend_type VARCHAR(255) NOT NULL, - CONSTRAINT fk_from_account FOREIGN KEY (from_account_id) REFERENCES account (id), - CONSTRAINT fk_to_account FOREIGN KEY (to_account_id) REFERENCES account (id) + CONSTRAINT fk_recurring_transaction_from_account FOREIGN KEY (from_account_id) REFERENCES account (id), + CONSTRAINT fk_recurring_transaction_to_account FOREIGN KEY (to_account_id) REFERENCES account (id) +); + +-- Transaction table +CREATE TABLE "transaction" ( --escape keyword "transaction" + id BIGINT NOT NULL PRIMARY KEY IDENTITY, + from_account_id BIGINT NOT NULL, + to_account_id BIGINT NOT NULL, + "date" DATE NOT NULL, --escape keyword "date" + description VARCHAR(1000), + amount BIGINT NOT NULL, + recurring_transaction_id BIGINT NOT NULL, + + CONSTRAINT fk_transaction_from_account FOREIGN KEY (from_account_id) REFERENCES account (id), + CONSTRAINT fk_transaction_to_account FOREIGN KEY (to_account_id) REFERENCES account (id), + CONSTRAINT fk_transaction_recurring_transaction FOREIGN KEY (recurring_transaction_id) REFERENCES recurring_transaction (id) ); \ No newline at end of file diff --git a/src/test/java/de/financer/controller/integration/AccountControllerIntegrationTest.java b/src/test/java/de/financer/controller/integration/AccountControllerIntegrationTest.java new file mode 100644 index 0000000..6fdf02e --- /dev/null +++ b/src/test/java/de/financer/controller/integration/AccountControllerIntegrationTest.java @@ -0,0 +1,47 @@ +package de.financer.controller.integration; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import de.financer.FinancerApplication; +import de.financer.model.Account; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import java.util.List; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = FinancerApplication.class) +@AutoConfigureMockMvc +@TestPropertySource( + locations = "classpath:application-integrationtest.properties") +public class AccountControllerIntegrationTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @Test + public void test_getAll() throws Exception { + final MvcResult mvcResult = this.mockMvc.perform(get("/accounts/getAll").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andReturn(); + + final List allAccounts = this.objectMapper.readValue(mvcResult.getResponse().getContentAsByteArray(), new TypeReference>(){}); + + Assert.assertEquals(4, allAccounts.size()); + } +} diff --git a/src/test/java/de/financer/service/RuleService_getMultiplierFromAccountTest.java b/src/test/java/de/financer/service/RuleService_getMultiplierFromAccountTest.java new file mode 100644 index 0000000..d2ddc3a --- /dev/null +++ b/src/test/java/de/financer/service/RuleService_getMultiplierFromAccountTest.java @@ -0,0 +1,71 @@ +package de.financer.service; + +import de.financer.model.Account; +import de.financer.model.AccountType; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class RuleService_getMultiplierFromAccountTest { + + private RuleService classUnderTest; + + @Before + public void setUp() { + this.classUnderTest = new RuleService(); + + this.classUnderTest.afterPropertiesSet(); + } + + @Test + public void test_getMultiplierFromAccount_INCOME() { + doTest(AccountType.INCOME, 1); + } + + @Test + public void test_getMultiplierFromAccount_BANK() { + doTest(AccountType.BANK, -1); + } + + @Test + public void test_getMultiplierFromAccount_CASH() { + doTest(AccountType.CASH, -1); + } + + @Test + public void test_getMultiplierFromAccount_EXPENSE() { + doTest(AccountType.EXPENSE, 1); + } + + @Test + public void test_getMultiplierFromAccount_LIABILITY() { + doTest(AccountType.LIABILITY, 1); + } + + @Test + public void test_getMultiplierFromAccount_START() { + doTest(AccountType.START, 1); + } + + public void doTest(AccountType accountType, long expected) { + // Arrange + final Account fromAccount = createAccount(accountType); + + // Act + final long multiplier = this.classUnderTest.getMultiplierFromAccount(fromAccount); + + // Assert + Assert.assertEquals(expected, multiplier); + } + + private Account createAccount(AccountType accountType) { + final Account account = new Account(); + + account.setType(accountType); + + return account; + } +} diff --git a/src/test/java/de/financer/service/RuleService_getMultiplierToAccountTest.java b/src/test/java/de/financer/service/RuleService_getMultiplierToAccountTest.java new file mode 100644 index 0000000..3224e30 --- /dev/null +++ b/src/test/java/de/financer/service/RuleService_getMultiplierToAccountTest.java @@ -0,0 +1,71 @@ +package de.financer.service; + +import de.financer.model.Account; +import de.financer.model.AccountType; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class RuleService_getMultiplierToAccountTest { + + private RuleService classUnderTest; + + @Before + public void setUp() { + this.classUnderTest = new RuleService(); + + this.classUnderTest.afterPropertiesSet(); + } + + @Test + public void test_getMultiplierToAccount_INCOME() { + doTest(AccountType.INCOME, -1); + } + + @Test + public void test_getMultiplierToAccount_BANK() { + doTest(AccountType.BANK, 1); + } + + @Test + public void test_getMultiplierToAccount_CASH() { + doTest(AccountType.CASH, 1); + } + + @Test + public void test_getMultiplierToAccount_EXPENSE() { + doTest(AccountType.EXPENSE, 1); + } + + @Test + public void test_getMultiplierToAccount_LIABILITY() { + doTest(AccountType.LIABILITY, -1); + } + + @Test + public void test_getMultiplierToAccount_START() { + doTest(AccountType.START, -1); + } + + public void doTest(AccountType accountType, long expected) { + // Arrange + final Account fromAccount = createAccount(accountType); + + // Act + final long multiplier = this.classUnderTest.getMultiplierToAccount(fromAccount); + + // Assert + Assert.assertEquals(expected, multiplier); + } + + private Account createAccount(AccountType accountType) { + final Account account = new Account(); + + account.setType(accountType); + + return account; + } +} diff --git a/src/test/java/de/financer/service/RuleService_isValidBookingTest.java b/src/test/java/de/financer/service/RuleService_isValidBookingTest.java new file mode 100644 index 0000000..45bfb54 --- /dev/null +++ b/src/test/java/de/financer/service/RuleService_isValidBookingTest.java @@ -0,0 +1,233 @@ +package de.financer.service; + +import de.financer.model.Account; +import de.financer.model.AccountType; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class RuleService_isValidBookingTest { + private RuleService classUnderTest; + + @Before + public void setUp() { + this.classUnderTest = new RuleService(); + + this.classUnderTest.afterPropertiesSet(); + } + + // from INCOME + + @Test + public void test_isValidBooking_INCOME_INCOME() { + doTest(AccountType.INCOME, AccountType.INCOME, false); + } + + @Test + public void test_isValidBooking_INCOME_BANK() { + doTest(AccountType.INCOME, AccountType.BANK, true); + } + + @Test + public void test_isValidBooking_INCOME_CASH() { + doTest(AccountType.INCOME, AccountType.CASH, true); + } + + @Test + public void test_isValidBooking_INCOME_EXPENSE() { + doTest(AccountType.INCOME, AccountType.EXPENSE, false); + } + + @Test + public void test_isValidBooking_INCOME_LIABILITY() { + doTest(AccountType.INCOME, AccountType.LIABILITY, false); + } + + @Test + public void test_isValidBooking_INCOME_START() { + doTest(AccountType.INCOME, AccountType.START, false); + } + + // from BANK + + @Test + public void test_isValidBooking_BANK_INCOME() { + doTest(AccountType.BANK, AccountType.INCOME, false); + } + + @Test + public void test_isValidBooking_BANK_BANK() { + doTest(AccountType.BANK, AccountType.BANK, true); + } + + @Test + public void test_isValidBooking_BANK_CASH() { + doTest(AccountType.BANK, AccountType.CASH, true); + } + + @Test + public void test_isValidBooking_BANK_EXPENSE() { + doTest(AccountType.BANK, AccountType.EXPENSE, true); + } + + @Test + public void test_isValidBooking_BANK_LIABILITY() { + doTest(AccountType.BANK, AccountType.LIABILITY, true); + } + + @Test + public void test_isValidBooking_BANK_START() { + doTest(AccountType.BANK, AccountType.START, false); + } + + // from CASH + + @Test + public void test_isValidBooking_CASH_INCOME() { + doTest(AccountType.CASH, AccountType.INCOME, false); + } + + @Test + public void test_isValidBooking_CASH_BANK() { + doTest(AccountType.CASH, AccountType.BANK, true); + } + + @Test + public void test_isValidBooking_CASH_CASH() { + doTest(AccountType.CASH, AccountType.CASH, false); + } + + @Test + public void test_isValidBooking_CASH_EXPENSE() { + doTest(AccountType.CASH, AccountType.EXPENSE, true); + } + + @Test + public void test_isValidBooking_CASH_LIABILITY() { + doTest(AccountType.CASH, AccountType.LIABILITY, true); + } + + @Test + public void test_isValidBooking_CASH_START() { + doTest(AccountType.CASH, AccountType.START, false); + } + + // from EXPENSE + + @Test + public void test_isValidBooking_EXPENSE_INCOME() { + doTest(AccountType.EXPENSE, AccountType.INCOME, false); + } + + @Test + public void test_isValidBooking_EXPENSE_BANK() { + doTest(AccountType.EXPENSE, AccountType.BANK, false); + } + + @Test + public void test_isValidBooking_EXPENSE_CASH() { + doTest(AccountType.EXPENSE, AccountType.CASH, false); + } + + @Test + public void test_isValidBooking_EXPENSE_EXPENSE() { + doTest(AccountType.EXPENSE, AccountType.EXPENSE, false); + } + + @Test + public void test_isValidBooking_EXPENSE_LIABILITY() { + doTest(AccountType.EXPENSE, AccountType.LIABILITY, false); + } + + @Test + public void test_isValidBooking_EXPENSE_START() { + doTest(AccountType.EXPENSE, AccountType.START, false); + } + + // from LIABILITY + + @Test + public void test_isValidBooking_LIABILITY_INCOME() { + doTest(AccountType.LIABILITY, AccountType.INCOME, false); + } + + @Test + public void test_isValidBooking_LIABILITY_BANK() { + doTest(AccountType.LIABILITY, AccountType.BANK, true); + } + + @Test + public void test_isValidBooking_LIABILITY_CASH() { + doTest(AccountType.LIABILITY, AccountType.CASH, true); + } + + @Test + public void test_isValidBooking_LIABILITY_EXPENSE() { + doTest(AccountType.LIABILITY, AccountType.EXPENSE, false); + } + + @Test + public void test_isValidBooking_LIABILITY_LIABILITY() { + doTest(AccountType.LIABILITY, AccountType.LIABILITY, false); + } + + @Test + public void test_isValidBooking_LIABILITY_START() { + doTest(AccountType.LIABILITY, AccountType.START, false); + } + + // from START + + @Test + public void test_isValidBooking_START_INCOME() { + doTest(AccountType.START, AccountType.INCOME, false); + } + + @Test + public void test_isValidBooking_START_BANK() { + doTest(AccountType.START, AccountType.BANK, true); + } + + @Test + public void test_isValidBooking_START_CASH() { + doTest(AccountType.START, AccountType.CASH, true); + } + + @Test + public void test_isValidBooking_START_EXPENSE() { + doTest(AccountType.START, AccountType.EXPENSE, false); + } + + @Test + public void test_isValidBooking_START_LIABILITY() { + doTest(AccountType.START, AccountType.LIABILITY, true); + } + + @Test + public void test_isValidBooking_START_START() { + doTest(AccountType.START, AccountType.START, false); + } + + private void doTest(AccountType fromAccountType, AccountType toAccountType, boolean expected) { + // Arrange + final Account fromAccount = createAccount(fromAccountType); + final Account toAccount = createAccount(toAccountType); + + // Act + final boolean isValid = this.classUnderTest.isValidBooking(fromAccount, toAccount); + + // Assert + Assert.assertEquals(expected, isValid); + } + + private Account createAccount(AccountType accountType) { + final Account account = new Account(); + + account.setType(accountType); + + return account; + } +} diff --git a/src/test/resources/application-integrationtest.properties b/src/test/resources/application-integrationtest.properties new file mode 100644 index 0000000..c7c7a35 --- /dev/null +++ b/src/test/resources/application-integrationtest.properties @@ -0,0 +1,17 @@ +### +### This is the main configuration file of the application +### + +server.servlet.context-path=/financer + +spring.jpa.hibernate.ddl-auto=validate + +info.app.name=Financer +info.app.description=A simple server for personal finance administration +info.build.group=@project.groupId@ +info.build.artifact=@project.artifactId@ +info.build.version=@project.version@ + +spring.datasource.url=jdbc:hsqldb:mem:. +spring.datasource.username=sa +spring.flyway.locations=classpath:/database/hsqldb \ No newline at end of file