- Add an Ollama client for Qwen3 8B generation with request timeouts and HTTP error handling - Implement POST /generate with request validation and sanitized failure responses - Update GET /ready to report Ollama availability and return HTTP 503 when unavailable - Add unit and integration tests for generation, dependency health, and failure handling - Document implemented API endpoints, setup instructions, scripts, and remaining planned capabilities in README
215 lines
5.1 KiB
TypeScript
215 lines
5.1 KiB
TypeScript
import assert from "node:assert";
|
|
import { after, describe, it } from "node:test";
|
|
|
|
import { buildApp } from "./server.js";
|
|
import { setServers } from "node:dns";
|
|
|
|
describe("API endpoints", () => {
|
|
const app = buildApp();
|
|
|
|
after(async () => {
|
|
await app.close();
|
|
});
|
|
|
|
it("GET /health returns 200 and status ok", async () => {
|
|
const response = await app.inject({
|
|
method: "GET",
|
|
url: "/health",
|
|
});
|
|
|
|
assert.equal(response.statusCode, 200);
|
|
assert.deepEqual(response.json(), {
|
|
status: "ok",
|
|
});
|
|
});
|
|
|
|
it("GET /ready returns 200 and dependency status", async () => {
|
|
const app = buildApp(undefined, async () => true);
|
|
|
|
try {
|
|
const response = await app.inject({
|
|
method: "GET",
|
|
url: "/ready",
|
|
});
|
|
|
|
assert.equal(response.statusCode, 200);
|
|
assert.deepEqual(response.json(), {
|
|
status: "ready",
|
|
dependencies: {
|
|
ollama: "available",
|
|
},
|
|
});
|
|
} finally {
|
|
await app.close();
|
|
}
|
|
});
|
|
|
|
it("GET /ready returns 503 when Ollama is unavailable", async () => {
|
|
const app = buildApp(undefined, async () => false);
|
|
|
|
try {
|
|
const response = await app.inject({
|
|
method: "GET",
|
|
url: "/ready",
|
|
});
|
|
|
|
assert.equal(response.statusCode, 503);
|
|
assert.deepEqual(response.json(), {
|
|
status: "not-ready",
|
|
dependencies: {
|
|
ollama: "unavailable",
|
|
},
|
|
});
|
|
} finally {
|
|
await app.close();
|
|
}
|
|
});
|
|
|
|
it("POST /echo returns the validated message", async () => {
|
|
const response = await app.inject({
|
|
method: "POST",
|
|
url: "/echo",
|
|
payload: {
|
|
message: "Hello, world!",
|
|
},
|
|
});
|
|
|
|
assert.equal(response.statusCode, 200);
|
|
assert.deepEqual(response.json(), {
|
|
message: "Hello, world!",
|
|
});
|
|
});
|
|
|
|
it("POST /echo accepts a 500-character message", async () => {
|
|
const message = "a".repeat(500);
|
|
|
|
const response = await app.inject({
|
|
method: "POST",
|
|
url: "/echo",
|
|
payload: { message },
|
|
});
|
|
|
|
assert.equal(response.statusCode, 200);
|
|
assert.deepEqual(response.json(), { message });
|
|
});
|
|
|
|
it("POST /generate returns generated text", async () => {
|
|
const app = buildApp(async (prompt) => {
|
|
assert.equal(prompt, "Explain a REST API.");
|
|
return "A REST API enables software systems to communicate.";
|
|
});
|
|
|
|
try {
|
|
const response = await app.inject({
|
|
method: "POST",
|
|
url: "/generate",
|
|
payload: {
|
|
message: "Explain a REST API.",
|
|
},
|
|
});
|
|
|
|
assert.equal(response.statusCode, 200);
|
|
assert.deepEqual(response.json(), {
|
|
response: "A REST API enables software systems to communicate.",
|
|
});
|
|
} finally {
|
|
await app.close();
|
|
}
|
|
});
|
|
|
|
it("POST /generate returns 503 when generation fails", async () => {
|
|
const app = buildApp(async () => {
|
|
throw new Error("Ollama is unavailable");
|
|
});
|
|
|
|
try {
|
|
const response = await app.inject({
|
|
method: "POST",
|
|
url: "/generate",
|
|
payload: {
|
|
message: "Explain a REST API.",
|
|
},
|
|
});
|
|
|
|
assert.equal(response.statusCode, 503);
|
|
assert.deepEqual(response.json(), {
|
|
error: "Generation service unavailable",
|
|
});
|
|
assert.equal(response.body.includes("Ollama is unavailable"), false);
|
|
} finally {
|
|
await app.close();
|
|
}
|
|
});
|
|
|
|
it("POST /echo rejects an empty message", async () => {
|
|
const response = await app.inject({
|
|
method: "POST",
|
|
url: "/echo",
|
|
payload: {
|
|
message: "",
|
|
},
|
|
});
|
|
|
|
assert.equal(response.statusCode, 400);
|
|
assert.deepEqual(response.json(), {
|
|
error: "Invalid request body",
|
|
});
|
|
});
|
|
|
|
it("POST /echo rejects a message longer than 500 characters", async () => {
|
|
const message = "a".repeat(501);
|
|
|
|
const response = await app.inject({
|
|
method: "POST",
|
|
url: "/echo",
|
|
payload: { message },
|
|
});
|
|
|
|
assert.equal(response.statusCode, 400);
|
|
assert.deepEqual(response.json(), {
|
|
error: "Invalid request body",
|
|
});
|
|
});
|
|
|
|
it("POST /echo rejects a non-string message", async () => {
|
|
const message = "a".repeat(501);
|
|
|
|
const response = await app.inject({
|
|
method: "POST",
|
|
url: "/echo",
|
|
payload: {
|
|
message: 123,
|
|
},
|
|
});
|
|
|
|
assert.equal(response.statusCode, 400);
|
|
assert.deepEqual(response.json(), {
|
|
error: "Invalid request body",
|
|
});
|
|
});
|
|
|
|
it("returns 404 for an unknown endpoint", async () => {
|
|
const response = await app.inject({
|
|
method: "GET",
|
|
url: "/does-not-exist",
|
|
});
|
|
|
|
assert.equal(response.statusCode, 404);
|
|
assert.deepEqual(response.json(), {
|
|
error: "Not Found",
|
|
});
|
|
});
|
|
|
|
it("returns a generic 500 response for unexpected error", async () => {
|
|
const response = await app.inject({
|
|
method: "GET",
|
|
url: "/test-error",
|
|
});
|
|
|
|
assert.equal(response.statusCode, 500);
|
|
assert.deepEqual(response.json(), {
|
|
error: "Internal Server Error",
|
|
});
|
|
assert.equal(response.body.includes("Sensitive internal detail"), false);
|
|
});
|
|
});
|