126 lines
5.0 KiB
TypeScript
126 lines
5.0 KiB
TypeScript
import pkg from '@/package.json';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { openApiSpec } from './openapi';
|
|
|
|
describe('OpenAPI spec', () => {
|
|
it('documents exactly the four public endpoints', () => {
|
|
expect(Object.keys(openApiSpec.paths).sort()).toEqual([
|
|
'/api/airports',
|
|
'/api/sim',
|
|
'/api/state',
|
|
'/api/version',
|
|
]);
|
|
});
|
|
|
|
it('keeps info.version in sync with package.json', () => {
|
|
expect(openApiSpec.info.title).toBe('Flight Ops API');
|
|
expect(openApiSpec.info.version).toBe(pkg.version);
|
|
});
|
|
|
|
it('uses an empty server url so "try it" targets the current origin', () => {
|
|
expect(openApiSpec.servers).toEqual([{ url: '' }]);
|
|
});
|
|
|
|
it('describes /api/airports as a GeoJSON FeatureCollection of airport features', () => {
|
|
const schema = openApiSpec.paths['/api/airports'].get.responses[200].content['application/json'].schema;
|
|
expect(schema.$ref).toBe('#/components/schemas/AirportFeatureCollection');
|
|
|
|
const fc = openApiSpec.components.schemas.AirportFeatureCollection.properties;
|
|
expect(fc.features.items.$ref).toBe('#/components/schemas/AirportFeature');
|
|
|
|
const feature = openApiSpec.components.schemas.AirportFeature.properties;
|
|
expect(feature.geometry.$ref).toBe('#/components/schemas/PointGeometry');
|
|
expect(feature.properties.$ref).toBe('#/components/schemas/AirportProperties');
|
|
|
|
const props = openApiSpec.components.schemas.AirportProperties.properties;
|
|
expect(Object.keys(props).sort()).toEqual(['icao', 'lat', 'lon', 'name']);
|
|
expect(props.name.type).toBe('string');
|
|
expect(props.icao.type).toBe('string');
|
|
expect(props.lat.type).toBe('number');
|
|
expect(props.lon.type).toBe('number');
|
|
|
|
const coords = openApiSpec.components.schemas.PointGeometry.properties.coordinates;
|
|
expect(coords.items.type).toBe('number');
|
|
});
|
|
|
|
it('describes /api/version as { version } with a string version', () => {
|
|
const schema = openApiSpec.paths['/api/version'].get.responses[200].content['application/json'].schema;
|
|
expect(schema.$ref).toBe('#/components/schemas/AppVersion');
|
|
|
|
const props = openApiSpec.components.schemas.AppVersion.properties;
|
|
expect(Object.keys(props)).toEqual(['version']);
|
|
expect(props.version.type).toBe('string');
|
|
});
|
|
|
|
it('describes /api/state as a StateSnapshot with account, simState, planes and contracts', () => {
|
|
const schema = openApiSpec.paths['/api/state'].get.responses[200].content['application/json'].schema;
|
|
expect(schema.$ref).toBe('#/components/schemas/StateSnapshot');
|
|
|
|
const snap = openApiSpec.components.schemas.StateSnapshot.properties;
|
|
expect(Object.keys(snap).sort()).toEqual(['account', 'contracts', 'planes', 'simState']);
|
|
expect(snap.account.$ref).toBe('#/components/schemas/AccountState');
|
|
expect(snap.simState.$ref).toBe('#/components/schemas/SimState');
|
|
expect(snap.planes.items.$ref).toBe('#/components/schemas/PlaneState');
|
|
expect(snap.contracts.items.$ref).toBe('#/components/schemas/ContractState');
|
|
|
|
const plane = openApiSpec.components.schemas.PlaneState.properties;
|
|
expect(Object.keys(plane).sort()).toEqual([
|
|
'contractId',
|
|
'fuel',
|
|
'id',
|
|
'lat',
|
|
'locationIcao',
|
|
'locationName',
|
|
'lon',
|
|
'maxFuel',
|
|
'model',
|
|
'status',
|
|
]);
|
|
|
|
const contract = openApiSpec.components.schemas.ContractState.properties;
|
|
expect(Object.keys(contract).sort()).toEqual([
|
|
'cargoCount',
|
|
'cargoType',
|
|
'createdSimTime',
|
|
'deadlineSimTime',
|
|
'destIcao',
|
|
'destName',
|
|
'id',
|
|
'originIcao',
|
|
'originName',
|
|
'planeId',
|
|
'reward',
|
|
'status',
|
|
]);
|
|
|
|
const sim = openApiSpec.components.schemas.SimState.properties;
|
|
expect(Object.keys(sim).sort()).toEqual(['clockMs', 'paused', 'timeScale']);
|
|
|
|
const contractStatus = openApiSpec.components.schemas.ContractState.properties.status;
|
|
expect(contractStatus.enum.sort()).toEqual(['accepted', 'completed', 'expired', 'open']);
|
|
});
|
|
|
|
it('describes POST /api/sim as SimControlsRequest → SimState with a 400 error shape', () => {
|
|
const post = openApiSpec.paths['/api/sim'].post;
|
|
expect(post.operationId).toBe('setSimControls');
|
|
|
|
const requestSchema = post.requestBody.content['application/json'].schema;
|
|
expect(requestSchema.$ref).toBe('#/components/schemas/SimControlsRequest');
|
|
|
|
const ok = post.responses[200].content['application/json'].schema;
|
|
expect(ok.$ref).toBe('#/components/schemas/SimState');
|
|
const bad = post.responses[400].content['application/json'].schema;
|
|
expect(bad.$ref).toBe('#/components/schemas/Error');
|
|
|
|
const controls = openApiSpec.components.schemas.SimControlsRequest.properties;
|
|
expect(Object.keys(controls).sort()).toEqual(['paused', 'timeScale']);
|
|
expect(controls.timeScale.type).toBe('number');
|
|
expect(controls.timeScale.minimum).toBe(1);
|
|
expect(controls.timeScale.maximum).toBe(60);
|
|
expect(controls.paused.type).toBe('boolean');
|
|
|
|
const error = openApiSpec.components.schemas.Error.properties;
|
|
expect(Object.keys(error)).toEqual(['error']);
|
|
});
|
|
});
|