267e80e2ec
- Refactor server initialization to separate concerns and improve error handling. - Implement centralized environment validation using Zod. - Introduce database, Redis, and queue lifecycle management. - Add health check endpoints for liveness and readiness. - Enhance error handling middleware for better response structure. - Implement rate limiting for API endpoints. - Add request ID middleware for traceability. - Create Sequelize CLI configuration and baseline migration for schema management. - Establish CI workflow with Gitea for testing and syntax checks. - Document foundational changes and migration strategy in PHASE_0_FOUNDATION_STABILIZATION.md. - Add Docker Compose configuration for local development and testing. - Implement unit and integration tests for critical functionality.
78 lines
3.5 KiB
JavaScript
78 lines
3.5 KiB
JavaScript
const express = require("express");
|
|
const request = require("supertest");
|
|
|
|
const mockQueue = { add: jest.fn(), close: jest.fn(), on: jest.fn(), getJob: jest.fn() };
|
|
const mockCheckDatabase = jest.fn();
|
|
const mockCheckRedis = jest.fn();
|
|
jest.mock("../../app/queues/activity.queue", () => mockQueue);
|
|
jest.mock("../../app/queues/document.queue", () => mockQueue);
|
|
jest.mock("../../app/queues/log.queue", () => mockQueue);
|
|
jest.mock("../../app/config/redisClient", () => ({
|
|
status: "wait", connect: jest.fn(), ping: jest.fn(), get: jest.fn(), set: jest.fn(), del: jest.fn(), quit: jest.fn(),
|
|
}));
|
|
jest.mock("../../app/config/database.lifecycle", () => ({ checkDatabase: mockCheckDatabase }));
|
|
jest.mock("../../app/config/redis.lifecycle", () => ({ checkRedis: mockCheckRedis }));
|
|
jest.mock("../../app/config/bullBoard.config", () => {
|
|
const express = require("express");
|
|
return { bullBoardRouter: express.Router().get("/", (_req, res) => res.json({ ok: true })) };
|
|
});
|
|
|
|
const app = require("../../app");
|
|
const { AppError, errorHandler } = require("../../app/middleware/error.middleware");
|
|
|
|
describe("foundation HTTP behavior", () => {
|
|
beforeEach(() => {
|
|
mockCheckDatabase.mockResolvedValue(true);
|
|
mockCheckRedis.mockResolvedValue(true);
|
|
});
|
|
test("GET /health/live reports process liveness", async () => {
|
|
const response = await request(app).get("/health/live").expect(200);
|
|
expect(response.body).toMatchObject({ status: "ok", service: "zumri-api" });
|
|
expect(response.body.timestamp).toBeDefined();
|
|
expect(response.headers["x-request-id"]).toBeDefined();
|
|
});
|
|
|
|
test("legacy GET /health remains a liveness alias", async () => {
|
|
await request(app).get("/health").expect(200).expect(({ body }) => expect(body.status).toBe("ok"));
|
|
});
|
|
|
|
test("GET /health/ready checks database and Redis", async () => {
|
|
await request(app).get("/health/ready").expect(200).expect(({ body }) => {
|
|
expect(body).toEqual({ status: "ready", checks: { database: "ok", redis: "ok" } });
|
|
});
|
|
expect(mockCheckDatabase).toHaveBeenCalled();
|
|
expect(mockCheckRedis).toHaveBeenCalled();
|
|
});
|
|
|
|
test("GET /health/ready returns 503 when a dependency fails", async () => {
|
|
mockCheckDatabase.mockResolvedValueOnce(false);
|
|
await request(app).get("/health/ready").expect(503).expect(({ body }) => {
|
|
expect(body.status).toBe("not_ready");
|
|
expect(body.checks.database).toBe("error");
|
|
});
|
|
});
|
|
|
|
test("unknown routes use the centralized 404 response", async () => {
|
|
const response = await request(app).get("/does-not-exist").expect(404);
|
|
expect(response.body.error).toMatchObject({ code: "NOT_FOUND", message: "Route not found" });
|
|
});
|
|
|
|
test("global errors use the standard response and request ID", async () => {
|
|
const errorApp = express();
|
|
errorApp.use(require("../../app/middleware/requestId.middleware"));
|
|
errorApp.get("/error", (_req, _res, next) => next(new AppError(400, "TEST_ERROR", "Controlled failure")));
|
|
errorApp.use(errorHandler);
|
|
const response = await request(errorApp).get("/error").expect(400);
|
|
expect(response.body.error).toEqual({ code: "TEST_ERROR", message: "Controlled failure" });
|
|
expect(response.body.requestId).toBeDefined();
|
|
});
|
|
|
|
test("an authenticated route rejects missing credentials", async () => {
|
|
await request(app).get("/api/auth/me").expect(401).expect(({ body }) => expect(body.success).toBe(false));
|
|
});
|
|
|
|
test("Bull Board rejects unauthenticated requests", async () => {
|
|
await request(app).get("/admin/queues").expect(401);
|
|
});
|
|
});
|