import { describe, it } from 'node:test'; import assert from 'node:assert/strict'; import { Pool } from 'pg'; import { createCategoryRouter } from './category.router.ts'; import { CategoryRepository } from './category.repository.ts'; import { Request, Response } from 'express'; // 1. Generate a mock factory for the Express Response object function createMockResponse() { const res: Partial = {}; const data: { statusCode: number; jsonPayload: any } = { statusCode: 200, jsonPayload: null }; res.status = function (code: number) { data.statusCode = code; return this as Response; }; res.json = function (payload: any) { data.jsonPayload = payload; return this as Response; }; return { mockRes: res as Response, data }; } // 2. Generate a mock factory for the repository layer function createMockRepository(behavior: 'success' | 'failure') { return { findAllDescendants: async (path: string) => { if (behavior === 'failure') throw new Error('Database connection failed'); // Return a valid single-root array format mimicking real ltree database outputs return [ { id: 1, name: 'Top', path: 'Top' }, { id: 2, name: 'Science', path: 'Top.Science' } ]; } } as unknown as CategoryRepository; } // Helper utility to safely extract the Express handler bypassing strict undefined types function getRouteHandler(router: any): Function { const routeLayer = router.stack.find((layer: any) => layer.route); if (!routeLayer || !routeLayer.route || !routeLayer.route.stack) { throw new Error('Could not find route handler in mock router stack'); } return routeLayer.route.stack[0].handle; // Target the primary callback handler array element } const pool = new Pool({ connectionString: process.env.DATABASE_URL || process.env.INTERNAL_PROD_DB_URL }); describe('Category Router Endpoints', () => { it('GET /tree{/:path} - should return 200 and structural JSON array data on valid requests', async () => { const mockRepo = createMockRepository('success'); const router = createCategoryRouter(mockRepo, pool); const mockReq = { params: { path: 'Top' } } as unknown as Request; const { mockRes, data } = createMockResponse(); const routeHandler = getRouteHandler(router); await routeHandler(mockReq, mockRes); assert.equal(data.statusCode, 200); assert.equal(data.jsonPayload.success, true); // Correct alignment: buildCategoryTree returns an ARRAY of root nodes assert.equal(Array.isArray(data.jsonPayload.data), true); assert.equal(data.jsonPayload.data[0].name, 'Top'); }); it('GET /tree{/:path} - should return 400 Bad Request if ltree path contains malformed formatting', async () => { const mockRepo = createMockRepository('success'); const router = createCategoryRouter(mockRepo, pool); const mockReq = { params: { path: 'Top.Bad Path!' } } as unknown as Request; const { mockRes, data } = createMockResponse(); const routeHandler = getRouteHandler(router); await routeHandler(mockReq, mockRes); assert.equal(data.statusCode, 400); assert.equal(data.jsonPayload.success, false); assert.match(data.jsonPayload.error, /Invalid path format/); }); it('GET /tree{/:path} - should handle runtime database crashes gracefully with a 500 error status', async () => { const mockRepo = createMockRepository('failure'); const router = createCategoryRouter(mockRepo); const mockReq = { params: { path: 'Top' } } as unknown as Request; const { mockRes, data } = createMockResponse(); // Prevent console.error from cluttering the test runner feedback during intentional failures const originalConsoleError = console.error; console.error = () => { }; const routeHandler = getRouteHandler(router); await routeHandler(mockReq, mockRes); // Restore original logger tracking console.error = originalConsoleError; assert.equal(data.statusCode, 500); assert.equal(data.jsonPayload.success, false); assert.equal(data.jsonPayload.error, 'Internal Server Error'); }); });