diff --git a/src/channel/httpBeaconChannel.ts b/src/channel/httpBeaconChannel.ts index 79021bd6..6bd95fb7 100644 --- a/src/channel/httpBeaconChannel.ts +++ b/src/channel/httpBeaconChannel.ts @@ -5,6 +5,7 @@ import type {Logger} from '../logging'; import {NullLogger} from '../logging'; import type {CidAssigner} from '../cid'; import {formatMessage} from '../error'; +import type {ApiProblem} from '../error'; import {CLIENT_LIBRARY} from '../constants'; import {Help} from '../help'; @@ -15,12 +16,11 @@ export type Configuration = { logger?: Logger, }; -type ApiProblem = { - type: string, - title: string, - status: number, - detail: string, -}; +export enum TrackingErrorType { + SUSPENDED_SERVICE = 'https://croct.help/sdk/javascript/suspended-service', + // Server errors + INTERNAL_ERROR = 'https://croct.help/api/event-tracking/internal-error', +} export class HttpBeaconChannel implements DuplexChannel> { private readonly configuration: Omit; @@ -65,7 +65,7 @@ export class HttpBeaconChannel implements DuplexChannel ({ - type: 'https://croct.help/api/event-tracker#unexpected-error', + type: TrackingErrorType.INTERNAL_ERROR, title: response.statusText, status: response.status, }), ); const isRetryable = HttpBeaconChannel.isRetryable(problem.status); - const help = Help.forStatusCode(problem.status); + const help = Help.forApiProblem(problem); if (help !== undefined) { this.logger.error(help); diff --git a/src/contentFetcher.ts b/src/contentFetcher.ts index a5afeca2..dab7f808 100644 --- a/src/contentFetcher.ts +++ b/src/contentFetcher.ts @@ -1,25 +1,21 @@ -import type {JsonObject} from '@croct/json'; +import type {JsonObject, JsonPrimitive} from '@croct/json'; import type {ContentDefinitionBundle} from '@croct/content-model/definition'; import type {EvaluationContext} from './evaluator'; import type {Token} from './token'; import {BASE_ENDPOINT_URL, CLIENT_LIBRARY} from './constants'; import {formatMessage} from './error'; +import type {ApiProblem} from './error'; import type {Logger} from './logging'; import {NullLogger} from './logging'; import {ApiKey} from './apiKey'; import {Help} from './help'; -export type ErrorResponse = { - type: string, - title: string, - status: number, - detail?: string, -}; - export enum ContentErrorType { TIMEOUT = 'https://croct.help/sdk/javascript/request-timeout', - UNEXPECTED_ERROR = 'https://croct.help/sdk/javascript/unexpected-error', SUSPENDED_SERVICE = 'https://croct.help/sdk/javascript/suspended-service', + // Server errors + INTERNAL_ERROR = 'https://croct.help/api/content/internal-error', + INVALID_PAYLOAD = 'https://croct.help/api/content/invalid-payload', } type FetchPayload = { @@ -31,7 +27,7 @@ type FetchPayload = { context?: EvaluationContext, }; -export class ContentError extends Error { +export class ContentError extends Error { public readonly response: T; public constructor(response: T) { @@ -43,6 +39,24 @@ export class ContentError extends Error } } +export type InputViolation = { + path: string, + reason?: string, + details?: Record, +}; + +export type InputErrorResponse = ApiProblem & { + violations?: InputViolation[], +}; + +export class InputError extends ContentError { + public constructor(response: InputErrorResponse) { + super(response); + + Object.setPrototypeOf(this, InputError.prototype); + } +} + export type FetchResponseOptions = { includeSchema?: boolean, }; @@ -164,7 +178,7 @@ export class ContentFetcher { if (timeout !== undefined) { timer = setTimeout( () => { - const response: ErrorResponse = { + const response: ApiProblem = { title: `Content could not be loaded in time for slot '${slotId}'.`, type: ContentErrorType.TIMEOUT, detail: `The content took more than ${timeout}ms to load.`, @@ -173,7 +187,7 @@ export class ContentFetcher { abortController.abort(); - this.logHelp(response.status); + this.logHelp(response); reject(new ContentError(response)); }, @@ -204,9 +218,15 @@ export class ContentFetcher { return resolve(body); } - this.logHelp(response.status); + const problem: ApiProblem = body; + + this.logHelp(problem); + + if (problem.type === ContentErrorType.INVALID_PAYLOAD) { + return reject(new InputError(problem as InputErrorResponse)); + } - reject(new ContentError(body)); + reject(new ContentError(problem)); }) .catch(error => { if (!response.ok) { @@ -221,7 +241,7 @@ export class ContentFetcher { reject( new ContentError({ title: formatMessage(error), - type: ContentErrorType.UNEXPECTED_ERROR, + type: ContentErrorType.INTERNAL_ERROR, detail: 'Please try again or contact Croct support if the error persists.', status: 500, // Internal Server Error }), @@ -311,8 +331,8 @@ export class ContentFetcher { }); } - private logHelp(statusCode: number): void { - const help = Help.forStatusCode(statusCode); + private logHelp(problem: ApiProblem): void { + const help = Help.forApiProblem(problem); if (help !== undefined) { this.logger.error(help); diff --git a/src/error.ts b/src/error.ts index 9db8c292..dadefa5f 100644 --- a/src/error.ts +++ b/src/error.ts @@ -1,3 +1,11 @@ +export type ApiProblem = { + title: string, + type: string, + status: number, + detail?: string, + instance?: string, +}; + function extractMessage(error: unknown): string { if (error instanceof Error) { return error.message; diff --git a/src/evaluator.ts b/src/evaluator.ts index 13aaf50f..bd07d585 100644 --- a/src/evaluator.ts +++ b/src/evaluator.ts @@ -2,6 +2,7 @@ import type {JsonObject, JsonValue} from '@croct/json'; import type {Token} from './token'; import {BASE_ENDPOINT_URL, CLIENT_LIBRARY, MAX_QUERY_LENGTH} from './constants'; import {formatMessage} from './error'; +import type {ApiProblem} from './error'; import type {Location} from './sourceLocation'; import {getLength, getLocation} from './sourceLocation'; import type {Logger} from './logging'; @@ -48,23 +49,15 @@ export type EvaluationOptions = { export enum EvaluationErrorType { TIMEOUT = 'https://croct.help/sdk/javascript/request-timeout', - UNEXPECTED_ERROR = 'https://croct.help/sdk/javascript/unexpected-error', - INVALID_QUERY = 'https://croct.help/sdk/javascript/invalid-query', - TOO_COMPLEX_QUERY = 'https://croct.help/sdk/javascript/too-complex-query', - EVALUATION_FAILED = 'https://croct.help/sdk/javascript/evaluation-failed', - UNALLOWED_RESULT = 'https://croct.help/sdk/javascript/unallowed-result', SUSPENDED_SERVICE = 'https://croct.help/sdk/javascript/suspended-service', - UNSERIALIZABLE_RESULT = 'https://croct.help/sdk/javascript/unserializable-result', + TOO_COMPLEX_QUERY = 'https://croct.help/sdk/javascript/too-complex-query', + // Server errors + INTERNAL_ERROR = 'https://croct.help/api/evaluation/internal-error', + INVALID_QUERY = 'https://croct.help/api/evaluation/invalid-query', + EVALUATION_FAILED = 'https://croct.help/api/evaluation/evaluation-failed', } -export type ErrorResponse = { - type: EvaluationErrorType, - title: string, - status: number, - detail?: string, -}; - -export class EvaluationError extends Error { +export class EvaluationError extends Error { public readonly response: T; public constructor(response: T) { @@ -81,7 +74,7 @@ type QueryErrorDetail = { location: Location, }; -export type QueryErrorResponse = ErrorResponse & { +export type QueryErrorResponse = ApiProblem & { errors: QueryErrorDetail[], }; @@ -174,7 +167,7 @@ export class Evaluator { if (timeout !== undefined) { timer = setTimeout( () => { - const response: ErrorResponse = { + const response: ApiProblem = { title: `Evaluation could not be completed in time for query "${reference}".`, type: EvaluationErrorType.TIMEOUT, detail: `The evaluation took more than ${timeout}ms to complete.`, @@ -183,7 +176,7 @@ export class Evaluator { abortController.abort(); - this.logHelp(response.status); + this.logHelp(response); reject(new EvaluationError(response)); }, @@ -217,14 +210,13 @@ export class Evaluator { return resolve(body); } - this.logHelp(response.status); + const problem: ApiProblem = body; - const problem: ErrorResponse = body; + this.logHelp(problem); switch (problem.type) { case EvaluationErrorType.INVALID_QUERY: case EvaluationErrorType.EVALUATION_FAILED: - case EvaluationErrorType.TOO_COMPLEX_QUERY: reject(new QueryError(problem as QueryErrorResponse)); break; @@ -250,7 +242,7 @@ export class Evaluator { reject( new EvaluationError({ title: formatMessage(error), - type: EvaluationErrorType.UNEXPECTED_ERROR, + type: EvaluationErrorType.INTERNAL_ERROR, detail: 'Please try again or contact Croct support if the error persists.', status: 500, // Internal Server Error }), @@ -310,8 +302,8 @@ export class Evaluator { }); } - private logHelp(statusCode: number): void { - const help = Help.forStatusCode(statusCode); + private logHelp(problem: ApiProblem): void { + const help = Help.forApiProblem(problem); if (help !== undefined) { this.logger.error(help); diff --git a/src/help.ts b/src/help.ts index 0cbaafc2..24599d73 100644 --- a/src/help.ts +++ b/src/help.ts @@ -1,5 +1,25 @@ +import type {ApiProblem} from './error'; + +enum AuthErrorType { + FORBIDDEN_ORIGIN = 'https://croct.help/api/authentication/forbidden-origin', + QUOTA_EXCEEDED = 'https://croct.help/api/authentication/quota-exceeded', +} + export namespace Help { - export function forStatusCode(statusCode: 204 | 401 | 403 | 408 | 423): string; + export function forApiProblem(problem: ApiProblem): string | undefined { + switch (problem.type) { + case AuthErrorType.FORBIDDEN_ORIGIN: + return 'The origin of the request is not allowed in your application settings. ' + + 'For help, see https://croct.help/sdk/javascript/unauthorized-origin'; + case AuthErrorType.QUOTA_EXCEEDED: + return 'The application has exceeded the monthly active users (MAU) quota. ' + + 'For help, see https://croct.help/sdk/javascript/mau-exceeded'; + default: + return Help.forStatusCode(problem.status); + } + } + + export function forStatusCode(statusCode: 202 | 401 | 408): string; export function forStatusCode(statusCode: number): string | undefined; @@ -13,18 +33,10 @@ export namespace Help { return 'The request was not authorized, most likely due to invalid credentials. ' + 'For help, see https://croct.help/sdk/javascript/invalid-credentials'; - case 403: - return 'The origin of the request is not allowed in your application settings. ' - + 'For help, see https://croct.help/sdk/javascript/unauthorized-origin'; - case 408: return 'The request timed out. ' + 'For help, see https://croct.help/sdk/javascript/request-timeout'; - case 423: - return 'The application has exceeded the monthly active users (MAU) quota. ' - + 'For help, see https://croct.help/sdk/javascript/mau-exceeded'; - default: return undefined; } diff --git a/test/channel/httpBeaconChannel.test.ts b/test/channel/httpBeaconChannel.test.ts index 3b35a3b2..c8d3341d 100644 --- a/test/channel/httpBeaconChannel.test.ts +++ b/test/channel/httpBeaconChannel.test.ts @@ -188,6 +188,7 @@ describe('An HTTP beacon channel', () => { type NonRetryableErrorScenario = { status: number, + type?: string, title: string, log?: string, }; @@ -209,22 +210,25 @@ describe('An HTTP beacon channel', () => { }, { status: 403, - title: 'Unallowed origin', + type: 'https://croct.help/api/authentication/quota-exceeded', + title: 'Quota exceeded', }, { - status: 423, - title: 'Quota exceeded', + status: 403, + type: 'https://croct.help/api/authentication/forbidden-origin', + title: 'Unallowed origin', }, ])('should report a non-retryable error if the response status is $status', async scenario => { const {status, title} = scenario; - const log = scenario.log ?? Help.forStatusCode(status); + const type = scenario.type ?? 'https://croct.help/api/event-tracker#error'; + const log = scenario.log ?? Help.forApiProblem({status: status, type: type, title: title}); expect(log).toBeDefined(); fetchMock.mockGlobal().route(endpointUrl, { status: status, body: JSON.stringify({ - type: 'https://croct.help/api/event-tracker#error', + type: type, title: title, status: status, }), diff --git a/test/contentFetcher.test.ts b/test/contentFetcher.test.ts index 63cd966c..9529c50c 100644 --- a/test/contentFetcher.test.ts +++ b/test/contentFetcher.test.ts @@ -2,8 +2,9 @@ import type {CallLog, UserRouteConfig} from 'fetch-mock'; import fetchMock from 'fetch-mock'; import type {EvaluationContext} from '../src/evaluator'; import {Token} from '../src/token'; -import type {ErrorResponse, FetchOptions, FetchResponse} from '../src/contentFetcher'; -import {ContentFetcher, ContentError, ContentErrorType} from '../src/contentFetcher'; +import type {FetchOptions, FetchResponse, InputErrorResponse} from '../src/contentFetcher'; +import {ContentFetcher, ContentError, ContentErrorType, InputError} from '../src/contentFetcher'; +import type {ApiProblem} from '../src/error'; import {BASE_ENDPOINT_URL, CLIENT_LIBRARY} from '../src/constants'; import {ApiKey} from '../src/apiKey'; import type {Logger} from '../src/logging'; @@ -644,7 +645,7 @@ describe('A content fetcher', () => { appId: appId, }); - const response: ErrorResponse = { + const response: ApiProblem = { type: 'error', title: 'Error title', status: 400, @@ -664,14 +665,49 @@ describe('A content fetcher', () => { await expect(promise).rejects.toHaveProperty('response', response); }); + it('should report an input error if the request payload is invalid', async () => { + const fetcher = new ContentFetcher({ + appId: appId, + }); + + const response: InputErrorResponse = { + type: ContentErrorType.INVALID_PAYLOAD, + title: 'The request payload is invalid.', + status: 422, + violations: [ + { + path: 'slotId', + reason: 'The slot ID is invalid.', + details: { + pattern: '^[a-z-]+$', + }, + }, + ], + }; + + fetchMock.mockGlobal().route({ + ...requestMatcher, + response: { + status: response.status, + body: response, + }, + }); + + const promise = fetcher.fetch(slotId); + + await expect(promise).rejects.toThrow(InputError); + await expect(promise).rejects.toBeInstanceOf(ContentError); + await expect(promise).rejects.toHaveProperty('response', response); + }); + it('should catch deserialization errors', async () => { const fetcher = new ContentFetcher({ appId: appId, }); - const response: ErrorResponse = { + const response: ApiProblem = { title: 'Error 500 - Internal Server Error', - type: ContentErrorType.UNEXPECTED_ERROR, + type: ContentErrorType.INTERNAL_ERROR, detail: 'Please try again or contact Croct support if the error persists.', status: 500, }; @@ -702,9 +738,9 @@ describe('A content fetcher', () => { appId: appId, }); - const response: ErrorResponse = { + const response: ApiProblem = { title: 'Unknown error', - type: ContentErrorType.UNEXPECTED_ERROR, + type: ContentErrorType.INTERNAL_ERROR, detail: 'Please try again or contact Croct support if the error persists.', status: 500, }; @@ -720,9 +756,9 @@ describe('A content fetcher', () => { appId: appId, }); - const response: ErrorResponse = { + const response: ApiProblem = { title: 'Something went wrong', - type: ContentErrorType.UNEXPECTED_ERROR, + type: ContentErrorType.INTERNAL_ERROR, detail: 'Please try again or contact Croct support if the error persists.', status: 500, }; @@ -742,23 +778,27 @@ describe('A content fetcher', () => { type HelpScenario = { status: number, + type: string, title: string, }; it.each([ { status: 401, + type: 'https://croct.help/api/content/some-error', title: 'Unauthorized request', }, { status: 403, - title: 'Unallowed origin', + type: 'https://croct.help/api/authentication/quota-exceeded', + title: 'Quota exceeded', }, { - status: 423, - title: 'Quota exceeded', + status: 403, + type: 'https://croct.help/api/authentication/forbidden-origin', + title: 'Unallowed origin', }, - ])('should log help messages for status code $status', async scenario => { + ])('should log help messages for the API problem $type', async scenario => { const logger: Logger = { debug: jest.fn(), info: jest.fn(), @@ -771,15 +811,17 @@ describe('A content fetcher', () => { logger: logger, }); + const response: ApiProblem = { + type: scenario.type, + status: scenario.status, + title: scenario.title, + }; + fetchMock.mockGlobal().route({ ...requestMatcher, response: { status: scenario.status, - body: { - type: 'https://croct.help/api/content', - status: scenario.status, - title: scenario.title, - } satisfies ErrorResponse, + body: response, }, }); @@ -787,7 +829,7 @@ describe('A content fetcher', () => { await expect(promise).rejects.toThrowWithMessage(ContentError, scenario.title); - const log = Help.forStatusCode(scenario.status); + const log = Help.forApiProblem(response); expect(log).toBeDefined(); expect(logger.error).toHaveBeenCalledWith(log); @@ -837,8 +879,8 @@ describe('A content fetcher', () => { describe('A content error', () => { it('should have a response', () => { - const response: ErrorResponse = { - type: ContentErrorType.UNEXPECTED_ERROR, + const response: ApiProblem = { + type: ContentErrorType.INTERNAL_ERROR, title: 'Error title', status: 400, }; diff --git a/test/evaluator.test.ts b/test/evaluator.test.ts index 62ad5b38..72940dbe 100644 --- a/test/evaluator.test.ts +++ b/test/evaluator.test.ts @@ -1,7 +1,8 @@ import type {CallLog, UserRouteConfig} from 'fetch-mock'; import fetchMock from 'fetch-mock'; -import type {ErrorResponse, EvaluationContext, EvaluationOptions, QueryErrorResponse} from '../src/evaluator'; +import type {EvaluationContext, EvaluationOptions, QueryErrorResponse} from '../src/evaluator'; import {EvaluationError, EvaluationErrorType, Evaluator, QueryError} from '../src/evaluator'; +import type {ApiProblem} from '../src/error'; import {Token} from '../src/token'; import {BASE_ENDPOINT_URL, CLIENT_LIBRARY} from '../src/constants'; import {ApiKey} from '../src/apiKey'; @@ -429,8 +430,8 @@ describe('An evaluator', () => { appId: appId, }); - const response: ErrorResponse = { - type: EvaluationErrorType.UNALLOWED_RESULT, + const response: ApiProblem = { + type: EvaluationErrorType.INTERNAL_ERROR, title: 'Error title', status: 400, }; @@ -448,11 +449,9 @@ describe('An evaluator', () => { await expect(promise).rejects.toThrow(EvaluationError); await expect(promise).rejects.toHaveProperty('response', response); }); - it.each([ [EvaluationErrorType.EVALUATION_FAILED], [EvaluationErrorType.INVALID_QUERY], - [EvaluationErrorType.TOO_COMPLEX_QUERY], ])( 'should report an query error if the error that can be traced back to the offending input (%s)', async (errorType: EvaluationErrorType) => { @@ -536,9 +535,9 @@ describe('An evaluator', () => { appId: appId, }); - const response: ErrorResponse = { + const response: ApiProblem = { title: 'Error 500 - Internal Server Error', - type: EvaluationErrorType.UNEXPECTED_ERROR, + type: EvaluationErrorType.INTERNAL_ERROR, detail: 'Please try again or contact Croct support if the error persists.', status: 500, }; @@ -562,9 +561,9 @@ describe('An evaluator', () => { appId: appId, }); - const response: ErrorResponse = { + const response: ApiProblem = { title: 'Unknown error', - type: EvaluationErrorType.UNEXPECTED_ERROR, + type: EvaluationErrorType.INTERNAL_ERROR, detail: 'Please try again or contact Croct support if the error persists.', status: 500, }; @@ -587,9 +586,9 @@ describe('An evaluator', () => { appId: appId, }); - const response: ErrorResponse = { + const response: ApiProblem = { title: 'Network error.', - type: EvaluationErrorType.UNEXPECTED_ERROR, + type: EvaluationErrorType.INTERNAL_ERROR, detail: 'Please try again or contact Croct support if the error persists.', status: 500, }; @@ -609,23 +608,27 @@ describe('An evaluator', () => { type HelpScenario = { status: number, + type: string, title: string, }; it.each([ { status: 401, + type: 'https://croct.help/api/evaluation/some-error', title: 'Unauthorized request', }, { status: 403, - title: 'Unallowed origin', + type: 'https://croct.help/api/authentication/quota-exceeded', + title: 'Quota exceeded', }, { - status: 423, - title: 'Quota exceeded', + status: 403, + type: 'https://croct.help/api/authentication/forbidden-origin', + title: 'Unallowed origin', }, - ])('should log help messages for status code $status', async scenario => { + ])('should log help messages for the API problem $type', async scenario => { const logger: Logger = { debug: jest.fn(), info: jest.fn(), @@ -638,9 +641,9 @@ describe('An evaluator', () => { logger: logger, }); - const response: ErrorResponse = { + const response: ApiProblem = { title: scenario.title, - type: EvaluationErrorType.UNEXPECTED_ERROR, + type: scenario.type, status: scenario.status, }; @@ -656,7 +659,7 @@ describe('An evaluator', () => { await expect(promise).rejects.toThrowWithMessage(EvaluationError, scenario.title); - const help = Help.forStatusCode(scenario.status); + const help = Help.forApiProblem(response); expect(help).toBeDefined(); @@ -708,8 +711,8 @@ describe('An evaluator', () => { describe('An evaluation error', () => { it('should have a response', () => { - const response: ErrorResponse = { - type: EvaluationErrorType.UNALLOWED_RESULT, + const response: ApiProblem = { + type: EvaluationErrorType.INTERNAL_ERROR, title: 'Error title', status: 400, }; diff --git a/test/help.test.ts b/test/help.test.ts index 7678da1d..a03351b1 100644 --- a/test/help.test.ts +++ b/test/help.test.ts @@ -1,3 +1,4 @@ +import type {ApiProblem} from '../src/error'; import {Help} from '../src/help'; describe('A function to provide help for errors', () => { @@ -15,18 +16,10 @@ describe('A function to provide help for errors', () => { status: 401, help: 'https://croct.help/sdk/javascript/invalid-credentials', }, - { - status: 403, - help: 'https://croct.help/sdk/javascript/unauthorized-origin', - }, { status: 408, help: 'https://croct.help/sdk/javascript/request-timeout', }, - { - status: 423, - help: 'https://croct.help/sdk/javascript/mau-exceeded', - }, ])('should provide help for status code %i', scenario => { expect(Help.forStatusCode(scenario.status)).toContain(scenario.help); }); @@ -34,4 +27,70 @@ describe('A function to provide help for errors', () => { it('should return undefined for status codes without help', () => { expect(Help.forStatusCode(999)).toBeUndefined(); }); + + type ApiProblemScenario = { + problem: ApiProblem, + help: string, + }; + + it.each([ + { + problem: { + type: 'https://croct.help/api/some-error', + title: 'Service is suspended.', + status: 202, + }, + help: 'https://croct.help/sdk/javascript/suspended-service', + }, + { + problem: { + type: 'https://croct.help/api/some-error', + title: 'Unauthorized request.', + status: 401, + }, + help: 'https://croct.help/sdk/javascript/invalid-credentials', + }, + { + problem: { + type: 'https://croct.help/api/some-error', + title: 'Request timed out.', + status: 408, + }, + help: 'https://croct.help/sdk/javascript/request-timeout', + }, + { + problem: { + type: 'https://croct.help/api/authentication/forbidden-origin', + title: 'Unallowed origin.', + status: 403, + }, + help: 'https://croct.help/sdk/javascript/unauthorized-origin', + }, + { + problem: { + type: 'https://croct.help/api/authentication/quota-exceeded', + title: 'Quota exceeded.', + status: 403, + }, + help: 'https://croct.help/sdk/javascript/mau-exceeded', + }, + ])('should provide help for the API problem $problem.type', scenario => { + expect(Help.forApiProblem(scenario.problem)).toContain(scenario.help); + }); + + it('should prioritize the error type help', () => { + expect(Help.forApiProblem({ + type: 'https://croct.help/api/authentication/quota-exceeded', + title: 'Unauthorized request.', + status: 401, + })).toContain('https://croct.help/sdk/javascript/mau-exceeded'); + }); + + it('should return undefined for API problems without help', () => { + expect(Help.forApiProblem({ + type: 'https://croct.help/api/some-error', + title: 'Some error.', + status: 500, + })).toBeUndefined(); + }); }); diff --git a/test/sdk.test.ts b/test/sdk.test.ts index bc3fd6fe..b89630ac 100644 --- a/test/sdk.test.ts +++ b/test/sdk.test.ts @@ -7,10 +7,10 @@ import {NullLogger} from '../src/logging'; import {Token} from '../src/token'; import {TabEventEmulator} from './utils/tabEventEmulator'; import type {BeaconPayload, NothingChanged} from '../src/trackingEvents'; -import type {ErrorResponse as ContentFetchErrorResponse, FetchResponse} from '../src/contentFetcher'; +import type {FetchResponse} from '../src/contentFetcher'; import {ContentError} from '../src/contentFetcher'; +import type {ApiProblem} from '../src/error'; import {BASE_ENDPOINT_URL} from '../src/constants'; -import type {ErrorResponse as EvaluationErrorResponse} from '../src/evaluator'; import {EvaluationError} from '../src/evaluator'; import {Container} from '../src/container'; @@ -496,7 +496,7 @@ describe('A SDK', () => { await expect(promise).rejects.toHaveProperty('response', expect.objectContaining({ detail: 'The evaluation took more than 5ms to complete.', - } satisfies Partial)); + } satisfies Partial)); }); it('should configure the evaluator with the default timeout', async () => { @@ -533,7 +533,7 @@ describe('A SDK', () => { await expect(promise).rejects.toHaveProperty('response', expect.objectContaining({ detail: 'The evaluation took more than 5000ms to complete.', - } satisfies Partial)); + } satisfies Partial)); }); it('should configure the content fetch with the default preferred locale', async () => { @@ -614,7 +614,7 @@ describe('A SDK', () => { await expect(promise).rejects.toHaveProperty('response', expect.objectContaining({ detail: 'The content took more than 5ms to load.', - } satisfies Partial)); + } satisfies Partial)); }); it('should configure the content fetcher with the default timeout', async () => { @@ -659,7 +659,7 @@ describe('A SDK', () => { await expect(promise).rejects.toHaveProperty('response', expect.objectContaining({ detail: 'The content took more than 5000ms to load.', - } satisfies Partial)); + } satisfies Partial)); }); it.each([