a457b9d96f
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.
107 lines
3.1 KiB
TypeScript
107 lines
3.1 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { InMemoryStateStore } from '../src/store.js';
|
|
import type { UniverseSnapshot } from '@kerbal-rt/shared-types';
|
|
|
|
function makeSnapshot(ut: number, vesselId = 'v-1'): UniverseSnapshot {
|
|
return {
|
|
ut,
|
|
capturedAt: new Date().toISOString(),
|
|
activeVesselId: vesselId,
|
|
bodies: [
|
|
{
|
|
id: 'kerbin',
|
|
name: 'Kerbin',
|
|
kind: 'planet',
|
|
parentId: 'kerbol',
|
|
radius: 600_000,
|
|
sphereOfInfluence: 84_159_286,
|
|
gravitationalParameter: 3.5316e12,
|
|
rotationPeriod: 21_600,
|
|
axialTilt: 0,
|
|
orbit: {
|
|
semiMajorAxis: 13_599_840_256,
|
|
eccentricity: 0,
|
|
inclination: 0,
|
|
longitudeOfAscendingNode: 0,
|
|
argumentOfPeriapsis: 0,
|
|
meanAnomalyAtEpoch: 0,
|
|
epoch: 0,
|
|
},
|
|
},
|
|
],
|
|
vessels: [
|
|
{
|
|
id: vesselId,
|
|
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: [],
|
|
};
|
|
}
|
|
|
|
describe('InMemoryStateStore', () => {
|
|
it('starts empty', async () => {
|
|
const store = new InMemoryStateStore();
|
|
expect(await store.latestSnapshot()).toBeNull();
|
|
expect(await store.listVessels()).toEqual([]);
|
|
});
|
|
|
|
it('stores and returns snapshots', async () => {
|
|
const store = new InMemoryStateStore();
|
|
await store.applySnapshot(makeSnapshot(100));
|
|
const snap = await store.latestSnapshot();
|
|
expect(snap).not.toBeNull();
|
|
expect(snap!.ut).toBe(100);
|
|
});
|
|
|
|
it('looks up vessels by id', async () => {
|
|
const store = new InMemoryStateStore();
|
|
await store.applySnapshot(makeSnapshot(1, 'kasa-1'));
|
|
const v = await store.getVessel('kasa-1');
|
|
expect(v?.name).toBe('Test');
|
|
expect(await store.getVessel('missing')).toBeNull();
|
|
});
|
|
|
|
it('overwrites on subsequent snapshots', async () => {
|
|
const store = new InMemoryStateStore();
|
|
await store.applySnapshot(makeSnapshot(1));
|
|
await store.applySnapshot(makeSnapshot(2));
|
|
expect((await store.latestSnapshot())!.ut).toBe(2);
|
|
});
|
|
|
|
it('notifies subscribers when a snapshot is applied', async () => {
|
|
const store = new InMemoryStateStore();
|
|
const seen: number[] = [];
|
|
const unsub = await store.subscribe((snap) => seen.push(snap.ut));
|
|
|
|
await store.applySnapshot(makeSnapshot(1));
|
|
// allow microtask to flush
|
|
await new Promise((r) => setTimeout(r, 10));
|
|
await store.applySnapshot(makeSnapshot(2));
|
|
await new Promise((r) => setTimeout(r, 10));
|
|
|
|
expect(seen).toEqual([1, 2]);
|
|
|
|
await unsub();
|
|
await store.applySnapshot(makeSnapshot(3));
|
|
await new Promise((r) => setTimeout(r, 10));
|
|
expect(seen).toEqual([1, 2]); // unsub'd
|
|
});
|
|
});
|