Skip to content

Commit be5f2d0

Browse files
committed
More realsitic tests
1 parent 417a176 commit be5f2d0

17 files changed

Lines changed: 558 additions & 426 deletions

‎pom.xml‎

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,6 @@
3838
<version>5.9.2</version>
3939
<scope>test</scope>
4040
</dependency>
41-
<dependency>
42-
<groupId>org.mockito</groupId>
43-
<artifactId>mockito-junit-jupiter</artifactId>
44-
<version>5.7.0</version>
45-
<scope>test</scope>
46-
</dependency>
4741
</dependencies>
4842

4943
<build>
@@ -58,9 +52,6 @@
5852
<groupId>org.apache.maven.plugins</groupId>
5953
<artifactId>maven-surefire-plugin</artifactId>
6054
<version>3.5.0</version>
61-
<configuration>
62-
<argLine>@{argLine} -Dnet.bytebuddy.experimental=true</argLine>
63-
</configuration>
6455
</plugin>
6556

6657
<plugin>
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package de.asedem;
2+
3+
import com.sun.net.httpserver.HttpServer;
4+
5+
import java.io.IOException;
6+
import java.net.InetSocketAddress;
7+
import java.nio.charset.StandardCharsets;
8+
import java.util.concurrent.atomic.AtomicReference;
9+
10+
/**
11+
* Minimal in-process HTTP server used to exercise the real {@link de.asedem.rest.Rest}
12+
* client end-to-end without mocking it. Each test configures the response body/status
13+
* and can inspect the captured request.
14+
*/
15+
public class HttpTestServer implements AutoCloseable {
16+
17+
private final HttpServer server;
18+
private volatile Response response = new Response(200, "");
19+
private final AtomicReference<String> lastMethod = new AtomicReference<>();
20+
private final AtomicReference<String> lastPath = new AtomicReference<>();
21+
private final AtomicReference<String> lastBody = new AtomicReference<>();
22+
23+
public HttpTestServer() throws IOException {
24+
server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
25+
server.createContext("/", exchange -> {
26+
lastMethod.set(exchange.getRequestMethod());
27+
lastPath.set(exchange.getRequestURI().getPath());
28+
final String method = exchange.getRequestMethod();
29+
if ("POST".equals(method) || "PUT".equals(method) || "DELETE".equals(method) || "PATCH".equals(method)) {
30+
try (var stream = exchange.getRequestBody()) {
31+
lastBody.set(new String(stream.readAllBytes(), StandardCharsets.UTF_8));
32+
}
33+
}
34+
final byte[] payload = response.body().getBytes(StandardCharsets.UTF_8);
35+
exchange.sendResponseHeaders(response.status(), payload.length);
36+
try (var stream = exchange.getResponseBody()) {
37+
stream.write(payload);
38+
}
39+
});
40+
server.start();
41+
}
42+
43+
public int getPort() {
44+
return server.getAddress().getPort();
45+
}
46+
47+
public void setResponse(int status, String body) {
48+
this.response = new Response(status, body);
49+
}
50+
51+
public String getLastMethod() {
52+
return lastMethod.get();
53+
}
54+
55+
public String getLastPath() {
56+
return lastPath.get();
57+
}
58+
59+
public String getLastBody() {
60+
return lastBody.get();
61+
}
62+
63+
@Override
64+
public void close() {
65+
server.stop(0);
66+
}
67+
68+
private record Response(int status, String body) {
69+
}
70+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package de.asedem.rest;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import static org.junit.jupiter.api.Assertions.assertEquals;
6+
7+
class HttpMethodeTest {
8+
9+
@Test
10+
void testGetString() {
11+
assertEquals("GET", HttpMethode.GET.get());
12+
}
13+
14+
@Test
15+
void testPostString() {
16+
assertEquals("POST", HttpMethode.POST.get());
17+
}
18+
19+
@Test
20+
void testDeleteString() {
21+
assertEquals("DELETE", HttpMethode.DELETE.get());
22+
}
23+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package de.asedem.rest;
2+
3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import org.junit.jupiter.api.Test;
5+
6+
import java.util.Map;
7+
8+
import static org.junit.jupiter.api.Assertions.*;
9+
10+
class RestResponseTest {
11+
12+
@Test
13+
void testStatusCodeAndBody() {
14+
final RestResponse response = new RestResponse(200, "hello");
15+
16+
assertEquals(200, response.getStatusCode());
17+
assertEquals("hello", response.asValueString());
18+
}
19+
20+
@Test
21+
void testAsJavaObjectParsesJson() throws JsonProcessingException {
22+
final RestResponse response = new RestResponse(200, "{\"value\":42}");
23+
24+
final Map<?, ?> map = response.asJavaObject(Map.class);
25+
26+
assertEquals(42, map.get("value"));
27+
}
28+
29+
@Test
30+
void testAsJavaObjectReturnsNullForNullBody() throws JsonProcessingException {
31+
final RestResponse response = new RestResponse(200, null);
32+
33+
assertNull(response.asJavaObject(Map.class));
34+
}
35+
36+
@Test
37+
void testAsJavaObjectThrowsOnInvalidJson() {
38+
final RestResponse response = new RestResponse(200, "not json");
39+
40+
assertThrows(JsonProcessingException.class, () -> response.asJavaObject(Map.class));
41+
}
42+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package de.asedem.rest;
2+
3+
import de.asedem.HttpTestServer;
4+
import org.junit.jupiter.api.Test;
5+
6+
import java.net.URL;
7+
8+
import static org.junit.jupiter.api.Assertions.*;
9+
10+
class RestTest {
11+
12+
@Test
13+
void testGetRequest() throws Exception {
14+
try (HttpTestServer server = new HttpTestServer()) {
15+
server.setResponse(200, "{\"ok\":true}");
16+
17+
final RestResponse response = Rest.requestSync(
18+
new URL("http://127.0.0.1:" + server.getPort() + "/api/tags"), HttpMethode.GET);
19+
20+
assertEquals(200, response.getStatusCode());
21+
assertEquals("{\"ok\":true}", response.asValueString());
22+
assertEquals("GET", server.getLastMethod());
23+
assertNull(server.getLastBody());
24+
}
25+
}
26+
27+
@Test
28+
void testPostRequestSendsBody() throws Exception {
29+
try (HttpTestServer server = new HttpTestServer()) {
30+
server.setResponse(200, "{\"ok\":true}");
31+
32+
final RestResponse response = Rest.requestSync(
33+
new URL("http://127.0.0.1:" + server.getPort() + "/api/generate"),
34+
HttpMethode.POST, new GenerateBody("llama2", "hi"));
35+
36+
assertEquals(200, response.getStatusCode());
37+
assertTrue(server.getLastBody().contains("\"model\":\"llama2\""));
38+
}
39+
}
40+
41+
@Test
42+
void testDeleteRequest() throws Exception {
43+
try (HttpTestServer server = new HttpTestServer()) {
44+
server.setResponse(200, "");
45+
46+
final RestResponse response = Rest.requestSync(
47+
new URL("http://127.0.0.1:" + server.getPort() + "/api/delete"),
48+
HttpMethode.DELETE, new DeleteBody("llama2"));
49+
50+
assertEquals(200, response.getStatusCode());
51+
assertEquals("DELETE", server.getLastMethod());
52+
}
53+
}
54+
55+
@Test
56+
void testErrorStatusReturnsStatusCodeAndNoBody() throws Exception {
57+
try (HttpTestServer server = new HttpTestServer()) {
58+
server.setResponse(404, "not found");
59+
60+
final RestResponse response = Rest.requestSync(
61+
new URL("http://127.0.0.1:" + server.getPort() + "/api/copy"),
62+
HttpMethode.POST, new CopyBody("a", "b"));
63+
64+
assertEquals(404, response.getStatusCode());
65+
assertNull(response.asValueString());
66+
}
67+
}
68+
69+
@Test
70+
void testThrowsOnConnectionFailure() throws Exception {
71+
try (HttpTestServer server = new HttpTestServer()) {
72+
final int port = server.getPort();
73+
server.close();
74+
75+
assertThrows(java.io.IOException.class, () -> Rest.requestSync(
76+
new URL("http://127.0.0.1:" + port + "/api/tags"), HttpMethode.GET));
77+
}
78+
}
79+
80+
record GenerateBody(String model, String prompt) {
81+
}
82+
83+
record DeleteBody(String name) {
84+
}
85+
86+
record CopyBody(String source, String destination) {
87+
}
88+
}

‎src/test/java/de/asedem/service/ChatServiceTest.java‎

Lines changed: 31 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
11
package de.asedem.service;
22

3+
import de.asedem.HttpTestServer;
34
import de.asedem.Ollama;
45
import de.asedem.exception.OllamaConnectionException;
56
import de.asedem.model.ChatRequest;
67
import de.asedem.model.ChatResponse;
78
import de.asedem.model.Message;
8-
import de.asedem.rest.HttpMethode;
9-
import de.asedem.rest.Rest;
10-
import de.asedem.rest.RestResponse;
119
import org.junit.jupiter.api.Test;
12-
import org.mockito.MockedStatic;
13-
import org.mockito.Mockito;
1410

15-
import java.io.IOException;
1611
import java.util.List;
1712

1813
import static org.junit.jupiter.api.Assertions.*;
@@ -25,31 +20,27 @@ class ChatServiceTest {
2520
);
2621

2722
@Test
28-
void testMethodCall() {
29-
30-
final Ollama ollama = Ollama.initDefault();
31-
32-
try (MockedStatic<Rest> utilities = Mockito.mockStatic(Rest.class)) {
33-
utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/chat"),
34-
HttpMethode.POST, request, 10000, 30000))
35-
.thenReturn(new RestResponse(200, """
36-
{
37-
"model": "llama3.2",
38-
"created_at": "2023-12-12T14:13:43.416799Z",
39-
"message": {
40-
"role": "assistant",
41-
"content": "Hello! How are you today?"
42-
},
43-
"done": true,
44-
"total_duration": 5191566416,
45-
"load_duration": 2154458,
46-
"prompt_eval_count": 26,
47-
"prompt_eval_duration": 383809000,
48-
"eval_count": 298,
49-
"eval_duration": 4799921000
50-
}
51-
"""));
23+
void testMethodCall() throws Exception {
24+
try (HttpTestServer server = new HttpTestServer()) {
25+
server.setResponse(200, """
26+
{
27+
"model": "llama3.2",
28+
"created_at": "2023-12-12T14:13:43.416799Z",
29+
"message": {
30+
"role": "assistant",
31+
"content": "Hello! How are you today?"
32+
},
33+
"done": true,
34+
"total_duration": 5191566416,
35+
"load_duration": 2154458,
36+
"prompt_eval_count": 26,
37+
"prompt_eval_duration": 383809000,
38+
"eval_count": 298,
39+
"eval_duration": 4799921000
40+
}
41+
""");
5242

43+
final Ollama ollama = Ollama.init("http://127.0.0.1", server.getPort());
5344
final ChatResponse response = ollama.chat(request);
5445

5546
assertEquals("llama3.2", response.model());
@@ -58,18 +49,21 @@ void testMethodCall() {
5849
assertTrue(response.done());
5950
assertEquals(5191566416L, response.totalDuration());
6051
assertEquals(4799921000L, response.evalDuration());
52+
53+
assertEquals("POST", server.getLastMethod());
54+
assertEquals("/api/chat", server.getLastPath());
55+
assertTrue(server.getLastBody().contains("\"model\":\"llama3.2\""));
56+
assertTrue(server.getLastBody().contains("\"stream\":false"));
6157
}
6258
}
6359

6460
@Test
65-
void testException() {
66-
67-
final Ollama ollama = Ollama.initDefault();
61+
void testExceptionOnConnectionFailure() throws Exception {
62+
try (HttpTestServer server = new HttpTestServer()) {
63+
final int port = server.getPort();
64+
server.close();
6865

69-
try (MockedStatic<Rest> utilities = Mockito.mockStatic(Rest.class)) {
70-
utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/chat"),
71-
HttpMethode.POST, request, 10000, 30000))
72-
.thenThrow(new IOException());
66+
final Ollama ollama = Ollama.init("http://127.0.0.1", port);
7367

7468
assertThrows(OllamaConnectionException.class, () -> ollama.chat(request));
7569
}

0 commit comments

Comments
 (0)