Skip to content

Commit 6c6415f

Browse files
committed
Add AbstractKVStoreIntegrationTest and PostgresImpl Test
1 parent b3d0746 commit 6c6415f

File tree

3 files changed

+217
-0
lines changed

3 files changed

+217
-0
lines changed

app/build.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ dependencies {
4444

4545
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
4646
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
47+
testImplementation "org.hamcrest:hamcrest-library:2.2"
48+
testImplementation "org.testcontainers:junit-jupiter:1.17.6"
49+
testImplementation "org.testcontainers:postgresql:1.17.6"
4750
}
4851

4952
test {
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package org.vss;
2+
3+
import com.google.protobuf.ByteString;
4+
import java.nio.charset.StandardCharsets;
5+
import java.util.List;
6+
import java.util.Objects;
7+
import org.junit.jupiter.api.Test;
8+
import org.vss.exception.ConflictException;
9+
10+
import static org.hamcrest.MatcherAssert.assertThat;
11+
import static org.hamcrest.Matchers.is;
12+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
13+
import static org.junit.jupiter.api.Assertions.assertThrows;
14+
import static org.junit.jupiter.api.Assertions.assertTrue;
15+
16+
public abstract class AbstractKVStoreIntegrationTest {
17+
18+
private final String STORE_ID = "storeId";
19+
20+
protected KVStore kvStore;
21+
22+
@Test
23+
void putShouldSucceedWhenSingleObjectPutOperation() {
24+
assertDoesNotThrow(() -> putObjects(0L, List.of(kv("k1", "k1v1", 0))));
25+
assertDoesNotThrow(() -> putObjects(1L, List.of(kv("k1", "k1v2", 1))));
26+
27+
KeyValue response = getObject("k1");
28+
assertThat(response.getKey(), is("k1"));
29+
assertThat(response.getVersion(), is(2L));
30+
assertThat(response.getValue().toStringUtf8(), is("k1v2"));
31+
}
32+
33+
@Test
34+
void putShouldSucceedWhenMultiObjectPutOperation() {
35+
final List<KeyValue> keyValues = List.of(kv("k1", "k1v1", 0),
36+
kv("k2", "k2v1", 0));
37+
38+
assertDoesNotThrow(() -> putObjects(0L, keyValues));
39+
40+
List<KeyValue> second_request = List.of(kv("k1", "k1v2", 1),
41+
kv("k2", "k2v2", 1));
42+
putObjects(1L, second_request);
43+
44+
KeyValue response = getObject("k1");
45+
assertThat(response.getKey(), is("k1"));
46+
assertThat(response.getVersion(), is(2L));
47+
assertThat(response.getValue().toStringUtf8(), is("k1v2"));
48+
49+
response = getObject("k2");
50+
assertThat(response.getKey(), is("k2"));
51+
assertThat(response.getVersion(), is(2L));
52+
assertThat(response.getValue().toStringUtf8(), is("k2v2"));
53+
}
54+
55+
@Test
56+
void putShouldFailWhenKeyVersionMismatched() {
57+
putObjects(0L, List.of(kv("k1", "k1v1", 0)));
58+
59+
// global_version correctly changed but key-version conflict.
60+
assertThrows(ConflictException.class, () -> putObjects(1L, List.of(kv("k1", "k1v2", 0))));
61+
62+
//Verify that values didn't change
63+
KeyValue response = getObject("k1");
64+
assertThat(response.getKey(), is("k1"));
65+
assertThat(response.getVersion(), is(1L));
66+
assertThat(response.getValue().toStringUtf8(), is("k1v1"));
67+
}
68+
69+
@Test
70+
void putShouldFailWhenGlobalVersionMismatched() {
71+
putObjects(0L, List.of(kv("k1", "k1v1", 0)));
72+
73+
// key-version correctly changed but global_version conflict.
74+
assertThrows(ConflictException.class, () -> putObjects(0L, List.of(kv("k1", "k1v2", 1))));
75+
76+
//Verify that values didn't change
77+
KeyValue response = getObject("k1");
78+
assertThat(response.getKey(), is("k1"));
79+
assertThat(response.getVersion(), is(1L));
80+
assertThat(response.getValue().toStringUtf8(), is("k1v1"));
81+
}
82+
83+
@Test
84+
void putShouldSucceedWhenNoGlobalVersionIsGiven() {
85+
assertDoesNotThrow(() -> putObjects(null, List.of(kv("k1", "k1v1", 0))));
86+
assertDoesNotThrow(() -> putObjects(null, List.of(kv("k1", "k1v2", 1))));
87+
88+
KeyValue response = getObject("k1");
89+
assertThat(response.getKey(), is("k1"));
90+
assertThat(response.getVersion(), is(2L));
91+
assertThat(response.getValue().toStringUtf8(), is("k1v2"));
92+
}
93+
94+
@Test
95+
void getShouldReturnEmptyResponseWhenKeyDoesNotExist() {
96+
KeyValue response = getObject("non_existent_key");
97+
98+
assertThat(response.getKey(), is("non_existent_key"));
99+
assertTrue(response.getValue().isEmpty());
100+
}
101+
102+
@Test
103+
void getShouldReturnCorrectValueWhenKeyExists() {
104+
105+
putObjects(0L, List.of(kv("k1", "k1v1", 0)));
106+
107+
KeyValue response = getObject("k1");
108+
assertThat(response.getKey(), is("k1"));
109+
assertThat(response.getVersion(), is(1L));
110+
assertThat(response.getValue().toStringUtf8(), is("k1v1"));
111+
112+
List<KeyValue> keyValues = List.of(kv("k1", "k1v2", 1),
113+
kv("k2", "k2v1", 0));
114+
putObjects(1L, keyValues);
115+
116+
response = getObject("k1");
117+
assertThat(response.getKey(), is("k1"));
118+
assertThat(response.getVersion(), is(2L));
119+
assertThat(response.getValue().toStringUtf8(), is("k1v2"));
120+
121+
response = getObject("k2");
122+
assertThat(response.getKey(), is("k2"));
123+
assertThat(response.getVersion(), is(1L));
124+
assertThat(response.getValue().toStringUtf8(), is("k2v1"));
125+
126+
keyValues = List.of(kv("k2", "k2v2", 1),
127+
kv("k3", "k3v1", 0));
128+
putObjects(2L, keyValues);
129+
130+
response = getObject("k2");
131+
assertThat(response.getKey(), is("k2"));
132+
assertThat(response.getVersion(), is(2L));
133+
assertThat(response.getValue().toStringUtf8(), is("k2v2"));
134+
135+
response = getObject("k3");
136+
assertThat(response.getKey(), is("k3"));
137+
assertThat(response.getVersion(), is(1L));
138+
assertThat(response.getValue().toStringUtf8(), is("k3v1"));
139+
}
140+
141+
private KeyValue getObject(String key) {
142+
GetObjectRequest getRequest = GetObjectRequest.newBuilder()
143+
.setStoreId(STORE_ID)
144+
.setKey(key)
145+
.build();
146+
return this.kvStore.get(getRequest).getValue();
147+
}
148+
149+
private void putObjects(Long globalVersion, List<KeyValue> keyValues) {
150+
PutObjectRequest.Builder putObjectRequestBuilder = PutObjectRequest.newBuilder()
151+
.setStoreId(STORE_ID)
152+
.addAllTransactionItems(keyValues);
153+
154+
if (Objects.nonNull(globalVersion)) {
155+
putObjectRequestBuilder.setGlobalVersion(globalVersion);
156+
}
157+
158+
this.kvStore.put(putObjectRequestBuilder.build());
159+
}
160+
161+
private KeyValue kv(String key, String value, int version) {
162+
return KeyValue.newBuilder().setKey(key).setVersion(version).setValue(
163+
ByteString.copyFrom(value.getBytes(
164+
StandardCharsets.UTF_8))).build();
165+
}
166+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package org.vss.impl.postgres;
2+
3+
import java.sql.Connection;
4+
import java.sql.DriverManager;
5+
import org.jooq.DSLContext;
6+
import org.jooq.SQLDialect;
7+
import org.jooq.impl.DSL;
8+
import org.junit.jupiter.api.BeforeEach;
9+
import org.testcontainers.containers.PostgreSQLContainer;
10+
import org.testcontainers.junit.jupiter.Container;
11+
import org.testcontainers.junit.jupiter.Testcontainers;
12+
import org.vss.AbstractKVStoreIntegrationTest;
13+
14+
@Testcontainers
15+
public class PostgresBackendImplIntegrationTest extends AbstractKVStoreIntegrationTest {
16+
17+
private final String POSTGRES_TEST_CONTAINER_DOCKER_IMAGE = "postgres:15";
18+
19+
@Container
20+
private final PostgreSQLContainer postgreSQLContainer =
21+
new PostgreSQLContainer(POSTGRES_TEST_CONTAINER_DOCKER_IMAGE)
22+
.withDatabaseName("postgres")
23+
.withUsername("postgres")
24+
.withPassword("postgres");
25+
26+
@BeforeEach
27+
public void initEach() throws Exception {
28+
29+
// This is required to get postgres driver in classpath before we attempt to fetch a connection
30+
Class.forName("org.postgresql.Driver");
31+
Connection conn = DriverManager.getConnection(postgreSQLContainer.getJdbcUrl(),
32+
postgreSQLContainer.getUsername(), postgreSQLContainer.getPassword());
33+
DSLContext dslContext = DSL.using(conn, SQLDialect.POSTGRES);
34+
35+
this.kvStore = new PostgresBackendImpl(dslContext);
36+
37+
createTable(dslContext);
38+
}
39+
40+
private void createTable(DSLContext dslContext) {
41+
dslContext.execute("CREATE TABLE vss_db ("
42+
+ "store_id character varying(120) NOT NULL CHECK (store_id <> ''),"
43+
+ "key character varying(120) NOT NULL,"
44+
+ "value bytea NULL,"
45+
+ "version bigint NOT NULL,"
46+
+ "PRIMARY KEY (store_id, key));");
47+
}
48+
}

0 commit comments

Comments
 (0)