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