local-ai-platform/src/server.ts
Brandon Wolfgang 66dc2b2065 feat: implement Phase 1 API foundation and testing
- Configure the Node.js test runner with tsx
- Add endpoint tests for health, readiness, and echo request validation
- Cover input length boundaries, invalid message types, unknown routes, and unexpected errors
- Extract the Fastify app factory to support isolated testing without starting the server
- Add centralized error handling and consistent 404 responses
- Guard server startup to prevent listeners from opening when the module is imported
2026-10-21 21:32:40 -05:00

81 lines
1.6 KiB
TypeScript

import Fastify, { type FastifyError } from "fastify";
import { z } from "zod";
const echoSchema = z.object({
message: z.string().min(1).max(500),
});
export const buildApp = () => {
const app = Fastify({
logger: true,
});
app.setErrorHandler((error: FastifyError, request, reply) => {
request.log.error(error);
const candidateStatusCode = error.statusCode ?? 500;
const statusCode =
candidateStatusCode >= 400 && candidateStatusCode < 500
? candidateStatusCode
: 500;
return reply.code(statusCode).send({
error: statusCode === 500 ? "Internal Server Error" : "Request Error",
});
});
app.setNotFoundHandler((_request, reply) => {
return reply.code(404).send({
error: "Not Found",
});
});
app.get("/health", async () => ({
status: "ok",
}));
app.get("/ready", async () => ({
status: "ready",
dependencies: {
ollama: "not-integrated",
},
}));
app.post("/echo", async (request, reply) => {
const result = echoSchema.safeParse(request.body);
if (!result.success) {
return reply.code(400).send({
error: "Invalid request body",
});
}
return {
message: result.data.message,
};
});
app.get("/test-error", async () => {
throw new Error("Sensitive internal detail");
});
return app;
};
const start = async () => {
const app = buildApp();
try {
await app.listen({
host: "127.0.0.1",
port: 3000,
});
} catch (error) {
app.log.error(error);
process.exit(1);
}
};
if (import.meta.url === `file://${process.argv[1]}`) {
void start();
}