Files
ServiceMap-Public/frontend/src/api.test.ts
T

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',
])
})
})