From eb97a4b581f07e01a8370b69d0f9c0685b7f05fa Mon Sep 17 00:00:00 2001
From: Robert Goldmann <deadlocker@gmx.de>
Date: Sat, 20 Jul 2024 17:08:27 +0200
Subject: [PATCH] #766 - fix csv import column validation (use number of
 columns instead of rows)

---
 .../transactions/transactionImportMacros.ftl  | 10 +++--
 .../integration/selenium/CsvImportTest.java   | 39 ++++++++++++-------
 .../resources/csv/fewer_rows_than_columns.csv |  3 ++
 3 files changed, 35 insertions(+), 17 deletions(-)
 create mode 100644 BudgetMasterServer/src/test/resources/csv/fewer_rows_than_columns.csv

diff --git a/BudgetMasterServer/src/main/resources/templates/transactions/transactionImportMacros.ftl b/BudgetMasterServer/src/main/resources/templates/transactions/transactionImportMacros.ftl
index b383e17c5..fa1b64a64 100644
--- a/BudgetMasterServer/src/main/resources/templates/transactions/transactionImportMacros.ftl
+++ b/BudgetMasterServer/src/main/resources/templates/transactions/transactionImportMacros.ftl
@@ -58,6 +58,8 @@
                 </div>
             </div>
 
+            <#assign maxColumnCount=csvRows[0].getColumns()?size/>
+
             <div class="row">
                 <div class="col s6 m4 offset-m2 l3 offset-l3">
                     <div class="transaction-import-text-with-icon">
@@ -66,7 +68,7 @@
                     </div>
                 </div>
                 <div class="input-field col s6 m6 l4 no-margin-top no-margin-bottom">
-                    <input id="columnDate" type="number" min="1" max="${csvRows?size}" name="columnDate" <@validation.validation "columnDate"/> value="<#if csvImportSettings.getColumnDate()??>${csvImportSettings.getColumnDate()}</#if>">
+                    <input id="columnDate" type="number" min="1" max="${maxColumnCount}" name="columnDate" <@validation.validation "columnDate"/> value="<#if csvImportSettings.getColumnDate()??>${csvImportSettings.getColumnDate()}</#if>">
                     <label class="input-label" for="columnDate">${locale.getString("transactions.import.column")}</label>
                 </div>
             </div>
@@ -88,7 +90,7 @@
                     </div>
                 </div>
                 <div class="input-field col s6 m6 l4 no-margin-top no-margin-bottom">
-                    <input id="columnName" type="number" min="1" max="${csvRows?size}" name="columnName" <@validation.validation "columnName"/> value="<#if csvImportSettings.getColumnName()??>${csvImportSettings.getColumnName()}</#if>">
+                    <input id="columnName" type="number" min="1" max="${maxColumnCount}" name="columnName" <@validation.validation "columnName"/> value="<#if csvImportSettings.getColumnName()??>${csvImportSettings.getColumnName()}</#if>">
                     <label class="input-label" for="columnName">${locale.getString("transactions.import.column")}</label>
                 </div>
             </div>
@@ -100,7 +102,7 @@
                     </div>
                 </div>
                 <div class="input-field col s6 m6 l4 no-margin-top no-margin-bottom">
-                    <input id="columnAmount" type="number" min="1" max="${csvRows?size}" name="columnAmount" <@validation.validation "columnAmount"/> value="<#if csvImportSettings.getColumnAmount()??>${csvImportSettings.getColumnAmount()}</#if>">
+                    <input id="columnAmount" type="number" min="1" max="${maxColumnCount}" name="columnAmount" <@validation.validation "columnAmount"/> value="<#if csvImportSettings.getColumnAmount()??>${csvImportSettings.getColumnAmount()}</#if>">
                     <label class="input-label" for="columnAmount">${locale.getString("transactions.import.column")}</label>
                 </div>
             </div>
@@ -124,7 +126,7 @@
                     </div>
                 </div>
                 <div class="input-field col s6 m6 l4 no-margin-top no-margin-bottom">
-                    <input id="columnDescription" type="number" min="1" max="${csvRows?size}" name="columnDescription" <@validation.validation "columnDescription"/> value="<#if csvImportSettings.getColumnDescription()??>${csvImportSettings.getColumnDescription()}</#if>">
+                    <input id="columnDescription" type="number" min="1" max="${maxColumnCount}" name="columnDescription" <@validation.validation "columnDescription"/> value="<#if csvImportSettings.getColumnDescription()??>${csvImportSettings.getColumnDescription()}</#if>">
                     <label class="input-label" for="columnDescription">${locale.getString("transactions.import.column")}</label>
                 </div>
             </div>
diff --git a/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/integration/selenium/CsvImportTest.java b/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/integration/selenium/CsvImportTest.java
index 28fec6532..29fce9d74 100644
--- a/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/integration/selenium/CsvImportTest.java
+++ b/BudgetMasterServer/src/test/java/de/deadlocker8/budgetmaster/integration/selenium/CsvImportTest.java
@@ -287,7 +287,7 @@ class CsvImportTest extends SeleniumTestBase
 	@Test
 	void test_upload_valid()
 	{
-		uploadAndSetColumnSettings();
+		uploadAndSetColumnSettings("three_entries.csv", 4);
 
 		assertThat(driver.findElements(By.className("transaction-import-row"))).hasSize(3);
 
@@ -328,7 +328,7 @@ class CsvImportTest extends SeleniumTestBase
 	@Test
 	void test_cancel()
 	{
-		uploadAndSetColumnSettings();
+		uploadAndSetColumnSettings("three_entries.csv", 4);
 
 		driver.findElement(By.id("button-cancel-csv-import")).click();
 		final WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
@@ -340,7 +340,7 @@ class CsvImportTest extends SeleniumTestBase
 	@Test
 	void test_skipRow()
 	{
-		uploadAndSetColumnSettings();
+		uploadAndSetColumnSettings("three_entries.csv", 4);
 		final List<WebElement> rows = driver.findElements(By.className("transaction-import-row"));
 
 		rows.get(0).findElement(By.className("button-request-transaction-import-skip")).click();
@@ -354,7 +354,7 @@ class CsvImportTest extends SeleniumTestBase
 	@Test
 	void test_undoSkipRow()
 	{
-		uploadAndSetColumnSettings();
+		uploadAndSetColumnSettings("three_entries.csv", 4);
 		List<WebElement> rows = driver.findElements(By.className("transaction-import-row"));
 
 		// skip
@@ -384,7 +384,7 @@ class CsvImportTest extends SeleniumTestBase
 	@Test
 	void test_saveInPlace()
 	{
-		uploadAndSetColumnSettings();
+		uploadAndSetColumnSettings("three_entries.csv", 4);
 
 		final List<WebElement> rows = driver.findElements(By.className("transaction-import-row"));
 		final WebElement row = rows.get(0);
@@ -416,7 +416,7 @@ class CsvImportTest extends SeleniumTestBase
 	@Test
 	void test_editNewTransaction()
 	{
-		uploadAndSetColumnSettings();
+		uploadAndSetColumnSettings("three_entries.csv", 4);
 
 		// click new transaction button
 		final List<WebElement> rows = driver.findElements(By.className("transaction-import-row"));
@@ -452,7 +452,7 @@ class CsvImportTest extends SeleniumTestBase
 	@Test
 	void test_editNewTransfer()
 	{
-		uploadAndSetColumnSettings();
+		uploadAndSetColumnSettings("three_entries.csv", 4);
 
 		// click new transaction button
 		final List<WebElement> rows = driver.findElements(By.className("transaction-import-row"));
@@ -487,7 +487,7 @@ class CsvImportTest extends SeleniumTestBase
 	@Test
 	void test_editNewFromTemplate()
 	{
-		uploadAndSetColumnSettings();
+		uploadAndSetColumnSettings("three_entries.csv", 4);
 
 		// click new transaction button
 		final List<WebElement> rows = driver.findElements(By.className("transaction-import-row"));
@@ -530,7 +530,7 @@ class CsvImportTest extends SeleniumTestBase
 	@Test
 	void test_showTransactionNameSuggestions()
 	{
-		uploadAndSetColumnSettings();
+		uploadAndSetColumnSettings("three_entries.csv", 4);
 
 		final List<WebElement> rows = driver.findElements(By.className("transaction-import-row"));
 		final WebElement row = rows.get(0);
@@ -553,22 +553,35 @@ class CsvImportTest extends SeleniumTestBase
 		assertThat(driver.findElements(By.cssSelector(".autocomplete-content li"))).hasSizeGreaterThan(0);
 	}
 
-	private void uploadAndSetColumnSettings()
+	@Test
+	void test_upload_valid_fewer_rows_than_columns()
+	{
+		uploadAndSetColumnSettings("fewer_rows_than_columns.csv", 3);
+
+		assertThat(driver.findElements(By.className("transaction-import-row"))).hasSize(2);
+
+		final WebElement row1 = driver.findElements(By.className("transaction-import-row")).get(0);
+		assertRow(row1, "blue", "2023-01-05", "No category", "Ipsum", "Ipsum", "-8.37 €");
+		final WebElement row2 = driver.findElements(By.className("transaction-import-row")).get(1);
+		assertRow(row2, "blue", "2023-01-03", "No category", "Lorem", "Lorem", "50.00 €");
+	}
+
+	private void uploadAndSetColumnSettings(String csvFileName, Integer csvNumberOfRows)
 	{
 		driver.get(helper.getUrl() + "/transactionImport");
 		WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
 		wait.until(ExpectedConditions.textToBePresentInElementLocated(By.cssSelector(".headline"), "Import from bank CSV"));
 
-		final String csvPath = new File(getClass().getClassLoader().getResource("csv/three_entries.csv").getFile()).getAbsolutePath();
+		final String csvPath = new File(getClass().getClassLoader().getResource("csv/" + csvFileName).getFile()).getAbsolutePath();
 
 		uploadCsv(csvPath, ";", "UTF-8", "1");
 		wait = new WebDriverWait(driver, Duration.ofSeconds(5));
 		wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("button-cancel-csv-import")));
 
-		assertThat(driver.findElement(By.id("csv-file-name")).getText()).isEqualTo("three_entries.csv");
+		assertThat(driver.findElement(By.id("csv-file-name")).getText()).isEqualTo(csvFileName);
 
 		final List<WebElement> overviewRows = driver.findElements(By.cssSelector("#transaction-import-overview tr"));
-		assertThat(overviewRows).hasSize(4);
+		assertThat(overviewRows).hasSize(csvNumberOfRows);
 		final List<WebElement> columns = overviewRows.get(1).findElements(By.tagName("td"));
 		assertThat(columns).hasSize(3);
 		assertThat(columns.get(0).getText()).isEqualTo("03.01.2023");
diff --git a/BudgetMasterServer/src/test/resources/csv/fewer_rows_than_columns.csv b/BudgetMasterServer/src/test/resources/csv/fewer_rows_than_columns.csv
new file mode 100644
index 000000000..0b6c282ed
--- /dev/null
+++ b/BudgetMasterServer/src/test/resources/csv/fewer_rows_than_columns.csv
@@ -0,0 +1,3 @@
+Date;Title;Amount
+03.01.2023;Lorem;50.00
+05.01.2023;Ipsum;-8.37
\ No newline at end of file
-- 
GitLab