Skip to content

Commit ca54569

Browse files
committed
"assertMatchesCsv(..)" can not compare CSVs that store nulls as empty strings #140
1 parent 0d065cd commit ca54569

12 files changed

Lines changed: 311 additions & 32 deletions

File tree

RELEASE-NOTES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* #137 Remove deprecated modules and APIs
44
* #138 Liquibase - exclude "javax.xml.bind:jaxb-api" dependency
55
* #139 Upgrade Apache Derby dependency to 10.17.x.x
6+
* #140 "assertMatchesCsv(..)" can not compare CSVs that store nulls as empty strings
67

78
## 3.0-RC1
89

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/*
2+
* Licensed to ObjectStyle LLC under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ObjectStyle LLC licenses
6+
* this file to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package io.bootique.jdbc.junit5.derby.matcher;
21+
22+
import io.bootique.jdbc.junit5.Table;
23+
import io.bootique.jdbc.junit5.derby.DerbyTester;
24+
import io.bootique.jdbc.junit5.matcher.CsvTableMatcher;
25+
import io.bootique.junit5.BQTest;
26+
import io.bootique.junit5.BQTestTool;
27+
import org.junit.jupiter.api.BeforeAll;
28+
import org.junit.jupiter.api.Test;
29+
30+
import java.math.BigDecimal;
31+
32+
import static org.junit.jupiter.api.Assertions.assertFalse;
33+
34+
@BQTest
35+
public class CsvTableMatcherIT {
36+
37+
@BQTestTool
38+
static final DerbyTester db = DerbyTester
39+
.db()
40+
.deleteBeforeEachTest("t1", "t2", "t3", "t4");
41+
42+
private static Table T1;
43+
private static Table T2;
44+
private static Table T3;
45+
private static Table T4;
46+
47+
@BeforeAll
48+
public static void setupDB() {
49+
50+
db.execStatement().exec("CREATE TABLE \"t1\" (\"c1\" INT, \"c2\" VARCHAR(10), \"c3\" VARCHAR(10))");
51+
db.execStatement().exec("CREATE TABLE \"t2\" (\"c1\" INT, \"c2\" INT, \"c3\" DATE, \"c4\" TIMESTAMP)");
52+
db.execStatement().exec("CREATE TABLE \"t3\" (\"c1\" INT, \"c2\" VARCHAR (10) FOR BIT DATA)");
53+
db.execStatement().exec("CREATE TABLE \"t4\" (\"c1\" BIGINT, \"c2\" VARCHAR (10), \"c3\" DECIMAL(10,2))");
54+
55+
T1 = db.getTable("t1");
56+
T2 = db.getTable("t2");
57+
T3 = db.getTable("t3");
58+
T4 = db.getTable("t4");
59+
}
60+
61+
@Test
62+
public void assertMatchesCsv() {
63+
64+
T1.insertColumns("c1", "c2", "c3")
65+
.values(2, "tt", "xyz")
66+
.values(1, "", "abcd")
67+
.exec();
68+
69+
new CsvTableMatcher(T1).keyColumns("c1").assertMatches("classpath:io/bootique/jdbc/junit5/derby/matcher/t1_ref.csv");
70+
}
71+
72+
@Test
73+
public void assertMatchesCsv_EmptyAsNulls() {
74+
75+
T1.insertColumns("c1", "c2", "c3")
76+
.values(2, "tt", "xyz")
77+
.values(1, null, "abcd")
78+
.exec();
79+
80+
new CsvTableMatcher(T1)
81+
.keyColumns("c1")
82+
.emptyStringIsNull()
83+
.assertMatches("classpath:io/bootique/jdbc/junit5/derby/matcher/t1_ref.csv");
84+
}
85+
86+
@Test
87+
public void assertMatchesCsv_NoMatch() {
88+
89+
T1.insertColumns("c1", "c2", "c3")
90+
.values(1, "tt", "xyz")
91+
.values(2, "", "abcd")
92+
.exec();
93+
94+
boolean succeeded;
95+
try {
96+
new CsvTableMatcher(T1).keyColumns("c1").assertMatches("classpath:io/bootique/jdbc/junit5/derby/matcher/t1_ref.csv");
97+
succeeded = true;
98+
} catch (AssertionError e) {
99+
// expected
100+
succeeded = false;
101+
}
102+
103+
assertFalse(succeeded, "Must have failed - data sets do not match");
104+
}
105+
106+
@Test
107+
public void assertMatchesCsv_Dates() {
108+
109+
110+
T2.insertColumns("c1", "c2", "c3", "c4")
111+
.values(3, null, "2018-01-09", "2018-01-10 14:00:01")
112+
.values(1, null, "2016-01-09", "2016-01-10 10:00:00")
113+
.values(2, null, "2017-01-09", "2017-01-10 13:00:01")
114+
.exec();
115+
116+
new CsvTableMatcher(T2).keyColumns("c1").assertMatches("classpath:io/bootique/jdbc/junit5/derby/matcher/t2_ref.csv");
117+
}
118+
119+
@Test
120+
public void assertMatchesCsv_Binary() {
121+
122+
T3.insertColumns("c1", "c2")
123+
.values(3, null)
124+
.values(1, "abcd".getBytes())
125+
.values(2, "kmln".getBytes())
126+
.exec();
127+
128+
new CsvTableMatcher(T3).keyColumns("c1").assertMatches("classpath:io/bootique/jdbc/junit5/derby/matcher/t3_ref.csv");
129+
}
130+
131+
@Test
132+
public void assertMatchesCsv_Numbers() {
133+
134+
T4.insertColumns("c1", "c2", "c3")
135+
.values(1L, "abc", new BigDecimal("2.34"))
136+
.values(2L, "xyz", BigDecimal.ZERO)
137+
.exec();
138+
139+
new CsvTableMatcher(T4).keyColumns("c1").assertMatches("classpath:io/bootique/jdbc/junit5/derby/matcher/t4_ref.csv");
140+
}
141+
}

bootique-jdbc-junit5-derby/src/test/java/io/bootique/jdbc/junit5/derby/matcher/TableMatcherIT.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ public void assertMatches_Eq_Negative() {
150150
"The matcher incorrectly assumed there was 1 row matching condition.");
151151
}
152152

153+
@Deprecated
153154
@Test
154155
public void assertMatchesCsv() {
155156
TableMatcher matcher = new TableMatcher(T1);
@@ -162,6 +163,7 @@ public void assertMatchesCsv() {
162163
matcher.assertMatchesCsv("classpath:io/bootique/jdbc/junit5/derby/matcher/t1_ref.csv", "c1");
163164
}
164165

166+
@Deprecated
165167
@Test
166168
public void assertMatchesCsv_NoMatch() {
167169

@@ -184,6 +186,7 @@ public void assertMatchesCsv_NoMatch() {
184186
assertFalse(succeeded, "Must have failed - data sets do not match");
185187
}
186188

189+
@Deprecated
187190
@Test
188191
public void assertMatchesCsv_Dates() {
189192

@@ -198,6 +201,7 @@ public void assertMatchesCsv_Dates() {
198201
matcher.assertMatchesCsv("classpath:io/bootique/jdbc/junit5/derby/matcher/t2_ref.csv", "c1");
199202
}
200203

204+
@Deprecated
201205
@Test
202206
public void assertMatchesCsv_Binary() {
203207

@@ -212,6 +216,7 @@ public void assertMatchesCsv_Binary() {
212216
matcher.assertMatchesCsv("classpath:io/bootique/jdbc/junit5/derby/matcher/t3_ref.csv", "c1");
213217
}
214218

219+
@Deprecated
215220
@Test
216221
public void assertMatchesCsv_Numbers() {
217222

bootique-jdbc-junit5/src/main/java/io/bootique/jdbc/junit5/Table.java

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,20 @@
1919

2020
package io.bootique.jdbc.junit5;
2121

22-
import io.bootique.jdbc.junit5.connector.*;
22+
import io.bootique.jdbc.junit5.connector.ArrayReader;
23+
import io.bootique.jdbc.junit5.connector.DbConnector;
2324
import io.bootique.jdbc.junit5.dataset.CsvDataSetBuilder;
2425
import io.bootique.jdbc.junit5.dataset.TableDataSet;
26+
import io.bootique.jdbc.junit5.matcher.CsvTableMatcher;
2527
import io.bootique.jdbc.junit5.matcher.TableMatcher;
2628
import io.bootique.jdbc.junit5.metadata.DbColumnMetadata;
2729
import io.bootique.jdbc.junit5.metadata.DbTableMetadata;
28-
import io.bootique.jdbc.junit5.sql.*;
30+
import io.bootique.jdbc.junit5.sql.DeleteBuilder;
31+
import io.bootique.jdbc.junit5.sql.ExecStatementBuilder;
32+
import io.bootique.jdbc.junit5.sql.InsertBuilder;
33+
import io.bootique.jdbc.junit5.sql.SelectBuilder;
34+
import io.bootique.jdbc.junit5.sql.SelectStatementBuilder;
35+
import io.bootique.jdbc.junit5.sql.UpdateSetBuilder;
2936

3037
/**
3138
* JDBC utility class for manipulating and analyzing data in a single DB table. Used to load, clean up and match test
@@ -88,7 +95,7 @@ public InsertBuilder insertColumns(String... columns) {
8895
}
8996

9097
/**
91-
* Returns a builder object to assemble a data set matching this table structure either from CSV-like strings,
98+
* Returns a builder object to assemble a dataset matching this table structure either from CSV-like strings,
9299
* or from a CSV file resource.
93100
*
94101
* @return a builder of a {@link TableDataSet}.
@@ -121,6 +128,13 @@ public TableMatcher matcher() {
121128
return new TableMatcher(this);
122129
}
123130

131+
/**
132+
* @since 4.0
133+
*/
134+
public CsvTableMatcher csvMatcher() {
135+
return new CsvTableMatcher(this);
136+
}
137+
124138
/**
125139
* @param columns an array of columns to select
126140
* @since 2.0

bootique-jdbc-junit5/src/main/java/io/bootique/jdbc/junit5/dataset/CsvDataSetBuilder.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import io.bootique.jdbc.junit5.Table;
2323
import io.bootique.resource.ResourceFactory;
24+
import org.apache.commons.csv.CSVFormat;
2425

2526
import java.io.IOException;
2627
import java.io.InputStreamReader;
@@ -35,26 +36,37 @@
3536
public class CsvDataSetBuilder {
3637

3738
private final Table table;
39+
40+
private CSVFormat format;
3841
private FromStringConverter valueConverter;
3942

4043
public CsvDataSetBuilder(Table table) {
4144
this.table = table;
4245
this.valueConverter = DefaultFromStringConverter.DEFAULT_CONVERTER;
46+
this.format = CSVFormat.DEFAULT;
4347
}
4448

4549
public CsvDataSetBuilder valueConverter(FromStringConverter converter) {
4650
this.valueConverter = Objects.requireNonNull(converter);
4751
return this;
4852
}
4953

54+
/**
55+
* @since 4.0
56+
*/
57+
public CsvDataSetBuilder format(CSVFormat format) {
58+
this.format = Objects.requireNonNull(format);
59+
return this;
60+
}
61+
5062
/**
5163
* Starts building a data set with the specified columns.
5264
*
5365
* @param csvString CSV string specifying DataSet columns.
5466
* @return a builder for API-based DataSet.
5567
*/
5668
public CsvStringsDataSetBuilder columns(String csvString) {
57-
return new CsvStringsDataSetBuilder(table, csvString, valueConverter);
69+
return new CsvStringsDataSetBuilder(table, csvString, format, valueConverter);
5870
}
5971

6072
/**
@@ -64,7 +76,7 @@ public CsvStringsDataSetBuilder columns(String csvString) {
6476
* @return a builder for API-based DataSet.
6577
*/
6678
public CsvStringsDataSetBuilder rows(String... csvStrings) {
67-
return new CsvStringsDataSetBuilder(table, valueConverter).rows(csvStrings);
79+
return new CsvStringsDataSetBuilder(table, null, format, valueConverter).rows(csvStrings);
6880
}
6981

7082
public TableDataSet load(String csvResource) {
@@ -74,7 +86,7 @@ public TableDataSet load(String csvResource) {
7486
public TableDataSet load(ResourceFactory csvResource) {
7587

7688
try (Reader reader = new InputStreamReader(csvResource.getUrl().openStream(), StandardCharsets.UTF_8)) {
77-
return new CsvReader(table, valueConverter).loadDataSet(reader);
89+
return new CsvReader(table, format, valueConverter).loadDataSet(reader);
7890
} catch (IOException e) {
7991
throw new RuntimeException("Error reading CSV " + csvResource, e);
8092
}

bootique-jdbc-junit5/src/main/java/io/bootique/jdbc/junit5/dataset/CsvReader.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,32 +31,35 @@
3131
import java.util.Collections;
3232
import java.util.Iterator;
3333
import java.util.List;
34+
import java.util.Objects;
3435

3536
class CsvReader {
3637

37-
private final FromStringConverter valueConverter;
3838
private final Table table;
39+
private final CSVFormat format;
40+
private final FromStringConverter valueConverter;
3941

40-
CsvReader(Table table, FromStringConverter valueConverter) {
41-
this.valueConverter = valueConverter;
42+
CsvReader(Table table, CSVFormat format, FromStringConverter valueConverter) {
4243
this.table = table;
44+
this.format = Objects.requireNonNull(format);
45+
this.valueConverter = valueConverter;
4346
}
4447

4548
TableDataSet loadDataSet(Reader dataReader) throws IOException {
46-
try (CSVParser parser = new CSVParser(dataReader, CSVFormat.DEFAULT, 0, 0)) {
49+
try (CSVParser parser = new CSVParser(dataReader, format, 0, 0)) {
4750

4851
Iterator<CSVRecord> rows = parser.iterator();
4952
if (!rows.hasNext()) {
5053
return new TableDataSet(table, new DbColumnMetadata[0], Collections.emptyList());
5154
}
5255

53-
DbColumnMetadata[] header = getHeader(rows.next());
56+
DbColumnMetadata[] header = getHeader(rows.next());
5457
return readData(header, rows);
5558
}
5659
}
5760

5861
TableDataSet loadDataSet(DbColumnMetadata[] header, Reader dataReader) throws IOException {
59-
try (CSVParser parser = new CSVParser(dataReader, CSVFormat.DEFAULT, 0, 0)) {
62+
try (CSVParser parser = new CSVParser(dataReader, format, 0, 0)) {
6063
return readData(header, parser.iterator());
6164
}
6265
}

0 commit comments

Comments
 (0)