88 lines
2.7 KiB
TypeScript
88 lines
2.7 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { parseTelemetry, computeDeviations, distHaversine, parsePrediction } from '$domain';
|
|
import type { TelemetryPoint, PredictionStage } from '$domain';
|
|
|
|
const points: TelemetryPoint[] = [
|
|
{ latitude: 62.0, longitude: 129.0, altitude: 0, datetime: '2026-01-01T00:00:00Z', payload: '{}' },
|
|
{
|
|
latitude: 62.05,
|
|
longitude: 129.1,
|
|
altitude: 15000,
|
|
datetime: '2026-01-01T00:30:00Z',
|
|
payload: '{}'
|
|
}
|
|
];
|
|
|
|
describe('parseTelemetry', () => {
|
|
it('builds a flight path and takes the first sample as launch', () => {
|
|
const t = parseTelemetry(points);
|
|
expect(t.flight_path).toEqual([
|
|
[62.0, 129.0, 0],
|
|
[62.05, 129.1, 15000]
|
|
]);
|
|
expect(t.launch.latlng.lat).toBe(62.0);
|
|
expect(t.datapoints).toHaveLength(2);
|
|
});
|
|
|
|
it('throws on an empty series', () => {
|
|
expect(() => parseTelemetry([])).toThrow();
|
|
});
|
|
});
|
|
|
|
describe('distHaversine', () => {
|
|
it('is zero for identical points and ~343 km London→Paris', () => {
|
|
expect(distHaversine({ lat: 55, lng: 37 }, { lat: 55, lng: 37 })).toBe(0);
|
|
const d = distHaversine({ lat: 51.5074, lng: -0.1278 }, { lat: 48.8566, lng: 2.3522 });
|
|
expect(d).toBeGreaterThan(340);
|
|
expect(d).toBeLessThan(346);
|
|
});
|
|
});
|
|
|
|
describe('computeDeviations', () => {
|
|
// Prediction runs 00:00 → 01:30 along the same corridor as the telemetry.
|
|
const stages: PredictionStage[] = [
|
|
{
|
|
stage: 'ascent',
|
|
trajectory: [
|
|
{ altitude: 0, datetime: '2026-01-01T00:00:00Z', latitude: 62.0, longitude: 129.0 },
|
|
{ altitude: 30000, datetime: '2026-01-01T01:00:00Z', latitude: 62.1, longitude: 129.2 }
|
|
]
|
|
},
|
|
{
|
|
stage: 'descent',
|
|
trajectory: [
|
|
{ altitude: 30000, datetime: '2026-01-01T01:00:00Z', latitude: 62.1, longitude: 129.2 },
|
|
{ altitude: 0, datetime: '2026-01-01T01:30:00Z', latitude: 62.05, longitude: 129.4 }
|
|
]
|
|
}
|
|
];
|
|
const prediction = parsePrediction(stages);
|
|
|
|
it('matches each sample to the closest prediction point', () => {
|
|
const devs = computeDeviations(points, prediction);
|
|
expect(devs).toHaveLength(2);
|
|
// First sample sits exactly on the predicted launch point.
|
|
expect(devs[0].horizontal).toBeCloseTo(0, 6);
|
|
expect(devs[0].vertical).toBe(0);
|
|
// Second sample is offset from the prediction, both laterally and in altitude.
|
|
expect(devs[1].horizontal).toBeGreaterThan(0);
|
|
expect(devs[1].altActual).toBe(15000);
|
|
});
|
|
|
|
it('skips samples outside the prediction window', () => {
|
|
const outside: TelemetryPoint[] = [
|
|
{
|
|
latitude: 62,
|
|
longitude: 129,
|
|
altitude: 0,
|
|
datetime: '2025-12-31T00:00:00Z', // a day early
|
|
payload: '{}'
|
|
}
|
|
];
|
|
expect(computeDeviations(outside, prediction)).toHaveLength(0);
|
|
});
|
|
|
|
it('returns nothing without telemetry', () => {
|
|
expect(computeDeviations([], prediction)).toEqual([]);
|
|
});
|
|
});
|