import { tap, expect } from '@git.zone/tstest/tapbundle'; import { AcmeError } from '../ts/acme/acme.classes.error.js'; // --- Basic error properties --- tap.test('AcmeError extends Error', async () => { const err = new AcmeError({ status: 400 }); expect(err).toBeInstanceOf(Error); expect(err).toBeInstanceOf(AcmeError); }); tap.test('AcmeError has correct name', async () => { const err = new AcmeError({ status: 400 }); expect(err.name).toEqual('AcmeError'); }); tap.test('message includes type, status, url, and detail', async () => { const err = new AcmeError({ status: 403, type: 'urn:ietf:params:acme:error:unauthorized', detail: 'Account is deactivated', url: 'https://acme.example/order/1', }); expect(err.message).toInclude('403'); expect(err.message).toInclude('urn:ietf:params:acme:error:unauthorized'); expect(err.message).toInclude('https://acme.example/order/1'); expect(err.message).toInclude('Account is deactivated'); }); tap.test('preserves all properties including subproblems', async () => { const subproblems = [ { type: 'urn:ietf:params:acme:error:caa', detail: 'CAA record prevents issuance', identifier: { type: 'dns', value: 'example.com' } }, ]; const err = new AcmeError({ status: 403, type: 'urn:ietf:params:acme:error:unauthorized', detail: 'Forbidden', subproblems, url: 'https://acme.example/chall/1', retryAfter: 30, }); expect(err.status).toEqual(403); expect(err.type).toEqual('urn:ietf:params:acme:error:unauthorized'); expect(err.detail).toEqual('Forbidden'); expect(err.subproblems.length).toEqual(1); expect(err.subproblems[0].type).toEqual('urn:ietf:params:acme:error:caa'); expect(err.url).toEqual('https://acme.example/chall/1'); expect(err.retryAfter).toEqual(30); }); tap.test('default values for optional fields', async () => { const err = new AcmeError({ status: 500 }); expect(err.type).toEqual(''); expect(err.detail).toEqual(''); expect(err.subproblems).toEqual([]); expect(err.url).toEqual(''); expect(err.retryAfter).toEqual(0); }); // --- isRateLimited --- tap.test('isRateLimited is true for status 429', async () => { const err = new AcmeError({ status: 429 }); expect(err.isRateLimited).toBeTrue(); }); tap.test('isRateLimited is true for rateLimited type URN', async () => { const err = new AcmeError({ status: 403, type: 'urn:ietf:params:acme:error:rateLimited', }); expect(err.isRateLimited).toBeTrue(); }); tap.test('isRateLimited is false for regular 400', async () => { const err = new AcmeError({ status: 400, type: 'urn:ietf:params:acme:error:malformed' }); expect(err.isRateLimited).toBeFalse(); }); // --- isRetryable --- tap.test('isRetryable is true for 429', async () => { const err = new AcmeError({ status: 429 }); expect(err.isRetryable).toBeTrue(); }); tap.test('isRetryable is true for 503 and 500', async () => { expect(new AcmeError({ status: 503 }).isRetryable).toBeTrue(); expect(new AcmeError({ status: 500 }).isRetryable).toBeTrue(); }); tap.test('isRetryable is true for badNonce type', async () => { const err = new AcmeError({ status: 400, type: 'urn:ietf:params:acme:error:badNonce' }); expect(err.isRetryable).toBeTrue(); }); tap.test('isRetryable is false for 403, 404, 409', async () => { expect(new AcmeError({ status: 403 }).isRetryable).toBeFalse(); expect(new AcmeError({ status: 404 }).isRetryable).toBeFalse(); expect(new AcmeError({ status: 409 }).isRetryable).toBeFalse(); }); export default tap.start();