diff --git a/README.md b/README.md index b595a03..593eba9 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ for the `/v2` Rails API) via `openapi-typescript` (`npm run generate:types` → `lib/types/schema.ts`). Where the spec conflicts with this client's existing, tested behavior, the tested behavior wins. Differences found so far, for whoever owns the spec/backend. Everything below was re-verified against both -the spec and the live server as of `@tlecommunity/api-spec` 1.1.0; entries +the spec and the live server as of `@tlecommunity/api-spec` 1.2.0; entries resolved by that release (and the accompanying backend work) have been removed. @@ -56,17 +56,17 @@ desc,population desc" via package Lacuna::DB::Result::Log::Alliance`; 10. **`building.demolish`'s response is documented but unverified.** The spec says `{status, buildings}`; nothing here demolishes a building to check, so `DemolishResponse` mirrors the spec on trust. -11. **Several `/v2` endpoints documented in the spec return error 1002** - ("Method ... is not recognized by the v2 API's argument map") — the - backend migration off legacy Perl hasn't wired these routes up yet: +11. **`/v2/empire/change_password` returns error 1002** ("Method ... is + not recognized by the v2 API's argument map") - the backend migration off + legacy Perl hasn't wired this route up yet. The legacy JSON-RPC endpoint + serves it fine, so it's the `/v2` route specifically that's missing. + Expected current backend state per CLAUDE.md, not a client bug; + `empire.test.ts` asserts the 1002 outright, so it fails once the route + lands. Every other endpoint this entry used to list - `archaeology/get_ores_available_for_processing`, `trade/view_market`, `transporter/view_market`, `empire/get_invite_friend_url`, - `empire/change_password`, `empire/get_species_templates`, and every - `inbox/*` method (`view_inbox`, `view_archived`, `view_trashed`, - `view_sent`, `view_unread`). Expected current backend state per - CLAUDE.md, not a client bug - tests skip the schema check (rather than - fail) on that specific code, so they'll fail once the route lands. The - three `empire/*` ones assert the 1002 outright, so they'll fail then too. + `empire/get_species_templates` and every `inbox/*` method - is wired up + as of 1.2.0 and now has a real test. 12. **`/v2/entertainment/get_lottery_voting_options` triggers a captcha challenge (error 1016).** The client surfaces the challenge to subscribers (see "Error handling" below) along with a retry callback, diff --git a/lib/endpoints/buildings/archaeology.test.ts b/lib/endpoints/buildings/archaeology.test.ts index ac0d114..aa19dfa 100644 --- a/lib/endpoints/buildings/archaeology.test.ts +++ b/lib/endpoints/buildings/archaeology.test.ts @@ -58,23 +58,10 @@ test('getOresAvailableForProcessing', async () => { return; } - const { result, error } = await lacuna.archaeology.getOresAvailableForProcessing({ + const { result } = await lacuna.archaeology.getOresAvailableForProcessing({ building_id: buildingId, }); - if (error?.code === 1002) { - // Live server currently returns 1002 "Method ... is not recognized by - // the v2 API's argument map" for this call, even though it's documented - // in @tlecommunity/api-spec - the backend migration hasn't wired this - // endpoint up yet. Skip rather than fail until that lands. See README - // "Spec Discrepancies" #24. - console.warn( - 'Skipping schema check: get_ores_available_for_processing errored on the live server', - error - ); - return; - } - expect(result?.ore).toBeDefined(); expectMatchesApiSchema('archaeology', 'get_ores_available_for_processing', result); }); diff --git a/lib/endpoints/buildings/trade.test.ts b/lib/endpoints/buildings/trade.test.ts index 6c54184..f11ef01 100644 --- a/lib/endpoints/buildings/trade.test.ts +++ b/lib/endpoints/buildings/trade.test.ts @@ -46,21 +46,7 @@ test('viewMarket', async () => { return; } - // The live server currently rejects this call with server error 1002 - // "Method /trade/view_market is not recognized by the v2 API's argument - // map" - the backend hasn't implemented this endpoint yet (this repo's - // tests are a live integration test for a backend still migrating off - // legacy Perl, per CLAUDE.md, so this is expected backend state, not a - // client bug). See README "Spec Discrepancies" #24. - const { result, error } = await lacuna.trade.viewMarket({ building_id: buildingId }); - - if (error?.code === 1002) { - console.warn( - 'Skipping schema check: trade/view_market is not yet implemented by the live server', - error - ); - return; - } + const { result } = await lacuna.trade.viewMarket({ building_id: buildingId }); expect(result?.trades).toBeDefined(); expectMatchesApiSchema('trade', 'view_market', result); diff --git a/lib/endpoints/buildings/transporter.test.ts b/lib/endpoints/buildings/transporter.test.ts index d1b89af..b9238d5 100644 --- a/lib/endpoints/buildings/transporter.test.ts +++ b/lib/endpoints/buildings/transporter.test.ts @@ -94,21 +94,7 @@ test('viewMarket', async () => { return; } - // The live server currently rejects this call with server error 1002 - // "Method /transporter/view_market is not recognized by the v2 API's - // argument map" - the backend hasn't implemented this endpoint yet (this - // repo's tests are a live integration test for a backend still migrating - // off legacy Perl, per CLAUDE.md, so this is expected backend state, not a - // client bug). See README "Spec Discrepancies" #24. - const { result, error } = await lacuna.transporter.viewMarket({ building_id: buildingId }); - - if (error?.code === 1002) { - console.warn( - 'Skipping schema check: transporter/view_market is not yet implemented by the live server', - error - ); - return; - } + const { result } = await lacuna.transporter.viewMarket({ building_id: buildingId }); expect(result?.trades).toBeDefined(); expectMatchesApiSchema('transporter', 'view_market', result); diff --git a/lib/endpoints/empire.test.ts b/lib/endpoints/empire.test.ts index 70df4bf..c7092e6 100644 --- a/lib/endpoints/empire.test.ts +++ b/lib/endpoints/empire.test.ts @@ -147,6 +147,16 @@ test('find', async () => { expectMatchesApiSchema('empire', 'find', result); }); +test('getInviteFriendUrl', async () => { + const lacuna = await getLacuna(); + + const { result } = await lacuna.empire.getInviteFriendUrl(); + + expect(result?.referral_url).toBeDefined(); + expect(result?.status.server).toBeDefined(); + expectMatchesApiSchema('empire', 'get_invite_friend_url', result); +}); + test('setStatusMessage', async () => { const lacuna = await getLacuna(); @@ -175,6 +185,41 @@ test('viewSpeciesStats', async () => { expectMatchesApiSchema('empire', 'view_species_stats', result); }); +test('getSpeciesTemplates', async () => { + // Sessionless: the templates are a static list, and the empire that needs + // them to fill in updateSpecies hasn't been founded yet. + const lacuna = new Lacuna(); + + const { result } = await lacuna.empire.getSpeciesTemplates(); + + expect(result!.length).toBeGreaterThan(0); + expect(result![0].name).toBeDefined(); + + // Every template has to be a legal updateSpecies submission: the eleven + // affinities, plus a point for each orbit from min_orbit to max_orbit + // inclusive, must total exactly 45. One that doesn't is one the server + // would reject if a caller submitted it back unchanged. + for (const template of result!) { + const affinities = + template.manufacturing_affinity + + template.deception_affinity + + template.research_affinity + + template.management_affinity + + template.farming_affinity + + template.mining_affinity + + template.science_affinity + + template.environmental_affinity + + template.political_affinity + + template.trade_affinity + + template.growth_affinity; + const orbits = template.max_orbit - template.min_orbit + 1; + + expect(affinities + orbits).toBe(45); + } + + expectMatchesApiSchema('empire', 'get_species_templates', result); +}); + test('redefineSpeciesLimits', async () => { const lacuna = await getLacuna(); @@ -262,26 +307,25 @@ describe('methods checked without being carried out', () => { }); }); -// The v2 API serves these per the spec, but the backend hasn't wired them into -// its argument map yet - see README "Spec Discrepancies". These assertions flip -// (and this block goes away) once it has. -describe.each([ - ['get_invite_friend_url', async () => (await getLacuna()).empire.getInviteFriendUrl()], - ['get_species_templates', async () => (await getLacuna()).empire.getSpeciesTemplates()], - [ - 'change_password', - async () => - (await getLacuna()).empire.changePassword({ password1: '1234qwer', password2: '1234qwer' }), - ], -] as const)('%s', (wireName, call) => { - test('is not wired up by the v2 API yet', async () => { - const { error } = await call(); +// The v2 API serves this per the spec, but the backend hasn't wired it into +// its argument map yet - see README "Spec Discrepancies". This assertion flips +// (and this test goes away) once it has. The legacy JSON-RPC endpoint does +// serve the method, so it's the /v2 route specifically that's missing. +test('changePassword is not wired up by the v2 API yet', async () => { + const lacuna = await getLacuna(); - expect(error?.code).toBe(1002); - expect(error?.message).toBe( - `Method /empire/${wireName} is not recognized by the v2 API's argument map.` - ); + // Deliberately mismatched, so nothing is written to the fixture empire even + // if the route lands underneath this - its password is the one every test + // logs in with (lib/__utils__/get-lacuna.ts). + const { error } = await lacuna.empire.changePassword({ + password1: 'abcdef1', + password2: 'abcdef2', }); + + expect(error?.code).toBe(1002); + expect(error?.message).toBe( + "Method /empire/change_password is not recognized by the v2 API's argument map." + ); }); // The v2 API hands a named-only method its whole request as a single hash, diff --git a/lib/endpoints/inbox.test.ts b/lib/endpoints/inbox.test.ts index 37c13de..fca54dc 100644 --- a/lib/endpoints/inbox.test.ts +++ b/lib/endpoints/inbox.test.ts @@ -8,22 +8,8 @@ beforeAll(async () => { lacuna = await getLacuna(); }); -// The live server currently returns 1002 "Method ... is not recognized by -// the v2 API's argument map" for every lacuna.inbox.* method, even though -// they're all documented in @tlecommunity/api-spec - the backend migration -// off legacy Perl hasn't wired the /v2/inbox/* routes up yet (see -// CLAUDE.md - this repo's tests are a live integration test for a backend -// still in progress, so this is expected current backend state, not a -// client bug). Skip the schema check rather than fail until that lands. -// See README "Spec Discrepancies" #24. - test('viewInbox', async () => { - const { result, error } = await lacuna.inbox.viewInbox({}); - - if (error?.code === 1002) { - console.warn('Skipping schema check: inbox/view_inbox errored on the live server', error); - return; - } + const { result } = await lacuna.inbox.viewInbox({}); expect(result?.messages).toBeDefined(); expect(result?.message_count).toBeDefined(); @@ -31,12 +17,7 @@ test('viewInbox', async () => { }); test('viewArchived', async () => { - const { result, error } = await lacuna.inbox.viewArchived({}); - - if (error?.code === 1002) { - console.warn('Skipping schema check: inbox/view_archived errored on the live server', error); - return; - } + const { result } = await lacuna.inbox.viewArchived({}); expect(result?.messages).toBeDefined(); expect(result?.message_count).toBeDefined(); @@ -44,12 +25,7 @@ test('viewArchived', async () => { }); test('viewTrashed', async () => { - const { result, error } = await lacuna.inbox.viewTrashed({}); - - if (error?.code === 1002) { - console.warn('Skipping schema check: inbox/view_trashed errored on the live server', error); - return; - } + const { result } = await lacuna.inbox.viewTrashed({}); expect(result?.messages).toBeDefined(); expect(result?.message_count).toBeDefined(); @@ -57,12 +33,7 @@ test('viewTrashed', async () => { }); test('viewSent', async () => { - const { result, error } = await lacuna.inbox.viewSent({}); - - if (error?.code === 1002) { - console.warn('Skipping schema check: inbox/view_sent errored on the live server', error); - return; - } + const { result } = await lacuna.inbox.viewSent({}); expect(result?.messages).toBeDefined(); expect(result?.message_count).toBeDefined(); @@ -70,12 +41,7 @@ test('viewSent', async () => { }); test('viewUnread', async () => { - const { result, error } = await lacuna.inbox.viewUnread({}); - - if (error?.code === 1002) { - console.warn('Skipping schema check: inbox/view_unread errored on the live server', error); - return; - } + const { result } = await lacuna.inbox.viewUnread({}); expect(result?.messages).toBeDefined(); expect(result?.message_count).toBeDefined(); diff --git a/lib/types/schema.ts b/lib/types/schema.ts index f72eee3..c756f55 100644 --- a/lib/types/schema.ts +++ b/lib/types/schema.ts @@ -18268,7 +18268,6 @@ export interface paths { [key: string]: { name: string; image: string; - /** Format: date-time */ date: string; /** @enum {integer} */ public: 1 | 0; @@ -18306,7 +18305,6 @@ export interface paths { skip_attack_messages: 1 | 0; /** @enum {integer} */ skip_incoming_ships: 1 | 0; - /** Format: email */ email: string; sitter_password: string; }; @@ -18438,10 +18436,7 @@ export interface paths { * @enum {integer} */ skip_incoming_ships?: 1 | 0; - /** - * Format: email - * @description An email address that can be used for system functions like password recovery. Must either resemble an email address or be empty. - */ + /** @description An email address that can be used for system functions like password recovery. Must either resemble an email address or be empty. */ email?: string; /** @description A password that can be safely given to account sitters and alliance members. Must be between 6 and 30 characters. */ sitter_password?: string; @@ -18468,7 +18463,6 @@ export interface paths { [key: string]: { name: string; image: string; - /** Format: date-time */ date: string; /** @enum {integer} */ public: 1 | 0; @@ -18506,7 +18500,6 @@ export interface paths { skip_attack_messages: 1 | 0; /** @enum {integer} */ skip_incoming_ships: 1 | 0; - /** Format: email */ email: string; sitter_password: string; }; @@ -18579,7 +18572,6 @@ export interface paths { [key: string]: { name: string; image: string; - /** Format: date-time */ date: string; /** @enum {integer} */ public: 1 | 0; @@ -19015,21 +19007,13 @@ export interface paths { jsonrpc?: string; result?: { boosts: { - /** Format: date-time */ spy_training: string; - /** Format: date-time */ ore: string; - /** Format: date-time */ water: string; - /** Format: date-time */ storage: string; - /** Format: date-time */ food: string; - /** Format: date-time */ happiness: string; - /** Format: date-time */ building: string; - /** Format: date-time */ energy: string; }; status: { @@ -19093,8 +19077,16 @@ export interface paths { id?: number; jsonrpc?: string; result?: { - /** Format: date-time */ - storage_boost: string; + boosts: { + spy_training: string; + ore: string; + water: string; + storage: string; + food: string; + happiness: string; + building: string; + energy: string; + }; status: { server: components['schemas']['server_status']; empire?: components['schemas']['empire_status']; @@ -19156,8 +19148,16 @@ export interface paths { id?: number; jsonrpc?: string; result?: { - /** Format: date-time */ - food_boost: string; + boosts: { + spy_training: string; + ore: string; + water: string; + storage: string; + food: string; + happiness: string; + building: string; + energy: string; + }; status: { server: components['schemas']['server_status']; empire?: components['schemas']['empire_status']; @@ -19219,8 +19219,16 @@ export interface paths { id?: number; jsonrpc?: string; result?: { - /** Format: date-time */ - water_boost: string; + boosts: { + spy_training: string; + ore: string; + water: string; + storage: string; + food: string; + happiness: string; + building: string; + energy: string; + }; status: { server: components['schemas']['server_status']; empire?: components['schemas']['empire_status']; @@ -19282,8 +19290,16 @@ export interface paths { id?: number; jsonrpc?: string; result?: { - /** Format: date-time */ - energy_boost: string; + boosts: { + spy_training: string; + ore: string; + water: string; + storage: string; + food: string; + happiness: string; + building: string; + energy: string; + }; status: { server: components['schemas']['server_status']; empire?: components['schemas']['empire_status']; @@ -19345,8 +19361,16 @@ export interface paths { id?: number; jsonrpc?: string; result?: { - /** Format: date-time */ - ore_boost: string; + boosts: { + spy_training: string; + ore: string; + water: string; + storage: string; + food: string; + happiness: string; + building: string; + energy: string; + }; status: { server: components['schemas']['server_status']; empire?: components['schemas']['empire_status']; @@ -19408,8 +19432,16 @@ export interface paths { id?: number; jsonrpc?: string; result?: { - /** Format: date-time */ - happiness_boost: string; + boosts: { + spy_training: string; + ore: string; + water: string; + storage: string; + food: string; + happiness: string; + building: string; + energy: string; + }; status: { server: components['schemas']['server_status']; empire?: components['schemas']['empire_status']; @@ -19471,8 +19503,16 @@ export interface paths { id?: number; jsonrpc?: string; result?: { - /** Format: date-time */ - building_boost: string; + boosts: { + spy_training: string; + ore: string; + water: string; + storage: string; + food: string; + happiness: string; + building: string; + energy: string; + }; status: { server: components['schemas']['server_status']; empire?: components['schemas']['empire_status']; @@ -19534,8 +19574,16 @@ export interface paths { id?: number; jsonrpc?: string; result?: { - /** Format: date-time */ - spy_training_boost: string; + boosts: { + spy_training: string; + ore: string; + water: string; + storage: string; + food: string; + happiness: string; + building: string; + energy: string; + }; status: { server: components['schemas']['server_status']; empire?: components['schemas']['empire_status']; @@ -20170,7 +20218,6 @@ export interface paths { sitters: { id: number; name: string; - /** Format: date-time */ expiry: string; }[]; status: { diff --git a/package-lock.json b/package-lock.json index 6580136..3d3f879 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "proper-url-join": "^2.1.2" }, "devDependencies": { - "@tlecommunity/api-spec": "^1.1.0", + "@tlecommunity/api-spec": "^1.2.0", "@types/jest": "^30.0.0", "@types/proper-url-join": "^2.1.5", "ajv": "^8.20.0", @@ -1541,9 +1541,9 @@ } }, "node_modules/@tlecommunity/api-spec": { - "version": "1.1.0", - "resolved": "https://gitea.allosaurus-chromatic.ts.net/api/packages/tlecommunity/npm/%40tlecommunity%2Fapi-spec/-/1.1.0/api-spec-1.1.0.tgz", - "integrity": "sha512-fefhEmn7V0tw8F4u7zMXZRWrepT25EwlwYSoeel5/rL2jKceOJumyvtjNe8L635p/THENhKXy4CprTfmRJrXTw==", + "version": "1.2.0", + "resolved": "https://gitea.allosaurus-chromatic.ts.net/api/packages/tlecommunity/npm/%40tlecommunity%2Fapi-spec/-/1.2.0/api-spec-1.2.0.tgz", + "integrity": "sha512-SYV0sQmU5UHGVyv2L1s3c/fQZyPNefEMJpSSjuA4ItAB3+97zxRdsFyAh3k3gqNRH3DTKNyRnhNBdrCQdufeBA==", "dev": true, "license": "MIT" }, @@ -7169,9 +7169,9 @@ } }, "@tlecommunity/api-spec": { - "version": "1.1.0", - "resolved": "https://gitea.allosaurus-chromatic.ts.net/api/packages/tlecommunity/npm/%40tlecommunity%2Fapi-spec/-/1.1.0/api-spec-1.1.0.tgz", - "integrity": "sha512-fefhEmn7V0tw8F4u7zMXZRWrepT25EwlwYSoeel5/rL2jKceOJumyvtjNe8L635p/THENhKXy4CprTfmRJrXTw==", + "version": "1.2.0", + "resolved": "https://gitea.allosaurus-chromatic.ts.net/api/packages/tlecommunity/npm/%40tlecommunity%2Fapi-spec/-/1.2.0/api-spec-1.2.0.tgz", + "integrity": "sha512-SYV0sQmU5UHGVyv2L1s3c/fQZyPNefEMJpSSjuA4ItAB3+97zxRdsFyAh3k3gqNRH3DTKNyRnhNBdrCQdufeBA==", "dev": true }, "@tybys/wasm-util": { diff --git a/package.json b/package.json index d6ac068..b20c22c 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "author": "Natalie Rose ", "license": "MIT", "devDependencies": { - "@tlecommunity/api-spec": "^1.1.0", + "@tlecommunity/api-spec": "^1.2.0", "@types/jest": "^30.0.0", "@types/proper-url-join": "^2.1.5", "ajv": "^8.20.0",