From a0e8585c811ae2f22e8057917e8ab895bfac444b Mon Sep 17 00:00:00 2001
From: Robert Goldmann <deadlocker@gmx.de>
Date: Sun, 8 Mar 2020 15:04:52 +0100
Subject: [PATCH] #455 - added tests for determination of files to delete

---
 pom.xml                                       |   8 ++
 .../database/DatabaseService.java             |  57 +++++---
 .../settings/SettingsController.java          |  10 +-
 .../unit/database/DatabaseServiceTest.java    | 133 ++++++++++++++++++
 .../unit/helpers/LoggerTestUtil.java          |  21 +++
 src/test/resources/backups/empty/.gitkeep     |   0
 ...getMasterDatabase_2020_03_07_14_10_50.json | 105 ++++++++++++++
 ...getMasterDatabase_2020_03_08_13_10_50.json | 105 ++++++++++++++
 ...getMasterDatabase_2020_03_08_14_10_50.json | 105 ++++++++++++++
 9 files changed, 526 insertions(+), 18 deletions(-)
 create mode 100644 src/test/java/de/deadlocker8/budgetmaster/unit/database/DatabaseServiceTest.java
 create mode 100644 src/test/java/de/deadlocker8/budgetmaster/unit/helpers/LoggerTestUtil.java
 create mode 100644 src/test/resources/backups/empty/.gitkeep
 create mode 100644 src/test/resources/backups/three/BudgetMasterDatabase_2020_03_07_14_10_50.json
 create mode 100644 src/test/resources/backups/three/BudgetMasterDatabase_2020_03_08_13_10_50.json
 create mode 100644 src/test/resources/backups/three/BudgetMasterDatabase_2020_03_08_14_10_50.json

diff --git a/pom.xml b/pom.xml
index d4d43580c..ee2d3f442 100644
--- a/pom.xml
+++ b/pom.xml
@@ -67,6 +67,7 @@
         <codemirror.version>5.45.0</codemirror.version>
         <webdrivermanager.version>2.2.1</webdrivermanager.version>
         <selenium.version>3.141.59</selenium.version>
+        <assertj-core.version>3.15.0</assertj-core.version>
 
         <app.versionDate>${maven.build.timestamp}</app.versionDate>
         <maven.build.timestamp.format>dd.MM.yy</maven.build.timestamp.format>
@@ -215,6 +216,13 @@
             <version>${selenium.version}</version>
             <scope>test</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.assertj</groupId>
+            <artifactId>assertj-core</artifactId>
+            <version>${assertj-core.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/src/main/java/de/deadlocker8/budgetmaster/database/DatabaseService.java b/src/main/java/de/deadlocker8/budgetmaster/database/DatabaseService.java
index a130efc5f..a77ae72bb 100644
--- a/src/main/java/de/deadlocker8/budgetmaster/database/DatabaseService.java
+++ b/src/main/java/de/deadlocker8/budgetmaster/database/DatabaseService.java
@@ -41,7 +41,7 @@ public class DatabaseService
 			.registerTypeAdapter(DateTime.class, (JsonSerializer<DateTime>) (json, typeOfSrc, context) -> new JsonPrimitive(ISODateTimeFormat.date().print(json)))
 			.create();
 
-	private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
+	private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseService.class);
 	private AccountService accountService;
 	private CategoryService categoryService;
 	private TransactionService transactionService;
@@ -99,41 +99,55 @@ public class DatabaseService
 		LOGGER.info("All tags reset.");
 	}
 
-	private void rotatingBackup(Path backupFolderPath)
+	public void rotatingBackup(Path backupFolderPath)
 	{
+		final List<Path> filesToDelete = determineFilesToDelete(backupFolderPath);
+
+		for(Path path : filesToDelete)
+		{
+			try
+			{
+				Files.deleteIfExists(path);
+			}
+			catch(IOException e)
+			{
+				e.printStackTrace();
+			}
+		}
+	}
+
+	public List<Path> determineFilesToDelete(Path backupFolderPath)
+	{
+		final ArrayList<Path> filesToDelete = new ArrayList<>();
+
 		final Integer numberOfFilesToKeep = settingsService.getSettings().getAutoBackupFilesToKeep();
 		if(numberOfFilesToKeep == 0)
 		{
 			LOGGER.debug("Skipping backup rotation since number of files to keep is set to unlimited");
-			return;
+			return filesToDelete;
 		}
 
 		final List<String> existingBackups = getExistingBackups(backupFolderPath);
 		if(existingBackups.size() < numberOfFilesToKeep)
 		{
 			LOGGER.debug("Skipping backup rotation (existing backups: " + existingBackups.size() + ", files to keep: " + numberOfFilesToKeep + ")");
-			return;
+			return filesToDelete;
 		}
 
-		LOGGER.debug("Removing old backups (existing backups: " + existingBackups.size() + ", files to keep: " + numberOfFilesToKeep + ")");
+		LOGGER.debug("Determining old backups (existing backups: " + existingBackups.size() + ", files to keep: " + numberOfFilesToKeep + ")");
 		// reserve 1 file for the backup created afterwards
 		final int allowedNumberOfFiles = existingBackups.size() - numberOfFilesToKeep + 1;
 		for(int i = 0; i < allowedNumberOfFiles; i++)
 		{
 			final Path oldBackup = Paths.get(existingBackups.get(i));
-			LOGGER.debug("Deleting old backup: " + oldBackup.toString());
-			try
-			{
-				Files.deleteIfExists(oldBackup);
-			}
-			catch(IOException e)
-			{
-				e.printStackTrace();
-			}
+			LOGGER.debug("Schedule old backup for deletion: " + oldBackup.toString());
+			filesToDelete.add(oldBackup);
 		}
+
+		return filesToDelete;
 	}
 
-	private List<String> getExistingBackups(Path backupFolderPath)
+	public List<String> getExistingBackups(Path backupFolderPath)
 	{
 		try(Stream<Path> walk = Files.walk(backupFolderPath))
 		{
@@ -159,7 +173,7 @@ public class DatabaseService
 		rotatingBackup(backupFolderPath);
 
 		final Database databaseForJsonSerialization = getDatabaseForJsonSerialization();
-		final String fileName = "BudgetMasterDatabase_" + DateTime.now().toString("yyyy_MM_dd_HH_mm_ss") + ".json";
+		final String fileName = getExportFileName(true);
 		final String backupPath = backupFolderPath.resolve(fileName).toString();
 
 		try(Writer writer = new FileWriter(backupPath))
@@ -174,6 +188,17 @@ public class DatabaseService
 		}
 	}
 
+	public static String getExportFileName(boolean includeTime)
+	{
+		String formatString = "yyyy_MM_dd";
+		if(includeTime)
+		{
+			formatString = "yyyy_MM_dd_HH_mm_ss";
+		}
+
+		return "BudgetMasterDatabase_" + DateTime.now().toString(formatString) + ".json";
+	}
+
 	public Database getDatabaseForJsonSerialization()
 	{
 		List<Category> categories = categoryService.getRepository().findAll();
diff --git a/src/main/java/de/deadlocker8/budgetmaster/settings/SettingsController.java b/src/main/java/de/deadlocker8/budgetmaster/settings/SettingsController.java
index ce9228c4c..3baf34fc4 100644
--- a/src/main/java/de/deadlocker8/budgetmaster/settings/SettingsController.java
+++ b/src/main/java/de/deadlocker8/budgetmaster/settings/SettingsController.java
@@ -19,7 +19,6 @@ import de.deadlocker8.budgetmaster.utils.Strings;
 import de.thecodelabs.utils.util.Localization;
 import de.thecodelabs.utils.util.RandomUtils;
 import de.thecodelabs.versionizer.UpdateItem;
-import org.joda.time.DateTime;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -41,6 +40,7 @@ import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
@@ -114,6 +114,12 @@ public class SettingsController extends BaseController
 			settings.setAutoBackupTime(defaultSettings.getAutoBackupTime());
 			settings.setAutoBackupFilesToKeep(defaultSettings.getAutoBackupFilesToKeep());
 		}
+		else
+		{
+			final Path applicationSupportFolder = Main.getApplicationSupportFolder();
+			final Path backupFolder = applicationSupportFolder.resolve("backups");
+			databaseService.backupDatabase(backupFolder);
+		}
 
 		if(bindingResult.hasErrors())
 		{
@@ -173,7 +179,7 @@ public class SettingsController extends BaseController
 		String data = DatabaseService.GSON.toJson(databaseForJsonSerialization);
 		byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
 
-		String fileName = "BudgetMasterDatabase_" + DateTime.now().toString("yyyy_MM_dd") + ".json";
+		String fileName = DatabaseService.getExportFileName(false);
 		response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
 
 		response.setContentType("application/json; charset=UTF-8");
diff --git a/src/test/java/de/deadlocker8/budgetmaster/unit/database/DatabaseServiceTest.java b/src/test/java/de/deadlocker8/budgetmaster/unit/database/DatabaseServiceTest.java
new file mode 100644
index 000000000..869248796
--- /dev/null
+++ b/src/test/java/de/deadlocker8/budgetmaster/unit/database/DatabaseServiceTest.java
@@ -0,0 +1,133 @@
+package de.deadlocker8.budgetmaster.unit.database;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.read.ListAppender;
+import de.deadlocker8.budgetmaster.accounts.AccountService;
+import de.deadlocker8.budgetmaster.categories.CategoryService;
+import de.deadlocker8.budgetmaster.database.DatabaseService;
+import de.deadlocker8.budgetmaster.settings.Settings;
+import de.deadlocker8.budgetmaster.settings.SettingsService;
+import de.deadlocker8.budgetmaster.tags.TagService;
+import de.deadlocker8.budgetmaster.transactions.TransactionService;
+import de.deadlocker8.budgetmaster.unit.helpers.LoggerTestUtil;
+import org.assertj.core.groups.Tuple;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
+
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.when;
+
+
+@RunWith(SpringJUnit4ClassRunner.class)
+public class DatabaseServiceTest
+{
+	@Mock
+	private AccountService accountService;
+
+	@Mock
+	private SettingsService settingsService;
+
+	@Mock
+	private CategoryService categoryService;
+
+	@Mock
+	private TransactionService transactionService;
+
+	@Mock
+	private TagService tagService;
+
+	@InjectMocks
+	private DatabaseService databaseService;
+
+	@Test
+	public void test_determineFilesToDelete_unlimited() throws URISyntaxException
+	{
+		ListAppender<ILoggingEvent> loggingAppender = LoggerTestUtil.getListAppenderForClass(DatabaseService.class);
+
+		final Settings mockedSettings = Settings.getDefault();
+		mockedSettings.setAutoBackupFilesToKeep(0);
+		when(settingsService.getSettings()).thenReturn(mockedSettings);
+
+		final Path backupFolder = Paths.get(getClass().getClassLoader().getResource("").toURI()).resolve("backups/empty");
+		final List<Path> filesToDelete = databaseService.determineFilesToDelete(backupFolder);
+
+		assertThat(filesToDelete).isEmpty();
+
+		assertThat(loggingAppender.list)
+				.extracting(ILoggingEvent::getMessage, ILoggingEvent::getLevel)
+				.contains(Tuple.tuple("Skipping backup rotation since number of files to keep is set to unlimited", Level.DEBUG));
+	}
+
+	@Test
+	public void test_determineFilesToDelete_limitNotReached() throws URISyntaxException
+	{
+		ListAppender<ILoggingEvent> loggingAppender = LoggerTestUtil.getListAppenderForClass(DatabaseService.class);
+
+		final Settings mockedSettings = Settings.getDefault();
+		mockedSettings.setAutoBackupFilesToKeep(5);
+		when(settingsService.getSettings()).thenReturn(mockedSettings);
+
+		final Path backupFolder = Paths.get(getClass().getClassLoader().getResource("").toURI()).resolve("backups/three");
+		final List<Path> filesToDelete = databaseService.determineFilesToDelete(backupFolder);
+
+		assertThat(filesToDelete).isEmpty();
+
+		assertThat(loggingAppender.list)
+				.extracting(ILoggingEvent::getMessage, ILoggingEvent::getLevel)
+				.contains(Tuple.tuple("Skipping backup rotation (existing backups: 3, files to keep: 5)", Level.DEBUG));
+	}
+
+	@Test
+	public void test_determineFilesToDelete_limitReachedExactly() throws URISyntaxException
+	{
+		ListAppender<ILoggingEvent> loggingAppender = LoggerTestUtil.getListAppenderForClass(DatabaseService.class);
+
+		final Settings mockedSettings = Settings.getDefault();
+		mockedSettings.setAutoBackupFilesToKeep(3);
+		when(settingsService.getSettings()).thenReturn(mockedSettings);
+
+		final Path testResources = Paths.get(getClass().getClassLoader().getResource("").toURI());
+		final Path backupFolder = testResources.resolve("backups/three");
+		final List<Path> filesToDelete = databaseService.determineFilesToDelete(backupFolder);
+
+		assertThat(filesToDelete)
+				.hasSize(1)
+				.contains(testResources.resolve("backups/three/BudgetMasterDatabase_2020_03_07_14_10_50.json"));
+
+		assertThat(loggingAppender.list)
+				.extracting(ILoggingEvent::getMessage, ILoggingEvent::getLevel)
+				.contains(Tuple.tuple("Determining old backups (existing backups: 3, files to keep: 3)", Level.DEBUG));
+	}
+
+	@Test
+	public void test_determineFilesToDelete_limitReached() throws URISyntaxException
+	{
+		ListAppender<ILoggingEvent> loggingAppender = LoggerTestUtil.getListAppenderForClass(DatabaseService.class);
+
+		final Settings mockedSettings = Settings.getDefault();
+		mockedSettings.setAutoBackupFilesToKeep(2);
+		when(settingsService.getSettings()).thenReturn(mockedSettings);
+
+		final Path testResources = Paths.get(getClass().getClassLoader().getResource("").toURI());
+		final Path backupFolder = testResources.resolve("backups/three");
+		final List<Path> filesToDelete = databaseService.determineFilesToDelete(backupFolder);
+
+		assertThat(filesToDelete)
+				.hasSize(2)
+				.contains(testResources.resolve("backups/three/BudgetMasterDatabase_2020_03_07_14_10_50.json"))
+				.contains(testResources.resolve("backups/three/BudgetMasterDatabase_2020_03_08_13_10_50.json"));
+
+		assertThat(loggingAppender.list)
+				.extracting(ILoggingEvent::getMessage, ILoggingEvent::getLevel)
+				.contains(Tuple.tuple("Determining old backups (existing backups: 3, files to keep: 2)", Level.DEBUG));
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/de/deadlocker8/budgetmaster/unit/helpers/LoggerTestUtil.java b/src/test/java/de/deadlocker8/budgetmaster/unit/helpers/LoggerTestUtil.java
new file mode 100644
index 000000000..f74212583
--- /dev/null
+++ b/src/test/java/de/deadlocker8/budgetmaster/unit/helpers/LoggerTestUtil.java
@@ -0,0 +1,21 @@
+package de.deadlocker8.budgetmaster.unit.helpers;
+
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.read.ListAppender;
+import org.slf4j.LoggerFactory;
+
+public class LoggerTestUtil
+{
+	public static ListAppender<ILoggingEvent> getListAppenderForClass(Class clazz)
+	{
+		Logger logger = (Logger) LoggerFactory.getLogger(clazz);
+
+		ListAppender<ILoggingEvent> loggingEventListAppender = new ListAppender<>();
+		loggingEventListAppender.start();
+
+		logger.addAppender(loggingEventListAppender);
+
+		return loggingEventListAppender;
+	}
+}
diff --git a/src/test/resources/backups/empty/.gitkeep b/src/test/resources/backups/empty/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/test/resources/backups/three/BudgetMasterDatabase_2020_03_07_14_10_50.json b/src/test/resources/backups/three/BudgetMasterDatabase_2020_03_07_14_10_50.json
new file mode 100644
index 000000000..f976e90dd
--- /dev/null
+++ b/src/test/resources/backups/three/BudgetMasterDatabase_2020_03_07_14_10_50.json
@@ -0,0 +1,105 @@
+{
+  "TYPE": "BUDGETMASTER_DATABASE",
+  "VERSION": 3,
+  "categories": [
+    {
+      "ID": 1,
+      "name": "No Category",
+      "color": "#eeeeee",
+      "type": "NONE"
+    },
+    {
+      "ID": 2,
+      "name": "Rest",
+      "color": "#FFFF00",
+      "type": "REST"
+    },
+    {
+      "ID": 39,
+      "name": "dfgdg",
+      "color": "#ff9500",
+      "type": "CUSTOM"
+    }
+  ],
+  "accounts": [
+    {
+      "ID": 1,
+      "name": "Placeholder",
+      "type": "ALL"
+    },
+    {
+      "ID": 2,
+      "name": "fdsdasa",
+      "type": "CUSTOM"
+    },
+    {
+      "ID": 37,
+      "name": "d2",
+      "type": "CUSTOM"
+    }
+  ],
+  "transactions": [
+    {
+      "ID": 37,
+      "amount": -100,
+      "date": "2020-02-05",
+      "account": {
+        "ID": 2,
+        "name": "fdsdasa",
+        "type": "CUSTOM"
+      },
+      "category": {
+        "ID": 39,
+        "name": "dfgdg",
+        "color": "#ff9500",
+        "type": "CUSTOM"
+      },
+      "name": "Apfel",
+      "description": "",
+      "tags": []
+    },
+    {
+      "ID": 38,
+      "amount": -200,
+      "date": "2020-02-05",
+      "account": {
+        "ID": 2,
+        "name": "fdsdasa",
+        "type": "CUSTOM"
+      },
+      "category": {
+        "ID": 1,
+        "name": "No Category",
+        "color": "#eeeeee",
+        "type": "NONE"
+      },
+      "name": "Birne",
+      "description": "",
+      "tags": []
+    },
+    {
+      "ID": 39,
+      "amount": -100,
+      "date": "2020-02-26",
+      "account": {
+        "ID": 2,
+        "name": "fdsdasa",
+        "type": "CUSTOM"
+      },
+      "category": {
+        "ID": 1,
+        "name": "No Category",
+        "color": "#eeeeee",
+        "type": "NONE"
+      },
+      "name": "Transfer Me",
+      "description": "",
+      "tags": [],
+      "transferAccount": {
+        "ID": 37,
+        "name": "d2",
+        "type": "CUSTOM"
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/test/resources/backups/three/BudgetMasterDatabase_2020_03_08_13_10_50.json b/src/test/resources/backups/three/BudgetMasterDatabase_2020_03_08_13_10_50.json
new file mode 100644
index 000000000..f976e90dd
--- /dev/null
+++ b/src/test/resources/backups/three/BudgetMasterDatabase_2020_03_08_13_10_50.json
@@ -0,0 +1,105 @@
+{
+  "TYPE": "BUDGETMASTER_DATABASE",
+  "VERSION": 3,
+  "categories": [
+    {
+      "ID": 1,
+      "name": "No Category",
+      "color": "#eeeeee",
+      "type": "NONE"
+    },
+    {
+      "ID": 2,
+      "name": "Rest",
+      "color": "#FFFF00",
+      "type": "REST"
+    },
+    {
+      "ID": 39,
+      "name": "dfgdg",
+      "color": "#ff9500",
+      "type": "CUSTOM"
+    }
+  ],
+  "accounts": [
+    {
+      "ID": 1,
+      "name": "Placeholder",
+      "type": "ALL"
+    },
+    {
+      "ID": 2,
+      "name": "fdsdasa",
+      "type": "CUSTOM"
+    },
+    {
+      "ID": 37,
+      "name": "d2",
+      "type": "CUSTOM"
+    }
+  ],
+  "transactions": [
+    {
+      "ID": 37,
+      "amount": -100,
+      "date": "2020-02-05",
+      "account": {
+        "ID": 2,
+        "name": "fdsdasa",
+        "type": "CUSTOM"
+      },
+      "category": {
+        "ID": 39,
+        "name": "dfgdg",
+        "color": "#ff9500",
+        "type": "CUSTOM"
+      },
+      "name": "Apfel",
+      "description": "",
+      "tags": []
+    },
+    {
+      "ID": 38,
+      "amount": -200,
+      "date": "2020-02-05",
+      "account": {
+        "ID": 2,
+        "name": "fdsdasa",
+        "type": "CUSTOM"
+      },
+      "category": {
+        "ID": 1,
+        "name": "No Category",
+        "color": "#eeeeee",
+        "type": "NONE"
+      },
+      "name": "Birne",
+      "description": "",
+      "tags": []
+    },
+    {
+      "ID": 39,
+      "amount": -100,
+      "date": "2020-02-26",
+      "account": {
+        "ID": 2,
+        "name": "fdsdasa",
+        "type": "CUSTOM"
+      },
+      "category": {
+        "ID": 1,
+        "name": "No Category",
+        "color": "#eeeeee",
+        "type": "NONE"
+      },
+      "name": "Transfer Me",
+      "description": "",
+      "tags": [],
+      "transferAccount": {
+        "ID": 37,
+        "name": "d2",
+        "type": "CUSTOM"
+      }
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/test/resources/backups/three/BudgetMasterDatabase_2020_03_08_14_10_50.json b/src/test/resources/backups/three/BudgetMasterDatabase_2020_03_08_14_10_50.json
new file mode 100644
index 000000000..f976e90dd
--- /dev/null
+++ b/src/test/resources/backups/three/BudgetMasterDatabase_2020_03_08_14_10_50.json
@@ -0,0 +1,105 @@
+{
+  "TYPE": "BUDGETMASTER_DATABASE",
+  "VERSION": 3,
+  "categories": [
+    {
+      "ID": 1,
+      "name": "No Category",
+      "color": "#eeeeee",
+      "type": "NONE"
+    },
+    {
+      "ID": 2,
+      "name": "Rest",
+      "color": "#FFFF00",
+      "type": "REST"
+    },
+    {
+      "ID": 39,
+      "name": "dfgdg",
+      "color": "#ff9500",
+      "type": "CUSTOM"
+    }
+  ],
+  "accounts": [
+    {
+      "ID": 1,
+      "name": "Placeholder",
+      "type": "ALL"
+    },
+    {
+      "ID": 2,
+      "name": "fdsdasa",
+      "type": "CUSTOM"
+    },
+    {
+      "ID": 37,
+      "name": "d2",
+      "type": "CUSTOM"
+    }
+  ],
+  "transactions": [
+    {
+      "ID": 37,
+      "amount": -100,
+      "date": "2020-02-05",
+      "account": {
+        "ID": 2,
+        "name": "fdsdasa",
+        "type": "CUSTOM"
+      },
+      "category": {
+        "ID": 39,
+        "name": "dfgdg",
+        "color": "#ff9500",
+        "type": "CUSTOM"
+      },
+      "name": "Apfel",
+      "description": "",
+      "tags": []
+    },
+    {
+      "ID": 38,
+      "amount": -200,
+      "date": "2020-02-05",
+      "account": {
+        "ID": 2,
+        "name": "fdsdasa",
+        "type": "CUSTOM"
+      },
+      "category": {
+        "ID": 1,
+        "name": "No Category",
+        "color": "#eeeeee",
+        "type": "NONE"
+      },
+      "name": "Birne",
+      "description": "",
+      "tags": []
+    },
+    {
+      "ID": 39,
+      "amount": -100,
+      "date": "2020-02-26",
+      "account": {
+        "ID": 2,
+        "name": "fdsdasa",
+        "type": "CUSTOM"
+      },
+      "category": {
+        "ID": 1,
+        "name": "No Category",
+        "color": "#eeeeee",
+        "type": "NONE"
+      },
+      "name": "Transfer Me",
+      "description": "",
+      "tags": [],
+      "transferAccount": {
+        "ID": 37,
+        "name": "d2",
+        "type": "CUSTOM"
+      }
+    }
+  ]
+}
\ No newline at end of file
-- 
GitLab