Routes call ensureSave first (mission functions assume an initialized save), map MissionError to 404/409 and non-integer path ids to 400. OpenAPI spec in code gains both paths + the Mission schema (12 -> 14 paths); openapi.test.ts covers the new shapes.
138 lines
7.1 KiB
TypeScript
138 lines
7.1 KiB
TypeScript
import pkg from '@/package.json';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { openApiSpec } from './openapi';
|
|
|
|
describe('OpenAPI spec', () => {
|
|
it('documents every public endpoint', () => {
|
|
expect(Object.keys(openApiSpec.paths).sort()).toEqual([
|
|
'/api/crew',
|
|
'/api/explore',
|
|
'/api/jump',
|
|
'/api/missions',
|
|
'/api/missions/{id}/accept',
|
|
'/api/research',
|
|
'/api/ship',
|
|
'/api/ship/build',
|
|
'/api/ship/expand',
|
|
'/api/ship/upgrade',
|
|
'/api/system',
|
|
'/api/tech',
|
|
'/api/version',
|
|
'/api/world',
|
|
]);
|
|
});
|
|
|
|
it('describes the ship endpoints as ShipState in and out, with error responses on the actions', () => {
|
|
const ship = openApiSpec.paths['/api/ship'].get.responses[200].content['application/json'].schema;
|
|
expect(ship.$ref).toBe('#/components/schemas/ShipState');
|
|
|
|
const system = openApiSpec.components.schemas.ShipSystem.properties;
|
|
expect(Object.keys(system)).toEqual(['id', 'type', 'level', 'capacity', 'built']);
|
|
|
|
for (const [path, request] of [
|
|
['/api/ship/upgrade', '#/components/schemas/UpgradeRequest'],
|
|
['/api/ship/expand', '#/components/schemas/ExpandRequest'],
|
|
['/api/ship/build', '#/components/schemas/BuildRequest'],
|
|
] as const) {
|
|
const post = openApiSpec.paths[path].post;
|
|
expect(post.requestBody.content['application/json'].schema.$ref).toBe(request);
|
|
expect(post.responses[200].content['application/json'].schema.$ref).toBe('#/components/schemas/ShipState');
|
|
for (const status of ['400', '404', '409', '422'] as const) {
|
|
expect(post.responses[status].content['application/json'].schema.$ref).toBe('#/components/schemas/Error');
|
|
}
|
|
}
|
|
});
|
|
|
|
it('describes the tech endpoints: GET /api/tech → Tech[], POST /api/research with error responses', () => {
|
|
const tech = openApiSpec.paths['/api/tech'].get.responses[200].content['application/json'].schema;
|
|
expect(tech.type).toBe('array');
|
|
expect((tech.items as { $ref: string }).$ref).toBe('#/components/schemas/Tech');
|
|
|
|
const techProps = openApiSpec.components.schemas.Tech.properties;
|
|
expect(Object.keys(techProps)).toEqual(['id', 'slug', 'name', 'costKind', 'costQty', 'prereqs', 'effect', 'state', 'missingPrereqs']);
|
|
expect((techProps.state as { enum: string[] }).enum).toEqual(['RESEARCHED', 'AVAILABLE', 'LOCKED']);
|
|
|
|
const post = openApiSpec.paths['/api/research'].post;
|
|
expect(post.requestBody.content['application/json'].schema.$ref).toBe('#/components/schemas/ResearchRequest');
|
|
expect(post.responses[200].content['application/json'].schema.items.$ref).toBe('#/components/schemas/Tech');
|
|
for (const status of ['400', '404', '409', '422'] as const) {
|
|
expect(post.responses[status].content['application/json'].schema.$ref).toBe('#/components/schemas/Error');
|
|
}
|
|
});
|
|
|
|
it('describes the mission endpoints: GET /api/missions → Mission[], POST accept with error responses', () => {
|
|
const missions = openApiSpec.paths['/api/missions'].get.responses[200].content['application/json'].schema;
|
|
expect(missions.type).toBe('array');
|
|
expect((missions.items as { $ref: string }).$ref).toBe('#/components/schemas/Mission');
|
|
|
|
const props = openApiSpec.components.schemas.Mission.properties;
|
|
expect(Object.keys(props)).toEqual(['id', 'type', 'description', 'targetSystemId', 'conditionKind', 'progressTarget', 'progress', 'rewardKind', 'rewardQty', 'state']);
|
|
expect((props.type as { enum: string[] }).enum).toEqual(['DELIVER', 'EXPLORE', 'FIND']);
|
|
expect((props.state as { enum: string[] }).enum).toEqual(['AVAILABLE', 'ACTIVE', 'DONE']);
|
|
|
|
const post = openApiSpec.paths['/api/missions/{id}/accept'].post;
|
|
expect(post.responses[200].content['application/json'].schema.items.$ref).toBe('#/components/schemas/Mission');
|
|
for (const status of ['400', '404', '409'] as const) {
|
|
expect(post.responses[status].content['application/json'].schema.$ref).toBe('#/components/schemas/Error');
|
|
}
|
|
});
|
|
|
|
it('describes /api/crew as an array of CrewMember and crewFound on ExploreResult', () => {
|
|
const crew = openApiSpec.paths['/api/crew'].get.responses[200].content['application/json'].schema;
|
|
expect(crew.type).toBe('array');
|
|
expect((crew.items as { $ref: string }).$ref).toBe('#/components/schemas/CrewMember');
|
|
|
|
const member = openApiSpec.components.schemas.CrewMember.properties;
|
|
expect(Object.keys(member)).toEqual(['id', 'name', 'role', 'trait_good', 'trait_bad', 'origin']);
|
|
|
|
const exploreResult = openApiSpec.components.schemas.ExploreResult as { allOf: unknown[] };
|
|
const extra = (exploreResult.allOf[1] as { properties: Record<string, unknown> }).properties;
|
|
expect(Object.keys(extra)).toEqual(['outcome', 'crewFound']);
|
|
});
|
|
|
|
it('describes the system endpoints as SystemState in and out', () => {
|
|
const system = openApiSpec.paths['/api/system'].get.responses[200].content['application/json'].schema;
|
|
expect(system.$ref).toBe('#/components/schemas/SystemState');
|
|
|
|
const explore = openApiSpec.paths['/api/explore'].post;
|
|
expect(explore.requestBody.content['application/json'].schema.$ref).toBe('#/components/schemas/ExploreRequest');
|
|
expect(explore.responses[200].content['application/json'].schema.$ref).toBe('#/components/schemas/ExploreResult');
|
|
// ExploreResult extends SystemState with the scan outcome.
|
|
const exploreResult = openApiSpec.components.schemas.ExploreResult as { allOf: { $ref?: string }[] };
|
|
expect(exploreResult.allOf[0].$ref).toBe('#/components/schemas/SystemState');
|
|
for (const status of ['400', '404', '409'] as const) {
|
|
expect(explore.responses[status].content['application/json'].schema.$ref).toBe('#/components/schemas/Error');
|
|
}
|
|
});
|
|
|
|
it('describes the world endpoints as WorldState in and out', () => {
|
|
const world = openApiSpec.paths['/api/world'].get.responses[200].content['application/json'].schema;
|
|
expect(world.$ref).toBe('#/components/schemas/WorldState');
|
|
|
|
const jump = openApiSpec.paths['/api/jump'].post;
|
|
expect(jump.requestBody.content['application/json'].schema.$ref).toBe('#/components/schemas/JumpRequest');
|
|
expect(jump.responses[200].content['application/json'].schema.$ref).toBe('#/components/schemas/WorldState');
|
|
for (const status of ['400', '404', '409'] as const) {
|
|
expect(jump.responses[status].content['application/json'].schema.$ref).toBe('#/components/schemas/Error');
|
|
}
|
|
});
|
|
|
|
it('keeps info.version in sync with package.json', () => {
|
|
expect(openApiSpec.info.title).toBe('SpaceWanderer 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/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 appVersion = openApiSpec.components.schemas.AppVersion.properties;
|
|
expect(Object.keys(appVersion)).toEqual(['version']);
|
|
expect(appVersion.version.type).toBe('string');
|
|
});
|
|
});
|