// src/routes/system.routes.test.ts import { describe, it, expect, vi, beforeEach } from 'vitest'; import supertest from 'supertest'; import { createTestApp } from '../tests/utils/createTestApp'; // 1. Mock the Service Layer // This decouples the route test from the service's implementation details. vi.mock('../services/systemService', () => ({ systemService: { getPm2Status: vi.fn(), }, })); // 2. Mock Geocoding vi.mock('../services/geocodingService.server', () => ({ geocodingService: { geocodeAddress: vi.fn(), }, })); // 3. Mock Logger vi.mock('../services/logger.server', () => ({ logger: { info: vi.fn(), debug: vi.fn(), error: vi.fn(), warn: vi.fn(), child: vi.fn().mockReturnThis(), }, })); // Import the router AFTER all mocks are defined to ensure systemService picks up the mocked util.promisify import { systemService } from '../services/systemService'; import systemRouter from './system.routes'; import { geocodingService } from '../services/geocodingService.server'; describe('System Routes (/api/system)', () => { const app = createTestApp({ router: systemRouter, basePath: '/api/system' }); beforeEach(() => { vi.clearAllMocks(); }); describe('GET /pm2-status', () => { it('should return success: true when pm2 process is online', async () => { // Arrange: Simulate a successful `pm2 describe` output for an online process. vi.mocked(systemService.getPm2Status).mockResolvedValue({ success: true, message: 'Application is online and running under PM2.', }); // Act const response = await supertest(app).get('/api/system/pm2-status'); // Assert expect(response.status).toBe(200); expect(response.body).toEqual({ success: true, message: 'Application is online and running under PM2.', }); }); it('should return success: false when pm2 process is stopped or errored', async () => { vi.mocked(systemService.getPm2Status).mockResolvedValue({ success: false, message: 'Application process exists but is not online.', }); const response = await supertest(app).get('/api/system/pm2-status'); // Assert expect(response.status).toBe(200); expect(response.body.success).toBe(false); }); it('should return success: false when pm2 process does not exist', async () => { // Arrange: Simulate `pm2 describe` failing because the process isn't found. vi.mocked(systemService.getPm2Status).mockResolvedValue({ success: false, message: 'Application process is not running under PM2.', }); // Act const response = await supertest(app).get('/api/system/pm2-status'); // Assert expect(response.status).toBe(200); expect(response.body).toEqual({ success: false, message: 'Application process is not running under PM2.', }); }); it('should return 500 if pm2 command produces stderr output', async () => { // Arrange: Simulate a successful exit code but with content in stderr. const serviceError = new Error('PM2 command produced an error: A non-fatal warning occurred.'); vi.mocked(systemService.getPm2Status).mockRejectedValue(serviceError); const response = await supertest(app).get('/api/system/pm2-status'); expect(response.status).toBe(500); expect(response.body.message).toBe(serviceError.message); }); it('should return 500 on a generic exec error', async () => { const serviceError = new Error('System error'); vi.mocked(systemService.getPm2Status).mockRejectedValue(serviceError); // Act const response = await supertest(app).get('/api/system/pm2-status'); // Assert expect(response.status).toBe(500); expect(response.body.message).toBe('System error'); }); }); describe('POST /geocode', () => { it('should return geocoded coordinates for a valid address', async () => { // Arrange const mockCoordinates = { lat: 48.4284, lng: -123.3656 }; vi.mocked(geocodingService.geocodeAddress).mockResolvedValue(mockCoordinates); // Act const response = await supertest(app) .post('/api/system/geocode') .send({ address: 'Victoria, BC' }); // Assert expect(response.status).toBe(200); expect(response.body).toEqual(mockCoordinates); }); it('should return 404 if the address cannot be geocoded', async () => { vi.mocked(geocodingService.geocodeAddress).mockResolvedValue(null); const response = await supertest(app) .post('/api/system/geocode') .send({ address: 'Invalid Address' }); expect(response.status).toBe(404); expect(response.body.message).toBe('Could not geocode the provided address.'); }); it('should return 500 if the geocoding service throws an error', async () => { const geocodeError = new Error('Geocoding service unavailable'); vi.mocked(geocodingService.geocodeAddress).mockRejectedValue(geocodeError); const response = await supertest(app) .post('/api/system/geocode') .send({ address: 'Any Address' }); expect(response.status).toBe(500); }); it('should return 400 if the address is missing from the body', async () => { const response = await supertest(app) .post('/api/system/geocode') .send({ not_address: 'Victoria, BC' }); expect(response.status).toBe(400); // Zod validation error message can vary slightly depending on configuration or version expect(response.body.errors[0].message).toMatch(/An address string is required|Required/i); }); }); });