Skip to content

Commit 65edd26

Browse files
committed
AttachmentHelper integration test refactoring
extra testcases, fix SQL Server issue with IDENTITY column primary key
1 parent 7b51075 commit 65edd26

File tree

3 files changed

+165
-63
lines changed

3 files changed

+165
-63
lines changed

src/main/java/org/tailormap/api/geotools/featuresources/AttachmentsHelper.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.List;
2323
import java.util.Locale;
2424
import java.util.UUID;
25+
import java.util.regex.Pattern;
2526
import org.apache.commons.dbcp.DelegatingConnection;
2627
import org.geotools.api.feature.type.AttributeDescriptor;
2728
import org.geotools.jdbc.JDBCDataStore;
@@ -36,6 +37,10 @@
3637
public final class AttachmentsHelper {
3738
private static final Logger logger =
3839
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
40+
41+
private static final Pattern NUMERIC_WITH_IDENTITY = Pattern.compile(
42+
"(?i)\\b(?:int|integer|bigint|smallint|numeric|decimal|number)(?:\\s*\\(\\s*\\d+(?:\\s*,\\s*\\d+)?\\s*\\))?\\s+identity\\b");
43+
3944
private static final List<String> allowedPKTypesSupportingSize = List.of(
4045
// list of database types that support size modifiers
4146
// for their foreign key columns
@@ -254,6 +259,7 @@ private static String getCreateAttachmentsForFeatureTypeStatements(
254259
typeModifier);
255260

256261
JDBCConnectionProperties connProperties = featureType.getFeatureSource().getJdbcConnection();
262+
fkColumnType = getValidColumnType(fkColumnType, connProperties.getDbtype());
257263
switch (connProperties.getDbtype()) {
258264
case POSTGIS -> {
259265
return getPostGISCreateAttachmentsTableStatement(
@@ -286,6 +292,16 @@ private static String getCreateAttachmentsForFeatureTypeStatements(
286292
}
287293
}
288294

295+
private static String getValidColumnType(String columnType, JDBCConnectionProperties.DbtypeEnum dbtype) {
296+
if (dbtype.equals(JDBCConnectionProperties.DbtypeEnum.SQLSERVER)
297+
&& NUMERIC_WITH_IDENTITY.matcher(columnType).find()) {
298+
// Remove IDENTITY keyword from numeric types as it is not supported in FK columns
299+
columnType = columnType.replaceAll("(?i)\\s+identity\\b", "");
300+
}
301+
302+
return columnType;
303+
}
304+
289305
private static String getValidModifier(String columnType, int fkColumnSize) {
290306
if (fkColumnSize > 0 && allowedPKTypesSupportingSize.contains(columnType.toUpperCase(Locale.ROOT))) {
291307
if (columnType.equalsIgnoreCase("NUMERIC")

src/test/java/org/tailormap/api/geotools/featuresources/AttachmentsHelperIntegrationTest.java

Lines changed: 83 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -6,121 +6,141 @@
66
package org.tailormap.api.geotools.featuresources;
77

88
import static org.hamcrest.MatcherAssert.assertThat;
9-
import static org.hamcrest.Matchers.containsStringIgnoringCase;
9+
import static org.hamcrest.Matchers.is;
10+
import static org.hamcrest.Matchers.lessThanOrEqualTo;
1011
import static org.junit.jupiter.api.Assertions.assertEquals;
11-
import static org.junit.jupiter.api.Assertions.assertThrows;
1212
import static org.junit.jupiter.api.Assertions.fail;
13+
import static org.junit.jupiter.api.Assumptions.assumeFalse;
14+
import static org.junit.jupiter.params.provider.Arguments.arguments;
1315

16+
import java.io.IOException;
1417
import java.sql.Connection;
1518
import java.sql.ResultSet;
19+
import java.sql.SQLException;
1620
import java.sql.Statement;
1721
import java.util.Objects;
1822
import java.util.stream.Stream;
1923
import org.geotools.jdbc.JDBCDataStore;
24+
import org.junit.jupiter.api.AfterEach;
25+
import org.junit.jupiter.api.BeforeEach;
26+
import org.junit.jupiter.api.DisplayName;
27+
import org.junit.jupiter.api.MethodOrderer;
28+
import org.junit.jupiter.api.Order;
2029
import org.junit.jupiter.api.Test;
21-
import org.junit.jupiter.params.ParameterizedTest;
30+
import org.junit.jupiter.api.TestMethodOrder;
31+
import org.junit.jupiter.params.Parameter;
32+
import org.junit.jupiter.params.ParameterizedClass;
2233
import org.junit.jupiter.params.provider.Arguments;
2334
import org.junit.jupiter.params.provider.MethodSource;
24-
import org.junit.jupiter.params.provider.NullAndEmptySource;
2535
import org.springframework.beans.factory.annotation.Autowired;
2636
import org.tailormap.api.annotation.PostgresIntegrationTest;
2737
import org.tailormap.api.persistence.TMFeatureType;
2838
import org.tailormap.api.persistence.json.AttachmentAttributeType;
2939
import org.tailormap.api.repository.FeatureSourceRepository;
3040
import org.tailormap.api.repository.FeatureTypeRepository;
3141

42+
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
3243
@PostgresIntegrationTest
44+
@ParameterizedClass
45+
@DisplayName("AttachmentsHelper integration test")
46+
@MethodSource("titlesAndNamesForFeatureSourcesAndFeatureTypes")
3347
class AttachmentsHelperIntegrationTest {
48+
3449
@Autowired
3550
private FeatureSourceRepository featureSourceRepository;
3651

3752
@Autowired
3853
private FeatureTypeRepository featureTypeRepository;
3954

40-
private static Stream<Arguments> titlesAndNamesForFeatureSourcesAndFeatureTypes() {
55+
@Parameter(0)
56+
private String featureSourceTitle;
57+
58+
@Parameter(1)
59+
private String featureTypeName;
60+
61+
private TMFeatureType featureType;
62+
private JDBCDataStore ds;
63+
64+
static Stream<Arguments> titlesAndNamesForFeatureSourcesAndFeatureTypes() {
4165
return Stream.of(
42-
Arguments.of("PostGIS", "bord"),
43-
Arguments.of("MS SQL Server", "bord"),
44-
Arguments.of("Oracle", "BORD"),
45-
Arguments.of("PostGIS OSM", "osm_polygon"));
66+
arguments("PostGIS", "bord"),
67+
arguments("PostGIS", "pk_variation_bigint"),
68+
arguments("PostGIS", "pk_variation_decimal"),
69+
arguments("PostGIS", "pk_variation_integer"),
70+
arguments("PostGIS", "pk_variation_numeric"),
71+
arguments("PostGIS", "pk_variation_serial"),
72+
arguments("PostGIS", "pk_variation_uuid"),
73+
arguments("MS SQL Server", "bord"),
74+
arguments("MS SQL Server", "pk_variation_bigint"),
75+
arguments("MS SQL Server", "pk_variation_decimal"),
76+
arguments("MS SQL Server", "pk_variation_integer"),
77+
arguments("MS SQL Server", "pk_variation_numeric"),
78+
arguments("MS SQL Server", "pk_variation_serial"),
79+
arguments("MS SQL Server", "pk_variation_uuid"),
80+
arguments("Oracle", "BORD"),
81+
arguments("Oracle", "PK_VARIATION_BIGINT"),
82+
arguments("Oracle", "PK_VARIATION_DECIMAL"),
83+
arguments("Oracle", "PK_VARIATION_INTEGER"),
84+
arguments("Oracle", "PK_VARIATION_NUMERIC"),
85+
arguments("Oracle", "PK_VARIATION_SERIAL"),
86+
arguments("Oracle", "PK_VARIATION_UUID"),
87+
arguments("PostGIS OSM", "osm_polygon"));
4688
}
4789

48-
@ParameterizedTest
49-
@MethodSource("titlesAndNamesForFeatureSourcesAndFeatureTypes")
50-
void testGetCreateAttachmentsForFeatureTypeStatements(String fsTitle, String ftName) {
51-
TMFeatureType featureType = featureTypeRepository
90+
@BeforeEach
91+
void setUp() throws IOException {
92+
featureType = featureTypeRepository
5293
.getTMFeatureTypeByNameAndFeatureSource(
53-
ftName, featureSourceRepository.getByTitle(fsTitle).orElseThrow())
94+
featureTypeName,
95+
featureSourceRepository.getByTitle(featureSourceTitle).orElseThrow())
5496
.orElseThrow();
5597

5698
featureType
5799
.getSettings()
58100
.addAttachmentAttributesItem(new AttachmentAttributeType()
59-
.attributeName("bord_photos")
101+
.attributeName(featureTypeName + "_photos")
60102
.maxAttachmentSize(4_000_000L)
61103
.mimeType("image/jpeg"));
62104

63-
JDBCDataStore ds = null;
105+
ds = (JDBCDataStore) new JDBCFeatureSourceHelper().createDataStore(featureType.getFeatureSource());
106+
if (Objects.equals(featureTypeName, "osm_polygon"))
107+
featureTypeName = ds.getDatabaseSchema() + "." + featureTypeName;
108+
}
109+
110+
@AfterEach
111+
void tearDown() throws SQLException {
112+
featureType.getSettings().getAttachmentAttributes().clear();
113+
try (Connection conn = ds.getDataSource().getConnection();
114+
Statement stmt = conn.createStatement()) {
115+
boolean success = stmt.execute("drop table " + featureTypeName + "_attachments");
116+
assumeFalse(success, "Maybe failed to cleanup attachments table, we did not get an update count result.");
117+
assertThat(stmt.getUpdateCount(), is(lessThanOrEqualTo(1)));
118+
} finally {
119+
if (ds != null) {
120+
ds.dispose();
121+
}
122+
}
123+
}
124+
125+
@Order(1)
126+
@Test
127+
@DisplayName("Create attachments table for feature type.")
128+
void createAttachmentsTableForFeatureTypeStatements() {
64129
try {
65130
AttachmentsHelper.createAttachmentTableForFeatureType(featureType);
66-
ds = (JDBCDataStore) new JDBCFeatureSourceHelper().createDataStore(featureType.getFeatureSource());
67131

68-
if (Objects.equals(ftName, "osm_polygon")) ftName = ds.getDatabaseSchema() + "." + ftName;
69132
try (Connection conn = ds.getDataSource().getConnection();
70133
Statement stmt = conn.createStatement();
71-
ResultSet rs = stmt.executeQuery("select count(*) from " + ftName + "_attachments")) {
134+
ResultSet rs = stmt.executeQuery("select count(*) from " + featureTypeName + "_attachments")) {
72135
if (rs.next()) {
73136
int count = rs.getInt(1);
74137
assertEquals(0, count, "Attachments table exists but is not empty.");
75138
} else {
76-
fail("Attachments table does not exist.");
139+
fail("Attachments table '%s_attachments' does not exist.".formatted(featureTypeName));
77140
}
78-
// cleanup created attachments table
79-
stmt.execute("drop table " + ftName + "_attachments");
80141
}
81142
} catch (Exception e) {
82143
fail(e.getMessage());
83-
} finally {
84-
if (ds != null) {
85-
ds.dispose();
86-
}
87144
}
88145
}
89-
90-
@ParameterizedTest
91-
@NullAndEmptySource
92-
void testInvalidAttachmentAttributes(String invalidInput) throws Exception {
93-
TMFeatureType featureType = featureTypeRepository
94-
.getTMFeatureTypeByNameAndFeatureSource(
95-
"bak", featureSourceRepository.getByTitle("PostGIS").orElseThrow())
96-
.orElseThrow();
97-
98-
featureType
99-
.getSettings()
100-
.addAttachmentAttributesItem(new AttachmentAttributeType()
101-
.attributeName(invalidInput) // Invalid attribute name
102-
.maxAttachmentSize(4_000_000L)
103-
.mimeType("image/jpeg"));
104-
105-
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
106-
AttachmentsHelper.createAttachmentTableForFeatureType(featureType);
107-
});
108-
109-
assertThat(
110-
exception.getMessage(),
111-
containsStringIgnoringCase(
112-
"FeatureType bak has an attachment attribute with invalid (null or empty) attribute name"));
113-
}
114-
115-
@Test
116-
void testNullFeatureType() {
117-
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
118-
AttachmentsHelper.createAttachmentTableForFeatureType(null);
119-
});
120-
121-
assertThat(
122-
exception.getMessage(),
123-
containsStringIgnoringCase(
124-
"FeatureType null is invalid or has no attachment attributes defined in its settings"));
125-
}
126146
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (C) 2025 B3Partners B.V.
3+
*
4+
* SPDX-License-Identifier: MIT
5+
*/
6+
package org.tailormap.api.geotools.featuresources;
7+
8+
import static org.hamcrest.MatcherAssert.assertThat;
9+
import static org.hamcrest.Matchers.containsStringIgnoringCase;
10+
import static org.junit.jupiter.api.Assertions.assertThrows;
11+
12+
import org.junit.jupiter.api.Test;
13+
import org.junit.jupiter.params.ParameterizedTest;
14+
import org.junit.jupiter.params.provider.NullAndEmptySource;
15+
import org.springframework.beans.factory.annotation.Autowired;
16+
import org.tailormap.api.annotation.PostgresIntegrationTest;
17+
import org.tailormap.api.persistence.TMFeatureType;
18+
import org.tailormap.api.persistence.json.AttachmentAttributeType;
19+
import org.tailormap.api.repository.FeatureSourceRepository;
20+
import org.tailormap.api.repository.FeatureTypeRepository;
21+
22+
@PostgresIntegrationTest
23+
class InvalidInputAttachmentsHelperIntegrationTest {
24+
@Autowired
25+
private FeatureSourceRepository featureSourceRepository;
26+
27+
@Autowired
28+
private FeatureTypeRepository featureTypeRepository;
29+
30+
@ParameterizedTest
31+
@NullAndEmptySource
32+
void testInvalidAttachmentAttributes(String invalidInput) throws Exception {
33+
TMFeatureType featureType = featureTypeRepository
34+
.getTMFeatureTypeByNameAndFeatureSource(
35+
"bak", featureSourceRepository.getByTitle("PostGIS").orElseThrow())
36+
.orElseThrow();
37+
38+
featureType
39+
.getSettings()
40+
.addAttachmentAttributesItem(new AttachmentAttributeType()
41+
.attributeName(invalidInput) // Invalid attribute name
42+
.maxAttachmentSize(4_000_000L)
43+
.mimeType("image/jpeg"));
44+
45+
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
46+
AttachmentsHelper.createAttachmentTableForFeatureType(featureType);
47+
});
48+
49+
assertThat(
50+
exception.getMessage(),
51+
containsStringIgnoringCase(
52+
"FeatureType bak has an attachment attribute with invalid (null or empty) attribute name"));
53+
}
54+
55+
@Test
56+
void testNullFeatureType() {
57+
Exception exception = assertThrows(IllegalArgumentException.class, () -> {
58+
AttachmentsHelper.createAttachmentTableForFeatureType(null);
59+
});
60+
61+
assertThat(
62+
exception.getMessage(),
63+
containsStringIgnoringCase(
64+
"FeatureType null is invalid or has no attachment attributes defined in its settings"));
65+
}
66+
}

0 commit comments

Comments
 (0)