Adds GET /api/clouds/:cloud/diagnostics and a Diagnostics page in the General nav. The report combines, in one call: - runtime reachability, latency, version and edition from /_floci/health - every service the runtime emulates, with the console pages backed by it - per-console-service probes (the existing status check) - EC2 instance containers from /_floci/diagnose - console uptime, sign-in mode and active session count Each source is optional, so a down runtime still yields a partial report.
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import {describe, expect, test} from 'bun:test'
|
||||
import type {CloudProvider, CloudServiceDescriptor, CloudStatus} from '../cloud-spi/types'
|
||||
import {runtimeDiagnostics} from './runtimeDiagnostics'
|
||||
|
||||
const STARTED_AT = new Date('2026-10-23T08:00:00.000Z')
|
||||
const NOW = new Date('2026-10-23T10:30:15.000Z')
|
||||
|
||||
function descriptor(service: CloudServiceDescriptor['service'], displayName: string, availability: CloudServiceDescriptor['availability'] = 'available'): CloudServiceDescriptor {
|
||||
return {cloud: 'aws', service, displayName, availability, route: `/cloud-explorer/aws/${service}`, iconKey: service, group: 'Compute', order: 1}
|
||||
}
|
||||
|
||||
function proxy(status: Partial<CloudStatus> = {}, descriptors: CloudServiceDescriptor[] = []) {
|
||||
return {
|
||||
status: async (cloud: CloudProvider) => ({
|
||||
cloud, adapterRegistered: true, runtime: 'reachable', endpoint: 'http://floci:4566', checkedAt: NOW.toISOString(), error: null,
|
||||
services: [{cloud, service: 'storage', adapterRegistered: true, runtime: 'reachable', endpoint: 'http://floci:4566', checkedAt: NOW.toISOString(), latencyMs: 4, error: null, errorCode: null}],
|
||||
...status,
|
||||
}) as CloudStatus,
|
||||
services: () => descriptors,
|
||||
}
|
||||
}
|
||||
|
||||
function runtime(responses: Record<string, unknown>): typeof fetch {
|
||||
return (async (input: string | URL | Request) => {
|
||||
const path = new URL(String(input instanceof Request ? input.url : input)).pathname
|
||||
return path in responses ? Response.json(responses[path]) : new Response('not found', {status: 404})
|
||||
}) as unknown as typeof fetch
|
||||
}
|
||||
|
||||
const fixed = {now: () => NOW, startedAt: STARTED_AT, authMode: 'oidc' as const, activeSessions: () => 2}
|
||||
|
||||
describe('runtimeDiagnostics', () => {
|
||||
test('reports the runtime version, every emulated service and its console pages', async () => {
|
||||
const report = await runtimeDiagnostics('aws', proxy({}, [
|
||||
descriptor('storage', 'S3'),
|
||||
descriptor('loadbalancing', 'Load Balancers'),
|
||||
descriptor('targetgroups', 'Target Groups'),
|
||||
descriptor('scheduler', 'Scheduler', 'coming_soon'),
|
||||
]), {
|
||||
...fixed,
|
||||
fetch: runtime({'/_floci/health': {version: '1.9.0', edition: 'compat', services: {s3: 'running', elasticloadbalancing: 'running', route53: 'running', scheduler: 'stopped'}}}),
|
||||
})
|
||||
|
||||
expect(report.runtime).toMatchObject({status: 'reachable', endpoint: 'http://floci:4566', version: '1.9.0', edition: 'compat', error: null})
|
||||
expect(report.runtime.latencyMs).toBeNumber()
|
||||
expect(report.emulatedServices).toEqual([
|
||||
{name: 'elasticloadbalancing', state: 'running', consolePages: [
|
||||
{service: 'loadbalancing', displayName: 'Load Balancers', route: '/cloud-explorer/aws/loadbalancing'},
|
||||
{service: 'targetgroups', displayName: 'Target Groups', route: '/cloud-explorer/aws/targetgroups'},
|
||||
]},
|
||||
{name: 'route53', state: 'running', consolePages: []},
|
||||
{name: 's3', state: 'running', consolePages: [{service: 'storage', displayName: 'S3', route: '/cloud-explorer/aws/storage'}]},
|
||||
{name: 'scheduler', state: 'stopped', consolePages: []},
|
||||
])
|
||||
expect(report.consoleServices).toHaveLength(1)
|
||||
})
|
||||
|
||||
test('lists the containers the runtime started for EC2 instances', async () => {
|
||||
const report = await runtimeDiagnostics('aws', proxy(), {
|
||||
...fixed,
|
||||
fetch: runtime({'/_floci/diagnose': {ec2: {instances: [
|
||||
{instanceId: 'i-0abc', containerStatus: 'running', sshHost: 'localhost', sshPort: 2201},
|
||||
{instanceId: 'i-0def', containerStatus: 'stopped'},
|
||||
{containerStatus: 'running'},
|
||||
]}}}),
|
||||
})
|
||||
|
||||
expect(report.containers).toEqual([
|
||||
{kind: 'ec2-instance', resourceId: 'i-0abc', status: 'running', sshHost: 'localhost', sshPort: 2201},
|
||||
{kind: 'ec2-instance', resourceId: 'i-0def', status: 'stopped', sshHost: null, sshPort: null},
|
||||
])
|
||||
})
|
||||
|
||||
test('still returns a partial report when the runtime is down', async () => {
|
||||
const report = await runtimeDiagnostics('aws', proxy({runtime: 'unavailable', error: 'Cannot reach Floci core at http://floci:4566', services: []}), {
|
||||
...fixed,
|
||||
fetch: (async () => { throw new TypeError('fetch failed') }) as unknown as typeof fetch,
|
||||
})
|
||||
|
||||
expect(report.runtime).toEqual({status: 'unavailable', endpoint: 'http://floci:4566', latencyMs: null, error: 'Cannot reach Floci core at http://floci:4566', version: null, edition: null})
|
||||
expect(report.emulatedServices).toBeNull()
|
||||
expect(report.containers).toBeNull()
|
||||
})
|
||||
|
||||
test('does not query Floci core endpoints for other clouds', async () => {
|
||||
const requested: string[] = []
|
||||
const report = await runtimeDiagnostics('azure', proxy(), {
|
||||
...fixed,
|
||||
fetch: (async (input: string | URL | Request) => { requested.push(String(input)); return Response.json({}) }) as unknown as typeof fetch,
|
||||
})
|
||||
|
||||
expect(requested).toEqual([])
|
||||
expect(report.emulatedServices).toBeNull()
|
||||
})
|
||||
|
||||
test('describes the console process itself', async () => {
|
||||
const report = await runtimeDiagnostics('aws', proxy(), {...fixed, fetch: runtime({})})
|
||||
|
||||
expect(report.checkedAt).toBe('2026-10-23T10:30:15.000Z')
|
||||
expect(report.console).toEqual({startedAt: '2026-10-23T08:00:00.000Z', uptimeSeconds: 9015, authMode: 'oidc', activeSessions: 2})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user