/** * DC-100: Service discovery tests */ const express = require('express'); const request = require('supertest'); function createApp(docker, servicesStateManager) { const app = express(); app.use(express.json()); const asyncHandler = (fn) => (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next); const discoverRoutes = require('../../routes/discover'); app.use('/api/v1', discoverRoutes({ docker, servicesStateManager, asyncHandler, })); return app; } describe('DC-100: Service Discovery', () => { it('returns 503 when Docker is not available', async () => { const app = createApp(null, null); const res = await request(app).get('/api/v1/discover'); expect(res.status).toBe(503); expect(res.body.success).toBe(false); expect(res.body.code).toBe('DC-CONT-011'); }); it('discovers running containers with pattern matching', async () => { const mockDocker = { client: { listContainers: jest.fn().mockResolvedValue([ { Id: 'abc123def456', Names: ['/plex-server'], Image: 'plexinc/pms-docker:latest', State: 'running', Ports: [ { IP: '0.0.0.0', PrivatePort: 32400, PublicPort: 32400, Type: 'tcp' }, ], Labels: {}, }, { Id: 'def789abc012', Names: ['/redis-cache'], Image: 'redis:7-alpine', State: 'running', Ports: [ { IP: '0.0.0.0', PrivatePort: 6379, PublicPort: 6379, Type: 'tcp' }, ], Labels: {}, }, ]), }, }; const mockStateManager = { read: jest.fn().mockResolvedValue([]), }; const app = createApp(mockDocker, mockStateManager); const res = await request(app).get('/api/v1/discover'); expect(res.status).toBe(200); expect(res.body.success).toBe(true); expect(res.body.total).toBe(2); expect(res.body.discovered).toHaveLength(2); const plex = res.body.discovered.find(d => d.name === 'plex-server'); expect(plex.suggested.type).toBe('plex'); expect(plex.suggested.name).toBe('Plex'); expect(plex.suggested.port).toBe(32400); expect(plex.existing).toBe(false); const redis = res.body.discovered.find(d => d.name === 'redis-cache'); expect(redis.suggested.type).toBe('redis'); }); it('marks already-added services as existing', async () => { const mockDocker = { client: { listContainers: jest.fn().mockResolvedValue([ { Id: 'abc123def456', Names: ['/plex-server'], Image: 'plexinc/pms-docker:latest', State: 'running', Ports: [], Labels: {}, }, ]), }, }; const mockStateManager = { read: jest.fn().mockResolvedValue([{ id: 'plex-server' }]), }; const app = createApp(mockDocker, mockStateManager); const res = await request(app).get('/api/v1/discover'); expect(res.status).toBe(200); expect(res.body.discovered[0].existing).toBe(true); }); it('handles empty container list', async () => { const mockDocker = { client: { listContainers: jest.fn().mockResolvedValue([]), }, }; const app = createApp(mockDocker, null); const res = await request(app).get('/api/v1/discover'); expect(res.status).toBe(200); expect(res.body.total).toBe(0); expect(res.body.discovered).toEqual([]); }); it('returns 500 on Docker error', async () => { const mockDocker = { client: { listContainers: jest.fn().mockRejectedValue(new Error('connection refused')), }, }; const app = createApp(mockDocker, null); const res = await request(app).get('/api/v1/discover'); expect(res.status).toBe(500); expect(res.body.success).toBe(false); }); });