Files
dashcaddy/dashcaddy-api/__tests__/routes/discover.routes.test.js
T
Hermes d45dc8d3b7
CI / Test & Lint (push) Canceled after 0s
CI / Security audit (push) Canceled after 0s
[grade=B] DC-100: Service discovery — auto-detect running containers
GET /api/v1/discover scans running Docker containers, matches images
against 20 known patterns (Plex, Jellyfin, Sonarr, Radarr, qBittorrent,
Gitea, Nextcloud, Redis, Postgres, etc.), and returns suggested service
configs. Marks services already in the dashboard as 'existing'.

Returns: container ID, name, image, suggested type/name/port/protocol,
port mappings, labels, and existing flag. 5 tests, 1623 total pass.
2026-08-12 12:27:57 -07:00

137 lines
3.8 KiB
JavaScript

/**
* 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);
});
});