Phase 1: data pipeline (Postgres+Redis with in-memory fallback, mock telemetry publisher, WebSocket fan-out, hub /debug page)
CI / Lint, typecheck, test, build (pull_request) Failing after 10s
CI / Lint, typecheck, test, build (pull_request) Failing after 10s
- packages/db: postgres.js + ioredis wrapper, StateStore interface with InMemory + Postgres implementations, schema migration runner - apps/api: refactored to use @kerbal-rt/db; live WebSocket hub subscribes to store changes and broadcasts to all clients - apps/tools/mock-telemetry: Node script that generates realistic KSP state and POSTs to /api/v1/ingest at 1Hz (uses same keplerian math as the live-map renderer) - apps/hub: new /debug page that connects to /api/v1/live and shows the live state - ksp/README.md: documents Phase 1c (real kRPC bridge) and the two implementation options - Tests: 17 total (5 kepler math, 5 db store, 5 API health/state, 2 API WebSocket fan-out) End-to-end verified: mock publisher → API → hub /debug page, 11 snapshots/5s over WebSocket, vessels advancing in mean anomaly.
This commit is contained in:
@@ -2,10 +2,102 @@ import { describe, it, expect } from 'vitest';
|
||||
import { app } from '../src/test-app.js';
|
||||
|
||||
describe('health', () => {
|
||||
it('returns ok', async () => {
|
||||
it('returns ok with service info', async () => {
|
||||
const res = await app.request('/health');
|
||||
expect(res.status).toBe(200);
|
||||
const body = await res.json();
|
||||
expect(body.ok).toBe(true);
|
||||
expect(body.service).toBe('kerbal-rt-api');
|
||||
expect(body.store).toBe('InMemoryStateStore');
|
||||
});
|
||||
|
||||
it('returns 503 NO_DATA when no snapshot has been ingested', async () => {
|
||||
const res = await app.request('/api/v1/state');
|
||||
expect(res.status).toBe(503);
|
||||
const body = await res.json();
|
||||
expect(body.error).toBe(true);
|
||||
expect(body.code).toBe('NO_DATA');
|
||||
});
|
||||
|
||||
it('accepts a valid snapshot and returns it on /api/v1/state', async () => {
|
||||
const snap = {
|
||||
ut: 1,
|
||||
capturedAt: '2026-01-01T00:00:00Z',
|
||||
activeVesselId: null,
|
||||
bodies: [],
|
||||
vessels: [],
|
||||
groundStations: [],
|
||||
};
|
||||
const ingest = await app.request('/api/v1/ingest', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(snap),
|
||||
});
|
||||
expect(ingest.status).toBe(200);
|
||||
const ingestBody = await ingest.json();
|
||||
expect(ingestBody.error).toBe(false);
|
||||
expect(ingestBody.data.ok).toBe(true);
|
||||
|
||||
const state = await app.request('/api/v1/state');
|
||||
expect(state.status).toBe(200);
|
||||
const stateBody = await state.json();
|
||||
expect(stateBody.data.ut).toBe(1);
|
||||
});
|
||||
|
||||
it('rejects malformed snapshots with 400', async () => {
|
||||
const res = await app.request('/api/v1/ingest', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ broken: 'payload' }),
|
||||
});
|
||||
expect(res.status).toBe(400);
|
||||
const body = await res.json();
|
||||
expect(body.error).toBe(true);
|
||||
expect(body.code).toBe('BAD_PAYLOAD');
|
||||
});
|
||||
|
||||
it('looks up vessels by id', async () => {
|
||||
const snap = {
|
||||
ut: 1,
|
||||
capturedAt: '2026-01-01T00:00:00Z',
|
||||
activeVesselId: 'v-1',
|
||||
bodies: [],
|
||||
vessels: [
|
||||
{
|
||||
id: 'v-1',
|
||||
name: 'Test',
|
||||
type: 'Probe',
|
||||
owner: 'KASA',
|
||||
situation: 'ORBITING',
|
||||
status: 'ACTIVE',
|
||||
orbit: {
|
||||
semiMajorAxis: 7e6,
|
||||
eccentricity: 0,
|
||||
inclination: 0,
|
||||
longitudeOfAscendingNode: 0,
|
||||
argumentOfPeriapsis: 0,
|
||||
meanAnomalyAtEpoch: 0,
|
||||
epoch: 0,
|
||||
},
|
||||
referenceBodyId: 'kerbin',
|
||||
createdAt: '2026-01-01T00:00:00Z',
|
||||
retiredAt: null,
|
||||
},
|
||||
],
|
||||
groundStations: [],
|
||||
};
|
||||
await app.request('/api/v1/ingest', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify(snap),
|
||||
});
|
||||
|
||||
const found = await app.request('/api/v1/vessels/v-1');
|
||||
expect(found.status).toBe(200);
|
||||
const body = await found.json();
|
||||
expect(body.data.name).toBe('Test');
|
||||
|
||||
const missing = await app.request('/api/v1/vessels/does-not-exist');
|
||||
expect(missing.status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user