133 lines
5.6 KiB
TypeScript
133 lines
5.6 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
import { api } from './api'
|
|
|
|
const service = {
|
|
id: 'svc-1',
|
|
name: 'API',
|
|
kind: 'http' as const,
|
|
url: 'https://api.local',
|
|
host: null,
|
|
port: null,
|
|
depends_on: [],
|
|
created_at: '2026-01-01T00:00:00Z',
|
|
updated_at: '2026-01-01T00:00:00Z',
|
|
}
|
|
|
|
afterEach(() => {
|
|
vi.unstubAllGlobals()
|
|
})
|
|
|
|
describe('ServiceMap API client', () => {
|
|
it('creates services with the v1 JSON contract', async () => {
|
|
const fetchMock = vi.fn().mockResolvedValue(
|
|
new Response(JSON.stringify(service), {
|
|
status: 201,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
}),
|
|
)
|
|
vi.stubGlobal('fetch', fetchMock)
|
|
|
|
const result = await api.createService({
|
|
name: 'API',
|
|
kind: 'http',
|
|
url: 'https://api.local',
|
|
depends_on: [],
|
|
})
|
|
|
|
expect(result.name).toBe('API')
|
|
expect(fetchMock).toHaveBeenCalledWith(
|
|
'/api/v1/services',
|
|
expect.objectContaining({
|
|
method: 'POST',
|
|
body: JSON.stringify({
|
|
name: 'API',
|
|
kind: 'http',
|
|
url: 'https://api.local',
|
|
depends_on: [],
|
|
}),
|
|
}),
|
|
)
|
|
})
|
|
|
|
it('uses dedicated bulk probing and incident endpoints', async () => {
|
|
const fetchMock = vi.fn()
|
|
.mockResolvedValueOnce(new Response(JSON.stringify({ requested: 0, completed: 0, results: [] }), { status: 200 }))
|
|
.mockResolvedValueOnce(new Response(JSON.stringify({ generated_at: '2026-01-01T00:00:00Z', active: false, candidates: [], degraded_service_ids: [], down_service_ids: [] }), { status: 200 }))
|
|
vi.stubGlobal('fetch', fetchMock)
|
|
|
|
await api.probeAll()
|
|
await api.incidents()
|
|
|
|
expect(fetchMock.mock.calls[0][0]).toBe('/api/v1/probe-all')
|
|
expect(fetchMock.mock.calls[1][0]).toBe('/api/v1/incidents')
|
|
})
|
|
|
|
it('uses the safe synthetic demo session endpoints', async () => {
|
|
const demoState = {
|
|
active: true,
|
|
synthetic: true,
|
|
scenario_name: 'Northstar Platform',
|
|
service_count: 9,
|
|
root_cause_service_id: 'postgresql',
|
|
root_cause_service_name: 'PostgreSQL',
|
|
affected_service_ids: ['api', 'web'],
|
|
affected_service_names: ['API Gateway', 'Web Frontend'],
|
|
affected_count: 2,
|
|
}
|
|
const fetchMock = vi.fn()
|
|
.mockResolvedValueOnce(new Response(JSON.stringify(demoState), { status: 200 }))
|
|
.mockResolvedValueOnce(new Response(JSON.stringify({ ...demoState, created: 9, updated: 0, total: 9, restored: false }), { status: 200 }))
|
|
.mockResolvedValueOnce(new Response(JSON.stringify({ ...demoState, created: 9, updated: 0, total: 9, restored: false }), { status: 200 }))
|
|
.mockResolvedValueOnce(new Response(JSON.stringify({ active: false, synthetic: false, scenario_name: null, service_count: 0, root_cause_service_id: null, root_cause_service_name: null, affected_service_ids: [], affected_service_names: [], affected_count: 0, created: 0, updated: 0, total: 0, restored: true }), { status: 200 }))
|
|
vi.stubGlobal('fetch', fetchMock)
|
|
|
|
await api.demoState()
|
|
await api.loadDemo()
|
|
await api.resetDemo()
|
|
await api.exitDemo()
|
|
|
|
expect(fetchMock.mock.calls.map(([url]) => url)).toEqual([
|
|
'/api/v1/demo',
|
|
'/api/v1/demo',
|
|
'/api/v1/demo/reset',
|
|
'/api/v1/demo/exit',
|
|
])
|
|
expect(fetchMock.mock.calls[1][1]).toEqual(expect.objectContaining({ method: 'POST' }))
|
|
})
|
|
|
|
it('uses the production discovery and canonical edge contracts', async () => {
|
|
const run = { run: { id: 'run-1', status: 'completed', started_at: '2026-01-01T00:00:00Z', completed_at: '2026-01-01T00:01:00Z', source_counts: {}, diff: { new: 1, changed: 0, missing: 0, unchanged: 0, stale: 0, new_suggestions: 1 }, error: null }, candidates: [], suggestions: [], observations: [], appops_available: true, appops_mappings: 1, proxy_routes: 2 }
|
|
const fetchMock = vi.fn()
|
|
.mockResolvedValueOnce(new Response(JSON.stringify(run), { status: 200 }))
|
|
.mockResolvedValueOnce(new Response(JSON.stringify([]), { status: 200 }))
|
|
.mockResolvedValueOnce(new Response(JSON.stringify({ id: 'edge-1' }), { status: 201 }))
|
|
vi.stubGlobal('fetch', fetchMock)
|
|
|
|
await api.runDiscovery('unix:///var/run/docker.sock', 'http://appops.local:1216')
|
|
await api.discoverySuggestions('open')
|
|
await api.createEdge({ source_service_id: 'dependent', target_service_id: 'dependency', lock: true })
|
|
|
|
expect(fetchMock.mock.calls[0][0]).toBe('/api/v1/discovery/run')
|
|
expect(JSON.parse(fetchMock.mock.calls[0][1].body)).toEqual({ docker_host: 'unix:///var/run/docker.sock', appops_url: 'http://appops.local:1216', include_stopped: false })
|
|
expect(fetchMock.mock.calls[1][0]).toBe('/api/v1/discovery/suggestions?status=open')
|
|
expect(fetchMock.mock.calls[2][0]).toBe('/api/v1/topology/edges')
|
|
})
|
|
|
|
it('previews and applies reachability repair through the explicit safety endpoint', async () => {
|
|
const report = { run_id: 'run-1', dry_run: true, inspected: 2, changed: 1, disabled_unsafe: 1, enabled_published: 0, docker_health_observed: 0, unmonitored: 1, resolved_incidents: 0, changes: [] }
|
|
const fetchMock = vi.fn()
|
|
.mockResolvedValueOnce(new Response(JSON.stringify(report), { status: 200 }))
|
|
.mockResolvedValueOnce(new Response(JSON.stringify({ ...report, dry_run: false }), { status: 200 }))
|
|
vi.stubGlobal('fetch', fetchMock)
|
|
|
|
await api.repairDiscoveredProbes(true, 'run-1')
|
|
await api.repairDiscoveredProbes(false, 'run-1')
|
|
|
|
expect(fetchMock.mock.calls.map(([url]) => url)).toEqual([
|
|
'/api/v1/discovery/probes/repair?dry_run=true&run_id=run-1',
|
|
'/api/v1/discovery/probes/repair?dry_run=false&run_id=run-1',
|
|
])
|
|
})
|
|
})
|