diff --git a/packages/shared/src/schemas/error.test.ts b/packages/shared/src/schemas/error.test.ts new file mode 100644 index 00000000..9ad79318 --- /dev/null +++ b/packages/shared/src/schemas/error.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from 'vitest'; +import { ApiError, apiErrorCodes } from './error'; + +/** The stable codes of the wiki API › Conventions, with `internal_error` of an unexpected 500. */ +const wikiCodes = [ + 'validation_error', + 'unauthorized', + 'forbidden', + 'not_found', + 'conflict', + 'rate_limited', + 'coherence_blocked', + 'link_expired', + 'premium_required', + 'route_removed', + 'account_required', + 'internal_error', +]; + +/** + * The codes of the admin (wiki Admin-Web), not yet cited by API › Conventions: until that page + * lists them (#618), the test compares to both lists. + */ +const adminCodes = [ + 'mfa_required', + 'recent_sign_in_required', + 'account_suspended', + 'already_decided', +]; + +describe('apiErrorCodes', () => { + it('holds the stable codes of the wiki, those of the admin, `internal_error`, and no other', () => { + expect([...apiErrorCodes].toSorted()).toEqual([...wikiCodes, ...adminCodes].toSorted()); + }); +}); + +describe('ApiError', () => { + it.each(['link_expired', 'account_required'])('accepts the code %s', (code) => { + expect(ApiError.parse({ code, message: 'Refused' })).toEqual({ code, message: 'Refused' }); + }); + + it('refuses a code the wiki does not list', () => { + expect(ApiError.safeParse({ code: 'link_gone', message: 'Refused' }).success).toBe(false); + }); +}); diff --git a/packages/shared/src/schemas/error.ts b/packages/shared/src/schemas/error.ts index 94db069a..f3d3ad38 100644 --- a/packages/shared/src/schemas/error.ts +++ b/packages/shared/src/schemas/error.ts @@ -9,6 +9,8 @@ export const apiErrorCodes = [ 'conflict', 'rate_limited', 'coherence_blocked', + /** 410 of a confirmation link that expired, was already used or was cancelled (D-062). */ + 'link_expired', 'premium_required', /** 410 of a withdrawn route, with `details.removedAt` (`RouteRemovedDetails`). */ 'route_removed', @@ -20,6 +22,8 @@ export const apiErrorCodes = [ 'account_suspended', /** 409 on a moderation decision: another member of the team decided first (D-087). */ 'already_decided', + /** 403 of a route that needs an account, called with the token of an anonymous account (D-099). */ + 'account_required', 'internal_error', ] as const;