feat(api): GET /api/missions + POST /api/missions/{id}/accept
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.
This commit is contained in:
@@ -8,6 +8,8 @@ describe('OpenAPI spec', () => {
|
||||
'/api/crew',
|
||||
'/api/explore',
|
||||
'/api/jump',
|
||||
'/api/missions',
|
||||
'/api/missions/{id}/accept',
|
||||
'/api/research',
|
||||
'/api/ship',
|
||||
'/api/ship/build',
|
||||
@@ -58,6 +60,23 @@ describe('OpenAPI spec', () => {
|
||||
}
|
||||
});
|
||||
|
||||
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');
|
||||
|
||||
@@ -454,6 +454,75 @@ export const openApiSpec = {
|
||||
},
|
||||
},
|
||||
},
|
||||
'/api/missions': {
|
||||
get: {
|
||||
summary: 'Mission board',
|
||||
description:
|
||||
"The save's mission contracts — generated deterministically from the seed, always topped up to at least three AVAILABLE. One mission is ACTIVE at a time; completion is checked after jumps and scans (DELIVER on arrival at its target system, EXPLORE after N scans, FIND when a haul contains its kind) and grants the reward into the cargo hold. Auto-initializes a new game when no save exists yet.",
|
||||
operationId: 'getMissions',
|
||||
responses: {
|
||||
200: {
|
||||
description: 'The mission board',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { type: 'array', items: { $ref: '#/components/schemas/Mission' } },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'/api/missions/{id}/accept': {
|
||||
post: {
|
||||
summary: 'Accept a mission',
|
||||
description:
|
||||
"Makes an AVAILABLE contract the single ACTIVE one (409 while another is active, or when it isn't AVAILABLE). Costs a turn and writes a mission event. Returns the fresh board.",
|
||||
operationId: 'acceptMission',
|
||||
parameters: [
|
||||
{
|
||||
name: 'id',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'integer' },
|
||||
description: 'missions.id of the contract to accept',
|
||||
},
|
||||
],
|
||||
responses: {
|
||||
200: {
|
||||
description: 'Fresh mission board after the acceptance',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { type: 'array', items: { $ref: '#/components/schemas/Mission' } },
|
||||
},
|
||||
},
|
||||
},
|
||||
400: {
|
||||
description: 'Non-integer id in the path',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/Error' },
|
||||
},
|
||||
},
|
||||
},
|
||||
404: {
|
||||
description: 'Unknown mission id',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/Error' },
|
||||
},
|
||||
},
|
||||
},
|
||||
409: {
|
||||
description: "Mission isn't AVAILABLE, or another mission is already ACTIVE (one at a time)",
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/Error' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
components: {
|
||||
schemas: {
|
||||
@@ -597,6 +666,22 @@ export const openApiSpec = {
|
||||
},
|
||||
required: ['techId'],
|
||||
},
|
||||
Mission: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
type: { type: 'string', enum: ['DELIVER', 'EXPLORE', 'FIND'] },
|
||||
description: { type: 'string', description: "Human-readable objective, e.g. 'Jump to Kepler Drift'" },
|
||||
targetSystemId: { type: ['integer', 'null'], nullable: true, description: 'DELIVER only — destination system (NULL otherwise)' },
|
||||
conditionKind: { type: ['string', 'null'], enum: ['resource', 'part', null], description: 'FIND only — item kind to haul in (NULL otherwise)' },
|
||||
progressTarget: { type: 'integer', description: 'EXPLORE: scans required; 1 for the others' },
|
||||
progress: { type: 'integer', description: 'Scans counted since acceptance; 0/1 for DELIVER/FIND' },
|
||||
rewardKind: { type: 'string', enum: ['resource', 'part'] },
|
||||
rewardQty: { type: 'integer', description: 'Reward quantity (1–3), granted into the cargo hold on completion' },
|
||||
state: { type: 'string', enum: ['AVAILABLE', 'ACTIVE', 'DONE'] },
|
||||
},
|
||||
required: ['id', 'type', 'description', 'targetSystemId', 'conditionKind', 'progressTarget', 'progress', 'rewardKind', 'rewardQty', 'state'],
|
||||
},
|
||||
UpgradeRequest: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
||||
Reference in New Issue
Block a user