Skip to content

Commit 20b37e3

Browse files
committed
COH-32681: Integration tests: REST API
[git-p4: depot-paths = "//dev/coherence-ce/main/": change = 118718]
1 parent c5508a8 commit 20b37e3

File tree

6 files changed

+581
-7
lines changed

6 files changed

+581
-7
lines changed

prj/coherence-rag-parent/coherence-rag/src/main/java/com/oracle/coherence/rag/api/Store.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -777,11 +777,9 @@ public Response getDocumentChunks(@QueryParam("docId") String docId)
777777
return Response.status(Response.Status.NOT_FOUND).build();
778778
}
779779

780-
DocChunk[] aChunks = new DocChunk[mapChunks.size()];
781-
for (int i = 0; i < aChunks.length; i++)
782-
{
783-
aChunks[i] = new DocChunk(mapChunks.get(DocumentChunk.id(docId, i)));
784-
}
780+
DocChunk[] aChunks = mapChunks.values().stream()
781+
.map(DocChunk::new)
782+
.toArray(DocChunk[]::new);
785783

786784
return Response.ok(new DocChunks(docId, aChunks)).build();
787785
}
@@ -928,7 +926,7 @@ private CoherenceEmbeddingStore createEmbeddingStore()
928926
*
929927
* @return map of chunk IDs to document chunks
930928
*/
931-
private Map<DocumentChunk.Id, DocumentChunk> getChunks(String docId)
929+
Map<DocumentChunk.Id, DocumentChunk> getChunks(String docId)
932930
{
933931
return chunks.entrySet(Filters.equal(DOC_ID, docId)).stream()
934932
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
@@ -1387,7 +1385,12 @@ public void embedAll(EmbeddingModel model)
13871385
*/
13881386
private DocumentChunk.Id chunkId(DocumentChunk chunk)
13891387
{
1390-
return new DocumentChunk.Id((String) chunk.metadata().get("url"), Integer.parseInt((String) chunk.metadata().get("index")));
1388+
Object oIndex = chunk.metadata().get("index");
1389+
int nIndex = oIndex instanceof Integer
1390+
? (Integer) oIndex
1391+
: Integer.parseInt((String) oIndex);
1392+
1393+
return new DocumentChunk.Id((String) chunk.metadata().get("url"), nIndex);
13911394
}
13921395

13931396
/**
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates.
3+
*
4+
* Licensed under the Universal Permissive License v 1.0 as shown at
5+
* https://oss.oracle.com/licenses/upl.
6+
*/
7+
package com.oracle.coherence.rag.api;
8+
9+
import io.helidon.microprofile.testing.junit5.HelidonTest;
10+
import jakarta.inject.Inject;
11+
import jakarta.ws.rs.client.WebTarget;
12+
import jakarta.ws.rs.core.Response;
13+
import org.junit.jupiter.api.Test;
14+
15+
import static org.hamcrest.MatcherAssert.assertThat;
16+
import static org.hamcrest.Matchers.is;
17+
18+
@SuppressWarnings("CdiInjectionPointsInspection")
19+
@HelidonTest
20+
class ConfigIT
21+
{
22+
23+
@Inject
24+
WebTarget target;
25+
26+
@Test
27+
void shouldSetAndGetProperty()
28+
{
29+
String property = "it.example.property";
30+
31+
try (Response put = target.path("api/_config/" + property)
32+
.request()
33+
.put(jakarta.ws.rs.client.Entity.text("value-1")))
34+
{
35+
assertThat(put.getStatus(), is(200));
36+
}
37+
38+
Response get = target.path("api/_config/" + property)
39+
.request()
40+
.get();
41+
assertThat(get.getStatus(), is(200));
42+
String value = get.readEntity(String.class);
43+
assertThat(value, is("value-1"));
44+
}
45+
}
46+
47+
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates.
3+
*
4+
* Licensed under the Universal Permissive License v 1.0 as shown at
5+
* https://oss.oracle.com/licenses/upl.
6+
*/
7+
package com.oracle.coherence.rag.api;
8+
9+
import com.oracle.bedrock.options.Timeout;
10+
import com.oracle.bedrock.testsupport.deferred.Eventually;
11+
12+
import io.helidon.microprofile.testing.junit5.HelidonTest;
13+
14+
import jakarta.inject.Inject;
15+
import jakarta.ws.rs.client.Entity;
16+
import jakarta.ws.rs.client.WebTarget;
17+
import jakarta.ws.rs.core.MediaType;
18+
import jakarta.ws.rs.core.Response;
19+
20+
import java.io.File;
21+
import java.util.concurrent.TimeUnit;
22+
23+
import org.junit.jupiter.api.BeforeAll;
24+
import org.junit.jupiter.api.MethodOrderer;
25+
import org.junit.jupiter.api.Order;
26+
import org.junit.jupiter.api.Test;
27+
import org.junit.jupiter.api.TestInstance;
28+
import org.junit.jupiter.api.TestMethodOrder;
29+
30+
import static org.hamcrest.MatcherAssert.assertThat;
31+
import static org.hamcrest.Matchers.anyOf;
32+
import static org.hamcrest.Matchers.containsString;
33+
import static org.hamcrest.Matchers.is;
34+
35+
@SuppressWarnings({"NewClassNamingConvention", "CdiInjectionPointsInspection"})
36+
@HelidonTest
37+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
38+
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
39+
class KbIT
40+
{
41+
42+
@Inject
43+
WebTarget target;
44+
45+
private final String store = "it-store";
46+
47+
private String docUri;
48+
49+
@Inject
50+
Kb kb;
51+
52+
@BeforeAll
53+
static void setUp()
54+
{
55+
System.setProperty("coherence.cacheconfig", "coherence-rag-cache-config.xml");
56+
}
57+
58+
@Test
59+
@Order(1)
60+
void setUpStoreAndIngest() throws Exception
61+
{
62+
// configure store with local ONNX embedding model
63+
String cfg = "{\"embeddingModel\":\"-/all-MiniLM-L6-v2\",\"normalizeEmbeddings\":true,\"chunkSize\":512,\"chunkOverlap\":64}";
64+
try (Response putCfg = target.path("api/kb/config/" + store)
65+
.request()
66+
.put(Entity.entity(cfg, MediaType.APPLICATION_JSON_TYPE)))
67+
{
68+
assertThat(putCfg.getStatus(), is(204));
69+
}
70+
71+
// compute absolute file URI to test PDF
72+
File pdf = new File("src/test/resources/database-release-notes.pdf");
73+
assertThat("Test PDF must exist", pdf.exists(), is(true));
74+
docUri = pdf.toURI().toString();
75+
76+
// import document via docs endpoint
77+
String importBody = "[\"" + docUri + "\"]";
78+
try (Response imp = target.path("api/kb/" + store + "/docs")
79+
.request()
80+
.post(Entity.entity(importBody, MediaType.APPLICATION_JSON_TYPE)))
81+
{
82+
assertThat(imp.getStatus(), is(204));
83+
}
84+
85+
// wait until chunking and embedding complete
86+
Eventually.assertDeferred(() -> isDocIngested(docUri, 70), is(true), Timeout.after(5, TimeUnit.MINUTES));
87+
}
88+
89+
@Test
90+
@Order(2)
91+
void testStoreList()
92+
{
93+
Response list = target.path("api/kb").request().get();
94+
assertThat(list.getStatus(), is(200));
95+
assertThat(list.readEntity(String.class), containsString(store));
96+
}
97+
98+
@Test
99+
@Order(3)
100+
void testGetStoreConfig()
101+
{
102+
Response getCfg = target.path("api/kb/config/" + store).request(MediaType.APPLICATION_JSON_TYPE).get();
103+
assertThat(getCfg.getStatus(), is(200));
104+
assertThat(getCfg.readEntity(String.class), containsString("all-MiniLM-L6-v2"));
105+
}
106+
107+
@Test
108+
@Order(4)
109+
void testGetChunks()
110+
{
111+
Response chunks = target.path("api/kb/" + store + "/chunks")
112+
.queryParam("docId", docUri)
113+
.request(MediaType.APPLICATION_JSON_TYPE)
114+
.get();
115+
assertThat(chunks.getStatus(), is(200));
116+
assertThat(chunks.readEntity(String.class), containsString("chunks"));
117+
}
118+
119+
@Test
120+
@Order(5)
121+
void testCreateAndRemoveIndex()
122+
{
123+
String hnsw = "{\"type\":\"HNSW\",\"parameters\":{\"m\":16,\"efConstruction\":200,\"efSearch\":64}}";
124+
try (Response createIndex = target.path("api/kb/" + store + "/index")
125+
.request()
126+
.post(Entity.entity(hnsw, MediaType.APPLICATION_JSON_TYPE)))
127+
{
128+
assertThat(createIndex.getStatus(), is(202));
129+
}
130+
131+
try (Response delIdx = target.path("api/kb/" + store + "/index").request().delete())
132+
{
133+
assertThat(delIdx.getStatus(), anyOf(is(202), is(304)));
134+
}
135+
}
136+
137+
@Test
138+
@Order(6)
139+
void testVectorOnlySearch()
140+
{
141+
String body = "{\"query\":\"release notes\",\"maxResults\":5,\"minScore\":0.0}";
142+
try (Response res = target.path("api/kb/search")
143+
.request(MediaType.APPLICATION_JSON_TYPE)
144+
.post(Entity.entity(body, MediaType.APPLICATION_JSON_TYPE)))
145+
{
146+
assertThat(res.getStatus(), is(200));
147+
assertThat(res.readEntity(String.class), containsString("results"));
148+
}
149+
}
150+
151+
@Test
152+
@Order(7)
153+
void testHybridSearch()
154+
{
155+
String body = "{\"query\":\"release notes\",\"maxResults\":5,\"minScore\":0.0,\"fullTextWeight\":0.5}";
156+
try (Response res = target.path("api/kb/search")
157+
.request(MediaType.APPLICATION_JSON_TYPE)
158+
.post(Entity.entity(body, MediaType.APPLICATION_JSON_TYPE)))
159+
{
160+
assertThat(res.getStatus(), is(200));
161+
assertThat(res.readEntity(String.class), containsString("results"));
162+
}
163+
}
164+
165+
// ---- helpers ---------------------------------------------------------
166+
167+
private boolean isDocIngested(String docId, int cExpectedChunks)
168+
{
169+
var mapChunks = kb.store(store).getChunks(docId);
170+
return mapChunks != null && mapChunks.size() >= cExpectedChunks;
171+
}
172+
}
173+
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright (c) 2025, Oracle and/or its affiliates.
3+
*
4+
* Licensed under the Universal Permissive License v 1.0 as shown at
5+
* https://oss.oracle.com/licenses/upl.
6+
*/
7+
package com.oracle.coherence.rag.api;
8+
9+
import io.helidon.microprofile.testing.junit5.HelidonTest;
10+
import jakarta.inject.Inject;
11+
import jakarta.ws.rs.client.Entity;
12+
import jakarta.ws.rs.client.WebTarget;
13+
import jakarta.ws.rs.core.MediaType;
14+
import jakarta.ws.rs.core.Response;
15+
import org.junit.jupiter.api.BeforeAll;
16+
import org.junit.jupiter.api.Test;
17+
18+
import static org.hamcrest.MatcherAssert.assertThat;
19+
import static org.hamcrest.Matchers.allOf;
20+
import static org.hamcrest.Matchers.containsString;
21+
import static org.hamcrest.Matchers.is;
22+
23+
@SuppressWarnings("CdiInjectionPointsInspection")
24+
@HelidonTest
25+
class ModelsIT
26+
{
27+
28+
@Inject
29+
WebTarget target;
30+
31+
@BeforeAll
32+
static void setUp()
33+
{
34+
System.setProperty("coherence.cacheconfig", "coherence-rag-cache-config.xml");
35+
}
36+
37+
@Test
38+
void shouldListAndCRUDModelConfig()
39+
{
40+
String path = "api/models/chat/OpenAI/gpt-4o-mini";
41+
42+
// ensure clean state
43+
//noinspection resource
44+
target.path(path).request().delete();
45+
46+
// list (ok)
47+
Response list0 = target.path("api/models").request().get();
48+
assertThat(list0.getStatus(), is(200));
49+
50+
// upsert
51+
try (Response put = target.path(path)
52+
.request()
53+
.put(Entity.entity("{\"temperature\":0.5,\"maxTokens\":256}", MediaType.APPLICATION_JSON_TYPE)))
54+
{
55+
assertThat(put.getStatus(), is(204));
56+
}
57+
58+
// get
59+
Response get = target.path(path).request(MediaType.APPLICATION_JSON_TYPE).get();
60+
assertThat(get.getStatus(), is(200));
61+
String json = get.readEntity(String.class);
62+
assertThat(json, allOf(containsString("temperature"), containsString("0.5")));
63+
64+
// list shows key
65+
Response list = target.path("api/models").request().get();
66+
assertThat(list.getStatus(), is(200));
67+
String listJson = list.readEntity(String.class);
68+
assertThat(listJson, containsString("chat:OpenAI/gpt-4o-mini"));
69+
70+
// delete
71+
try (Response del = target.path(path).request().delete())
72+
{
73+
assertThat(del.getStatus(), is(204));
74+
}
75+
76+
// get returns 404
77+
Response missing = target.path(path).request().get();
78+
assertThat(missing.getStatus(), is(404));
79+
}
80+
}
81+
82+

0 commit comments

Comments
 (0)