Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,6 @@
<version>5.9.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.7.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand All @@ -58,9 +52,6 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.5.0</version>
<configuration>
<argLine>@{argLine} -Dnet.bytebuddy.experimental=true</argLine>
</configuration>
</plugin>

<plugin>
Expand Down
70 changes: 70 additions & 0 deletions src/test/java/de/asedem/HttpTestServer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package de.asedem;

import com.sun.net.httpserver.HttpServer;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;

/**
* Minimal in-process HTTP server used to exercise the real {@link de.asedem.rest.Rest}
* client end-to-end without mocking it. Each test configures the response body/status
* and can inspect the captured request.
*/
public class HttpTestServer implements AutoCloseable {

private final HttpServer server;
private volatile Response response = new Response(200, "");
private final AtomicReference<String> lastMethod = new AtomicReference<>();
private final AtomicReference<String> lastPath = new AtomicReference<>();
private final AtomicReference<String> lastBody = new AtomicReference<>();

public HttpTestServer() throws IOException {
server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
server.createContext("/", exchange -> {
lastMethod.set(exchange.getRequestMethod());
lastPath.set(exchange.getRequestURI().getPath());
final String method = exchange.getRequestMethod();
if ("POST".equals(method) || "PUT".equals(method) || "DELETE".equals(method) || "PATCH".equals(method)) {
try (var stream = exchange.getRequestBody()) {
lastBody.set(new String(stream.readAllBytes(), StandardCharsets.UTF_8));
}
}
final byte[] payload = response.body().getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(response.status(), payload.length);
try (var stream = exchange.getResponseBody()) {
stream.write(payload);
}
});
server.start();
}

public int getPort() {
return server.getAddress().getPort();
}

public void setResponse(int status, String body) {
this.response = new Response(status, body);
}

public String getLastMethod() {
return lastMethod.get();
}

public String getLastPath() {
return lastPath.get();
}

public String getLastBody() {
return lastBody.get();
}

@Override
public void close() {
server.stop(0);
}

private record Response(int status, String body) {
}
}
23 changes: 23 additions & 0 deletions src/test/java/de/asedem/rest/HttpMethodeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package de.asedem.rest;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class HttpMethodeTest {

@Test
void testGetString() {
assertEquals("GET", HttpMethode.GET.get());
}

@Test
void testPostString() {
assertEquals("POST", HttpMethode.POST.get());
}

@Test
void testDeleteString() {
assertEquals("DELETE", HttpMethode.DELETE.get());
}
}
42 changes: 42 additions & 0 deletions src/test/java/de/asedem/rest/RestResponseTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package de.asedem.rest;

import com.fasterxml.jackson.core.JsonProcessingException;
import org.junit.jupiter.api.Test;

import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;

class RestResponseTest {

@Test
void testStatusCodeAndBody() {
final RestResponse response = new RestResponse(200, "hello");

assertEquals(200, response.getStatusCode());
assertEquals("hello", response.asValueString());
}

@Test
void testAsJavaObjectParsesJson() throws JsonProcessingException {
final RestResponse response = new RestResponse(200, "{\"value\":42}");

final Map<?, ?> map = response.asJavaObject(Map.class);

assertEquals(42, map.get("value"));
}

@Test
void testAsJavaObjectReturnsNullForNullBody() throws JsonProcessingException {
final RestResponse response = new RestResponse(200, null);

assertNull(response.asJavaObject(Map.class));
}

@Test
void testAsJavaObjectThrowsOnInvalidJson() {
final RestResponse response = new RestResponse(200, "not json");

assertThrows(JsonProcessingException.class, () -> response.asJavaObject(Map.class));
}
}
88 changes: 88 additions & 0 deletions src/test/java/de/asedem/rest/RestTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package de.asedem.rest;

import de.asedem.HttpTestServer;
import org.junit.jupiter.api.Test;

import java.net.URL;

import static org.junit.jupiter.api.Assertions.*;

class RestTest {

@Test
void testGetRequest() throws Exception {
try (HttpTestServer server = new HttpTestServer()) {
server.setResponse(200, "{\"ok\":true}");

final RestResponse response = Rest.requestSync(
new URL("http://127.0.0.1:" + server.getPort() + "/api/tags"), HttpMethode.GET);

assertEquals(200, response.getStatusCode());
assertEquals("{\"ok\":true}", response.asValueString());
assertEquals("GET", server.getLastMethod());
assertNull(server.getLastBody());
}
}

@Test
void testPostRequestSendsBody() throws Exception {
try (HttpTestServer server = new HttpTestServer()) {
server.setResponse(200, "{\"ok\":true}");

final RestResponse response = Rest.requestSync(
new URL("http://127.0.0.1:" + server.getPort() + "/api/generate"),
HttpMethode.POST, new GenerateBody("llama2", "hi"));

assertEquals(200, response.getStatusCode());
assertTrue(server.getLastBody().contains("\"model\":\"llama2\""));
}
}

@Test
void testDeleteRequest() throws Exception {
try (HttpTestServer server = new HttpTestServer()) {
server.setResponse(200, "");

final RestResponse response = Rest.requestSync(
new URL("http://127.0.0.1:" + server.getPort() + "/api/delete"),
HttpMethode.DELETE, new DeleteBody("llama2"));

assertEquals(200, response.getStatusCode());
assertEquals("DELETE", server.getLastMethod());
}
}

@Test
void testErrorStatusReturnsStatusCodeAndNoBody() throws Exception {
try (HttpTestServer server = new HttpTestServer()) {
server.setResponse(404, "not found");

final RestResponse response = Rest.requestSync(
new URL("http://127.0.0.1:" + server.getPort() + "/api/copy"),
HttpMethode.POST, new CopyBody("a", "b"));

assertEquals(404, response.getStatusCode());
assertNull(response.asValueString());
}
}

@Test
void testThrowsOnConnectionFailure() throws Exception {
try (HttpTestServer server = new HttpTestServer()) {
final int port = server.getPort();
server.close();

assertThrows(java.io.IOException.class, () -> Rest.requestSync(
new URL("http://127.0.0.1:" + port + "/api/tags"), HttpMethode.GET));
}
}

record GenerateBody(String model, String prompt) {
}

record DeleteBody(String name) {
}

record CopyBody(String source, String destination) {
}
}
68 changes: 31 additions & 37 deletions src/test/java/de/asedem/service/ChatServiceTest.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
package de.asedem.service;

import de.asedem.HttpTestServer;
import de.asedem.Ollama;
import de.asedem.exception.OllamaConnectionException;
import de.asedem.model.ChatRequest;
import de.asedem.model.ChatResponse;
import de.asedem.model.Message;
import de.asedem.rest.HttpMethode;
import de.asedem.rest.Rest;
import de.asedem.rest.RestResponse;
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;

import java.io.IOException;
import java.util.List;

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

@Test
void testMethodCall() {

final Ollama ollama = Ollama.initDefault();

try (MockedStatic<Rest> utilities = Mockito.mockStatic(Rest.class)) {
utilities.when(() -> Rest.requestSync(ollama.buildUrl("/api/chat"),
HttpMethode.POST, request, 10000, 30000))
.thenReturn(new RestResponse(200, """
{
"model": "llama3.2",
"created_at": "2023-12-12T14:13:43.416799Z",
"message": {
"role": "assistant",
"content": "Hello! How are you today?"
},
"done": true,
"total_duration": 5191566416,
"load_duration": 2154458,
"prompt_eval_count": 26,
"prompt_eval_duration": 383809000,
"eval_count": 298,
"eval_duration": 4799921000
}
"""));
void testMethodCall() throws Exception {
try (HttpTestServer server = new HttpTestServer()) {
server.setResponse(200, """
{
"model": "llama3.2",
"created_at": "2023-12-12T14:13:43.416799Z",
"message": {
"role": "assistant",
"content": "Hello! How are you today?"
},
"done": true,
"total_duration": 5191566416,
"load_duration": 2154458,
"prompt_eval_count": 26,
"prompt_eval_duration": 383809000,
"eval_count": 298,
"eval_duration": 4799921000
}
""");

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

assertEquals("llama3.2", response.model());
Expand All @@ -58,18 +49,21 @@ void testMethodCall() {
assertTrue(response.done());
assertEquals(5191566416L, response.totalDuration());
assertEquals(4799921000L, response.evalDuration());

assertEquals("POST", server.getLastMethod());
assertEquals("/api/chat", server.getLastPath());
assertTrue(server.getLastBody().contains("\"model\":\"llama3.2\""));
assertTrue(server.getLastBody().contains("\"stream\":false"));
}
}

@Test
void testException() {

final Ollama ollama = Ollama.initDefault();
void testExceptionOnConnectionFailure() throws Exception {
try (HttpTestServer server = new HttpTestServer()) {
final int port = server.getPort();
server.close();

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

assertThrows(OllamaConnectionException.class, () -> ollama.chat(request));
}
Expand Down
Loading
Loading