Skip to content

Commit 344779e

Browse files
committed
Add AbstractKVStoreIntegrationTest and PostgresImpl Test
1 parent 9bedb1e commit 344779e

File tree

7 files changed

+256
-9
lines changed

7 files changed

+256
-9
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 {

app/src/main/java/org/vss/KVStore.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,3 @@ public interface KVStore {
1010

1111
ListKeyVersionsResponse listKeyVersions(ListKeyVersionsRequest request);
1212
}
13-

app/src/main/java/org/vss/exception/ConflictException.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,3 @@ public ConflictException(String message) {
55
super(message);
66
}
77
}
8-

app/src/main/java/org/vss/impl/postgres/PostgresBackendImpl.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,4 +130,3 @@ public ListKeyVersionsResponse listKeyVersions(ListKeyVersionsRequest request) {
130130
throw new UnsupportedOperationException("Operation not implemented");
131131
}
132132
}
133-
Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
CREATE TABLE vss_db (
2-
store_id character varying(120) NOT NULL,
3-
key character varying(120) NOT NULL,
4-
value bytea NULL,
5-
version bigint NOT NULL,
2+
store_id character varying(120) NOT NULL CHECK (store_id <> ''),
3+
key character varying(120) NOT NULL,
4+
value bytea NULL,
5+
version bigint NOT NULL,
66
PRIMARY KEY (store_id, key)
7-
);
8-
7+
);
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
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 putMultiObjectShouldFailWhenSingleKeyVersionMismatched() {
71+
final List<KeyValue> keyValues = List.of(kv("k1", "k1v1", 0),
72+
kv("k2", "k2v1", 0));
73+
74+
assertDoesNotThrow(() -> putObjects(null, keyValues));
75+
76+
List<KeyValue> second_request = List.of(kv("k1", "k1v2", 0),
77+
kv("k2", "k2v2", 1));
78+
79+
assertThrows(ConflictException.class, () -> putObjects(null, second_request));
80+
81+
//Verify that values didn't change
82+
KeyValue response = getObject("k1");
83+
assertThat(response.getKey(), is("k1"));
84+
assertThat(response.getVersion(), is(1L));
85+
assertThat(response.getValue().toStringUtf8(), is("k1v1"));
86+
87+
response = getObject("k2");
88+
assertThat(response.getKey(), is("k2"));
89+
assertThat(response.getVersion(), is(1L));
90+
assertThat(response.getValue().toStringUtf8(), is("k2v1"));
91+
}
92+
93+
@Test
94+
void putShouldFailWhenGlobalVersionMismatched() {
95+
putObjects(0L, List.of(kv("k1", "k1v1", 0)));
96+
97+
// key-version correctly changed but global_version conflict.
98+
assertThrows(ConflictException.class, () -> putObjects(0L, List.of(kv("k1", "k1v2", 1))));
99+
100+
//Verify that values didn't change
101+
KeyValue response = getObject("k1");
102+
assertThat(response.getKey(), is("k1"));
103+
assertThat(response.getVersion(), is(1L));
104+
assertThat(response.getValue().toStringUtf8(), is("k1v1"));
105+
}
106+
107+
@Test
108+
void putShouldSucceedWhenNoGlobalVersionIsGiven() {
109+
assertDoesNotThrow(() -> putObjects(null, List.of(kv("k1", "k1v1", 0))));
110+
assertDoesNotThrow(() -> putObjects(null, List.of(kv("k1", "k1v2", 1))));
111+
112+
KeyValue response = getObject("k1");
113+
assertThat(response.getKey(), is("k1"));
114+
assertThat(response.getVersion(), is(2L));
115+
assertThat(response.getValue().toStringUtf8(), is("k1v2"));
116+
}
117+
118+
@Test
119+
void getShouldReturnEmptyResponseWhenKeyDoesNotExist() {
120+
KeyValue response = getObject("non_existent_key");
121+
122+
assertThat(response.getKey(), is("non_existent_key"));
123+
assertTrue(response.getValue().isEmpty());
124+
assertThat(response.getVersion(), is(0L));
125+
}
126+
127+
@Test
128+
void getShouldReturnCorrectValueWhenKeyExists() {
129+
130+
putObjects(0L, List.of(kv("k1", "k1v1", 0)));
131+
132+
KeyValue response = getObject("k1");
133+
assertThat(response.getKey(), is("k1"));
134+
assertThat(response.getVersion(), is(1L));
135+
assertThat(response.getValue().toStringUtf8(), is("k1v1"));
136+
137+
List<KeyValue> keyValues = List.of(kv("k1", "k1v2", 1),
138+
kv("k2", "k2v1", 0));
139+
putObjects(1L, keyValues);
140+
141+
response = getObject("k1");
142+
assertThat(response.getKey(), is("k1"));
143+
assertThat(response.getVersion(), is(2L));
144+
assertThat(response.getValue().toStringUtf8(), is("k1v2"));
145+
146+
response = getObject("k2");
147+
assertThat(response.getKey(), is("k2"));
148+
assertThat(response.getVersion(), is(1L));
149+
assertThat(response.getValue().toStringUtf8(), is("k2v1"));
150+
151+
keyValues = List.of(kv("k2", "k2v2", 1),
152+
kv("k3", "k3v1", 0));
153+
putObjects(2L, keyValues);
154+
155+
response = getObject("k2");
156+
assertThat(response.getKey(), is("k2"));
157+
assertThat(response.getVersion(), is(2L));
158+
assertThat(response.getValue().toStringUtf8(), is("k2v2"));
159+
160+
response = getObject("k3");
161+
assertThat(response.getKey(), is("k3"));
162+
assertThat(response.getVersion(), is(1L));
163+
assertThat(response.getValue().toStringUtf8(), is("k3v1"));
164+
}
165+
166+
private KeyValue getObject(String key) {
167+
GetObjectRequest getRequest = GetObjectRequest.newBuilder()
168+
.setStoreId(STORE_ID)
169+
.setKey(key)
170+
.build();
171+
return this.kvStore.get(getRequest).getValue();
172+
}
173+
174+
private void putObjects(Long globalVersion, List<KeyValue> keyValues) {
175+
PutObjectRequest.Builder putObjectRequestBuilder = PutObjectRequest.newBuilder()
176+
.setStoreId(STORE_ID)
177+
.addAllTransactionItems(keyValues);
178+
179+
if (Objects.nonNull(globalVersion)) {
180+
putObjectRequestBuilder.setGlobalVersion(globalVersion);
181+
}
182+
183+
this.kvStore.put(putObjectRequestBuilder.build());
184+
}
185+
186+
private KeyValue kv(String key, String value, int version) {
187+
return KeyValue.newBuilder().setKey(key).setVersion(version).setValue(
188+
ByteString.copyFrom(value.getBytes(
189+
StandardCharsets.UTF_8))).build();
190+
}
191+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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.AfterEach;
9+
import org.junit.jupiter.api.BeforeEach;
10+
import org.testcontainers.containers.PostgreSQLContainer;
11+
import org.testcontainers.junit.jupiter.Container;
12+
import org.testcontainers.junit.jupiter.Testcontainers;
13+
import org.vss.AbstractKVStoreIntegrationTest;
14+
15+
@Testcontainers
16+
public class PostgresBackendImplIntegrationTest extends AbstractKVStoreIntegrationTest {
17+
18+
private final String POSTGRES_TEST_CONTAINER_DOCKER_IMAGE = "postgres:15";
19+
20+
@Container
21+
private final PostgreSQLContainer postgreSQLContainer =
22+
new PostgreSQLContainer(POSTGRES_TEST_CONTAINER_DOCKER_IMAGE)
23+
.withDatabaseName("postgres")
24+
.withUsername("postgres")
25+
.withPassword("postgres");
26+
27+
private Connection connection;
28+
29+
@BeforeEach
30+
void initEach() throws Exception {
31+
32+
// This is required to get postgres driver in classpath before we attempt to fetch a connection
33+
Class.forName("org.postgresql.Driver");
34+
this.connection = DriverManager.getConnection(postgreSQLContainer.getJdbcUrl(),
35+
postgreSQLContainer.getUsername(), postgreSQLContainer.getPassword());
36+
DSLContext dslContext = DSL.using(connection, SQLDialect.POSTGRES);
37+
38+
this.kvStore = new PostgresBackendImpl(dslContext);
39+
40+
createTable(dslContext);
41+
}
42+
43+
@AfterEach
44+
void destroy() throws Exception {
45+
this.connection.close();
46+
}
47+
48+
private void createTable(DSLContext dslContext) {
49+
dslContext.execute("CREATE TABLE vss_db ("
50+
+ "store_id character varying(120) NOT NULL CHECK (store_id <> ''),"
51+
+ "key character varying(120) NOT NULL,"
52+
+ "value bytea NULL,"
53+
+ "version bigint NOT NULL,"
54+
+ "PRIMARY KEY (store_id, key)"
55+
+ ");");
56+
}
57+
}

0 commit comments

Comments
 (0)