Skip to content

Commit 2f350ad

Browse files
authored
Merge pull request #141 from kit-data-manager/135-h2-db-+-local-pidsystem-may-cause-unexpected-errors
Fix 135 "h2 db + local pidsystem may cause unexpected errors"
2 parents 88c50de + 5e152b8 commit 2f350ad

File tree

7 files changed

+231
-8
lines changed

7 files changed

+231
-8
lines changed

src/main/java/edu/kit/datamanager/pit/pidsystem/impl/local/PidDatabaseObject.java

+24
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,31 @@ public class PidDatabaseObject {
3232
@Column(name = "pid")
3333
private String pid;
3434

35+
/**
36+
* About the column definition we use here:
37+
*
38+
* Some databases (known: h2) store collections in encoded strings. This causes
39+
* an issue for mid-to-large records. We need to increase the length of stings
40+
* in this case:
41+
*
42+
* int javax.persistence.Column.length() (Optional) The column length. (Applies
43+
* only if a string-valued column is used.) Default: 255
44+
*
45+
* The h2 database we use for testing has a CHARACTER VARYING limit of
46+
* 1_000_000_000
47+
* (http://h2database.com/html/datatypes.html#character_varying_type).
48+
*
49+
* Postgres does not have a limit according to
50+
* https://www.postgresql.org/docs/current/datatype-character.html, but we
51+
* assume it will not use its text datatype for the collection anyway.
52+
*
53+
* The limit of MySql seems to be 65_535. SQlite has a upper limit of
54+
* 1_000_000_000 (set by default).
55+
*
56+
* Our extended tests with the h2 database require a length of ~6500 or more.
57+
*/
3558
@ElementCollection(fetch = FetchType.EAGER)
59+
@Column(length = 65_535)
3660
private Map<String, ArrayList<String>> entries = new HashMap<>();
3761

3862
/** For hibernate */
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package edu.kit.datamanager.pit;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import java.util.stream.IntStream;
6+
7+
import edu.kit.datamanager.pit.domain.PIDRecord;
8+
import edu.kit.datamanager.pit.pidgeneration.PidSuffixGenerator;
9+
10+
public class RecordTestHelper {
11+
/**
12+
* Generates a PID record with N attibutes, each having M values. All attributes
13+
* and values are PIDs of {@link pidGenerator} prefixed with {@link PID_PREFIX}.
14+
*
15+
* @param numAttributes the number of attibutes (called N in the description
16+
* above)
17+
* @param numValues the number of attributes (called M in the description
18+
* above)
19+
* @return a PID record as configured.
20+
*/
21+
public static PIDRecord getFakePidRecord(
22+
final int numAttributes,
23+
final int numValues,
24+
final String prefix,
25+
final PidSuffixGenerator generator)
26+
{
27+
PIDRecord r = new PIDRecord();
28+
r.setPid(generator.generate().get());
29+
IntStream.range(0, numAttributes)
30+
.mapToObj(i -> generator.generate().getWithPrefix(prefix))
31+
.forEach(attribute -> {
32+
IntStream.range(0, numValues)
33+
.mapToObj(i -> generator.generate().getWithPrefix(prefix))
34+
.forEach(value -> r.addEntry(attribute, "name", value));
35+
});
36+
37+
// In theory these could fail if the generator generated a PID twice, but it is very unlikely.
38+
assertEquals(numAttributes, r.getPropertyIdentifiers().size());
39+
assertEquals(numValues, r.getPropertyValues(r.getPropertyIdentifiers().iterator().next()).length);
40+
41+
return r;
42+
}
43+
}

src/test/java/edu/kit/datamanager/pit/pidsystem/IIdentifierSystemTest.java src/test/java/edu/kit/datamanager/pit/pidsystem/IIdentifierSystemQueryTest.java

+9-3
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,17 @@
2121
import net.handle.hdllib.HandleException;
2222

2323
/**
24-
* This test ensures the interface is implemented correctly, with the following limits:
25-
* - TODO do write tests (update, register, delete PIDs), as this requires authentication or mocking
24+
* This test ensures the interface is implemented correctly, with the following
25+
* limits:
26+
*
27+
* - Only read-tests (write tests are only possible for sandboxed systems on a
28+
* regular and automated basis)
2629
* - TODO do queryByType tests (requires PID with registered type as property)
30+
*
31+
* NOTE: The difference to the tests in the web module is that this only tests
32+
* the pidsystem without any validation or other functionality.
2733
*/
28-
public class IIdentifierSystemTest {
34+
public class IIdentifierSystemQueryTest {
2935

3036
private static Stream<Arguments> implProvider() throws HandleException, IOException {
3137
HandleProtocolProperties props = new HandleProtocolProperties();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package edu.kit.datamanager.pit.pidsystem;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
5+
6+
import java.io.IOException;
7+
8+
import org.junit.jupiter.api.BeforeEach;
9+
import org.junit.jupiter.api.DisplayName;
10+
import org.junit.jupiter.api.Test;
11+
import org.springframework.beans.factory.annotation.Autowired;
12+
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
13+
import org.springframework.boot.test.context.SpringBootTest;
14+
import org.springframework.test.context.ActiveProfiles;
15+
import org.springframework.test.context.TestPropertySource;
16+
17+
import edu.kit.datamanager.pit.RecordTestHelper;
18+
import edu.kit.datamanager.pit.domain.PIDRecord;
19+
import edu.kit.datamanager.pit.pidgeneration.PidSuffixGenerator;
20+
import edu.kit.datamanager.pit.pidsystem.impl.local.LocalPidSystem;
21+
22+
/**
23+
* Write tests are only possible with sandboxed PID systems like InMemory and
24+
* Local.
25+
*
26+
* The aim is to test the creation of large PID records.
27+
*
28+
* For the Local system, we need the database and therefore a Spring context.
29+
*/
30+
// JUnit5 + Spring
31+
@SpringBootTest
32+
// Set the in-memory implementation
33+
@TestPropertySource(
34+
locations = "/test/application-test.properties",
35+
properties = "pit.pidsystem.implementation=LOCAL"
36+
)
37+
@ActiveProfiles("test")
38+
public class IIdentifierSystemWriteTest {
39+
40+
@Autowired
41+
private LocalPidSystem localPidSystem;
42+
43+
@Autowired
44+
private PidSuffixGenerator pidGenerator;
45+
46+
private static final String PID_PREFIX = "sandboxed/";
47+
48+
@Autowired
49+
DataSourceProperties dataSourceProperties;
50+
51+
@BeforeEach
52+
void setup() throws InterruptedException, IOException {
53+
// ensure we run on an in-memory DB for testing
54+
//assertTrue(dataSourceProperties.determineUrl().contains("mem"));
55+
// ensure wiring worked
56+
assertNotNull(localPidSystem);
57+
58+
// Register just a small record for the database to initialize maybe or so.
59+
PIDRecord r = new PIDRecord();
60+
r.setPid(pidGenerator.generate().get());
61+
String attribute = pidGenerator.generate().getWithPrefix(PID_PREFIX);
62+
String value = pidGenerator.generate().getWithPrefix(PID_PREFIX);
63+
r.addEntry(attribute, "test", value);
64+
65+
localPidSystem.registerPID(r);
66+
}
67+
68+
@Test
69+
@DisplayName("Testing PID Records with usual/larger size, with the Local PID system (in-memory db).")
70+
void testExtensiveRecordWithLocalPidSystem() throws IOException {
71+
// as we use an in-memory db for testing, lets not make it too large.
72+
int numAttributes = 100;
73+
int numValues = 100;
74+
75+
PIDRecord r = RecordTestHelper.getFakePidRecord(numAttributes, numValues, PID_PREFIX, pidGenerator);
76+
assertEquals(numAttributes, r.getPropertyIdentifiers().size());
77+
assertEquals(numValues, r.getPropertyValues(r.getPropertyIdentifiers().iterator().next()).length);
78+
79+
this.localPidSystem.registerPID(r);
80+
}
81+
}

src/test/java/edu/kit/datamanager/pit/pidsystem/impl/local/LocalPidSystemTest.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import edu.kit.datamanager.pit.common.InvalidConfigException;
2525
import edu.kit.datamanager.pit.domain.PIDRecord;
2626
import edu.kit.datamanager.pit.domain.TypeDefinition;
27-
import edu.kit.datamanager.pit.pidsystem.IIdentifierSystemTest;
27+
import edu.kit.datamanager.pit.pidsystem.IIdentifierSystemQueryTest;
2828

2929
/**
3030
* This tests the same things as `IIdentifierSystemTest`, but is separated from
@@ -40,7 +40,7 @@
4040
)
4141
@ActiveProfiles("test")
4242
public class LocalPidSystemTest {
43-
IIdentifierSystemTest systemTests = new IIdentifierSystemTest();
43+
IIdentifierSystemQueryTest systemTests = new IIdentifierSystemQueryTest();
4444

4545
@Autowired
4646
LocalPidSystem localPidSystem;
@@ -96,8 +96,8 @@ void testAllSystemTests() throws Exception {
9696
PIDRecord newRec = localPidSystem.queryAllProperties(pid);
9797
assertEquals(rec, newRec);
9898

99-
Set<Method> publicMethods = new HashSet<>(Arrays.asList(IIdentifierSystemTest.class.getMethods()));
100-
Set<Method> allDirectMethods = new HashSet<>(Arrays.asList(IIdentifierSystemTest.class.getDeclaredMethods()));
99+
Set<Method> publicMethods = new HashSet<>(Arrays.asList(IIdentifierSystemQueryTest.class.getMethods()));
100+
Set<Method> allDirectMethods = new HashSet<>(Arrays.asList(IIdentifierSystemQueryTest.class.getDeclaredMethods()));
101101
publicMethods.retainAll(allDirectMethods);
102102
assertEquals(7, publicMethods.size());
103103
for (Method test : publicMethods) {

src/test/java/edu/kit/datamanager/pit/web/RestWithInMemoryTest.java

+33
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.fasterxml.jackson.databind.ObjectMapper;
1414

1515
import org.junit.jupiter.api.BeforeEach;
16+
import org.junit.jupiter.api.DisplayName;
1617
import org.junit.jupiter.api.Test;
1718
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
1819
import org.springframework.beans.factory.annotation.Autowired;
@@ -22,7 +23,11 @@
2223
import org.springframework.http.MediaType;
2324
import org.springframework.web.context.WebApplicationContext;
2425

26+
import edu.kit.datamanager.pit.RecordTestHelper;
27+
import edu.kit.datamanager.pit.configuration.ApplicationProperties;
28+
import edu.kit.datamanager.pit.configuration.ApplicationProperties.ValidationStrategy;
2529
import edu.kit.datamanager.pit.domain.PIDRecord;
30+
import edu.kit.datamanager.pit.pidgeneration.PidSuffixGenerator;
2631
import edu.kit.datamanager.pit.pidlog.KnownPid;
2732
import edu.kit.datamanager.pit.pidlog.KnownPidsDao;
2833
import edu.kit.datamanager.pit.pidsystem.impl.HandleProtocolAdapter;
@@ -69,6 +74,12 @@ public class RestWithInMemoryTest {
6974
@Autowired
7075
private WebApplicationContext webApplicationContext;
7176

77+
@Autowired
78+
private PidSuffixGenerator pidGenerator;
79+
80+
@Autowired
81+
private ApplicationProperties appProps;
82+
7283
private MockMvc mockMvc;
7384

7485
private ObjectMapper mapper;
@@ -85,6 +96,7 @@ public void setup() throws Exception {
8596
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
8697
this.mapper = this.webApplicationContext.getBean("OBJECT_MAPPER_BEAN", ObjectMapper.class);
8798
this.knownPidsDao.deleteAll();
99+
this.appProps.setValidationStrategy(ValidationStrategy.EMBEDDED_STRICT);
88100
}
89101

90102
@Test
@@ -130,6 +142,27 @@ public void testCreateEmptyRecord() throws Exception {
130142
assertEquals(0, this.knownPidsDao.count());
131143
}
132144

145+
@Test
146+
@DisplayName("Testing PID Records with usual/larger size, with the InMemory PID system.")
147+
public void testExtensiveRecord() throws Exception {
148+
// create mockup of a large record. It contains non-registered PIDs and can not be validated.
149+
this.appProps.setValidationStrategy(ValidationStrategy.NONE_DEBUG);
150+
// as we use an in-memory data structure, lets not make it too large.
151+
int numAttributes = 100;
152+
int numValues = 100;
153+
assertTrue(numAttributes * numValues > 256);
154+
PIDRecord r = RecordTestHelper.getFakePidRecord(numAttributes, numValues, "sandboxed/", pidGenerator);
155+
156+
String rJson = ApiMockUtils.serialize(r);
157+
ApiMockUtils.registerRecord(
158+
this.mockMvc,
159+
rJson,
160+
MediaType.APPLICATION_JSON_VALUE,
161+
MediaType.APPLICATION_JSON_VALUE,
162+
MockMvcResultMatchers.status().isCreated()
163+
);
164+
}
165+
133166
@Test
134167
public void testNontypeRecord() throws Exception {
135168
PIDRecord r = new PIDRecord();

src/test/java/edu/kit/datamanager/pit/web/RestWithLocalPidSystemTest.java

+37-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.fasterxml.jackson.databind.ObjectMapper;
1313

1414
import org.junit.jupiter.api.BeforeEach;
15+
import org.junit.jupiter.api.DisplayName;
1516
import org.junit.jupiter.api.Test;
1617
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
1718
import org.springframework.beans.factory.annotation.Autowired;
@@ -21,13 +22,17 @@
2122
import org.springframework.http.MediaType;
2223
import org.springframework.web.context.WebApplicationContext;
2324

25+
import edu.kit.datamanager.pit.RecordTestHelper;
26+
import edu.kit.datamanager.pit.configuration.ApplicationProperties;
27+
import edu.kit.datamanager.pit.configuration.ApplicationProperties.ValidationStrategy;
2428
import edu.kit.datamanager.pit.domain.PIDRecord;
29+
import edu.kit.datamanager.pit.pidgeneration.PidSuffixGenerator;
2530
import edu.kit.datamanager.pit.pidlog.KnownPid;
2631
import edu.kit.datamanager.pit.pidlog.KnownPidsDao;
2732
import edu.kit.datamanager.pit.pidsystem.impl.HandleProtocolAdapter;
2833
import edu.kit.datamanager.pit.pidsystem.impl.InMemoryIdentifierSystem;
2934
import edu.kit.datamanager.pit.pidsystem.impl.local.LocalPidSystem;
30-
import edu.kit.datamanager.pit.web.impl.TypingRESTResourceImpl;
35+
import edu.kit.datamanager.pit.pitservice.ITypingService;
3136

3237
// org.springframework.mock is for unit testing
3338
// Source: https://docs.spring.io/spring-framework/docs/current/reference/html/testing.html
@@ -75,6 +80,15 @@ public class RestWithLocalPidSystemTest {
7580
@Autowired
7681
private WebApplicationContext webApplicationContext;
7782

83+
@Autowired
84+
private PidSuffixGenerator pidGenerator;
85+
86+
@Autowired
87+
ITypingService typingService;
88+
89+
@Autowired
90+
ApplicationProperties appProps;
91+
7892
private MockMvc mockMvc;
7993

8094
private ObjectMapper mapper;
@@ -91,6 +105,7 @@ public void setup() throws Exception {
91105
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
92106
this.mapper = this.webApplicationContext.getBean("OBJECT_MAPPER_BEAN", ObjectMapper.class);
93107
this.knownPidsDao.deleteAll();
108+
this.appProps.setValidationStrategy(ValidationStrategy.EMBEDDED_STRICT);
94109
}
95110

96111
@Test
@@ -163,6 +178,27 @@ public void testCreateValidRecord() throws Exception {
163178
kp.getCreated().isBefore(kp.getModified());
164179
}
165180

181+
@Test
182+
@DisplayName("Testing PID Records with usual/larger size, with the Local PID system (in-memory db).")
183+
public void testExtensiveRecord() throws Exception {
184+
// create mockup of a large record. It contains non-registered PIDs and can not be validated.
185+
this.appProps.setValidationStrategy(ValidationStrategy.NONE_DEBUG);
186+
// as we use an in-memory db for testing, lets not make it too large.
187+
int numAttributes = 100;
188+
int numValues = 100;
189+
assertTrue(numAttributes * numValues > 256);
190+
PIDRecord r = RecordTestHelper.getFakePidRecord(numAttributes, numValues, "sandboxed/", pidGenerator);
191+
192+
String rJson = ApiMockUtils.serialize(r);
193+
ApiMockUtils.registerRecord(
194+
this.mockMvc,
195+
rJson,
196+
MediaType.APPLICATION_JSON_VALUE,
197+
MediaType.APPLICATION_JSON_VALUE,
198+
MockMvcResultMatchers.status().isCreated()
199+
);
200+
}
201+
166202
@Test
167203
public void testUpdateRecord() throws Exception {
168204
PIDRecord record = this.createSomeRecord();

0 commit comments

Comments
 (0)