feat: Add better support for non-native errors

This commit is contained in:
Joachim Van Herwegen 2021-06-07 15:54:28 +02:00
parent cefc866109
commit 7cfb87e516
25 changed files with 111 additions and 98 deletions

View File

@ -5,7 +5,7 @@ import type { RepresentationPreferences } from '../ldp/representation/Representa
import { getLoggerFor } from '../logging/LogUtil';
import type { HttpHandlerInput } from '../server/HttpHandler';
import { HttpHandler } from '../server/HttpHandler';
import { assertNativeError, isNativeError } from '../util/errors/ErrorUtil';
import { assertError, createErrorMessage } from '../util/errors/ErrorUtil';
import type { IdentityProviderFactory } from './IdentityProviderFactory';
import type { InteractionHttpHandler } from './interaction/InteractionHttpHandler';
import type { InteractionPolicy } from './interaction/InteractionPolicy';
@ -55,7 +55,7 @@ export class IdentityProviderHttpHandler extends HttpHandler {
try {
this.provider = await this.providerFactory.createProvider(this.interactionPolicy);
} catch (err: unknown) {
this.logger.error(`Failed to create Provider: ${isNativeError(err) ? err.message : 'Unknown error'}`);
this.logger.error(`Failed to create Provider: ${createErrorMessage(err)}`);
throw err;
}
}
@ -75,7 +75,7 @@ export class IdentityProviderHttpHandler extends HttpHandler {
try {
await this.interactionHttpHandler.handle({ ...input, provider });
} catch (error: unknown) {
assertNativeError(error);
assertError(error);
const preferences: RepresentationPreferences = { type: { 'text/plain': 1 }};
const result = await this.errorHandler.handleSafe({ error, preferences });
await this.responseWriter.handleSafe({ response: input.response, result });

View File

@ -1,5 +1,5 @@
import assert from 'assert';
import { isNativeError } from '../../../util/errors/ErrorUtil';
import { createErrorMessage } from '../../../util/errors/ErrorUtil';
import { HttpError } from '../../../util/errors/HttpError';
import { IdpInteractionError } from '../util/IdpInteractionError';
@ -18,10 +18,8 @@ export function throwIdpInteractionError(error: unknown, prefilled: Record<strin
}
} else if (HttpError.isInstance(error)) {
throw new IdpInteractionError(error.statusCode, error.message, prefilled);
} else if (isNativeError(error)) {
throw new IdpInteractionError(500, error.message, prefilled);
} else {
throw new IdpInteractionError(500, 'Unknown Error', prefilled);
throw new IdpInteractionError(500, createErrorMessage(error), prefilled);
}
}

View File

@ -3,7 +3,7 @@ import { getLoggerFor } from '../../../../logging/LogUtil';
import type { HttpHandlerInput } from '../../../../server/HttpHandler';
import { HttpHandler } from '../../../../server/HttpHandler';
import type { RenderHandler } from '../../../../server/util/RenderHandler';
import { isNativeError } from '../../../../util/errors/ErrorUtil';
import { createErrorMessage } from '../../../../util/errors/ErrorUtil';
import { getFormDataRequestBody } from '../../util/FormDataUtil';
import { assertPassword } from '../EmailPasswordUtil';
import type { AccountStore } from '../storage/AccountStore';
@ -53,11 +53,10 @@ export class ResetPasswordHandler extends HttpHandler {
},
});
} catch (err: unknown) {
const errorMessage: string = isNativeError(err) ? err.message : 'An unknown error occurred';
await this.renderHandler.handleSafe({
response: input.response,
props: {
errorMessage,
errorMessage: createErrorMessage(err),
recordId: prefilledRecordId,
},
});

View File

@ -1,6 +1,6 @@
import type { HttpHandler } from '../../../server/HttpHandler';
import { RouterHandler } from '../../../server/util/RouterHandler';
import { isNativeError } from '../../../util/errors/ErrorUtil';
import { createErrorMessage } from '../../../util/errors/ErrorUtil';
import type { InteractionHttpHandlerInput } from '../InteractionHttpHandler';
import { IdpInteractionError } from './IdpInteractionError';
import type { IdpRenderHandler } from './IdpRenderHandler';
@ -35,9 +35,8 @@ export class IdpRouteController extends RouterHandler {
try {
await this.handler.handleSafe(input);
} catch (err: unknown) {
const errorMessage = isNativeError(err) ? err.message : 'An unknown error occurred';
const prefilled = IdpInteractionError.isInstance(err) ? err.prefilled : {};
await this.render(input, errorMessage, prefilled);
await this.render(input, createErrorMessage(err), prefilled);
}
}
}

View File

@ -2,7 +2,7 @@ import fetch from '@rdfjs/fetch';
import type { DatasetResponse } from '@rdfjs/fetch-lite';
import type { Dataset } from 'rdf-js';
import { getLoggerFor } from '../../logging/LogUtil';
import { isNativeError } from '../../util/errors/ErrorUtil';
import { createErrorMessage } from '../../util/errors/ErrorUtil';
const logger = getLoggerFor('FetchUtil');
@ -14,17 +14,16 @@ export async function fetchDataset(url: string): Promise<Dataset> {
try {
rawResponse = (await fetch(url)) as DatasetResponse<Dataset>;
} catch (err: unknown) {
const errorMessage = `Cannot fetch ${url}: ${isNativeError(err) ? err.message : 'Unknown error'}`;
logger.error(errorMessage);
throw new Error(errorMessage);
logger.error(`Cannot fetch ${url}: ${createErrorMessage(err)}`);
throw new Error(`Cannot fetch ${url}`);
}
let dataset: Dataset;
try {
dataset = await rawResponse.dataset();
} catch (err: unknown) {
const errorMessage = `Could not parse RDF in ${url}: ${isNativeError(err) ? err.message : 'Unknown error'}`;
logger.error(errorMessage);
throw new Error(errorMessage);
logger.error(`Could not parse RDF in ${url}: ${createErrorMessage(err)}`);
// Keeping the error message the same to prevent leaking possible information about intranet
throw new Error(`Cannot fetch ${url}`);
}
return dataset;
}

View File

@ -6,7 +6,7 @@ import type { HttpHandlerInput } from '../server/HttpHandler';
import { HttpHandler } from '../server/HttpHandler';
import type { HttpRequest } from '../server/HttpRequest';
import type { HttpResponse } from '../server/HttpResponse';
import { assertNativeError } from '../util/errors/ErrorUtil';
import { assertError } from '../util/errors/ErrorUtil';
import type { ErrorHandler } from './http/ErrorHandler';
import type { RequestParser } from './http/RequestParser';
import type { ResponseDescription } from './http/response/ResponseDescription';
@ -103,7 +103,7 @@ export class AuthenticatedLdpHandler extends HttpHandler {
try {
writeData = { response: input.response, result: await this.runHandlers(input.request) };
} catch (error: unknown) {
assertNativeError(error);
assertError(error);
// We don't know the preferences yet at this point
const preferences: RepresentationPreferences = { type: { 'text/plain': 1 }};
const result = await this.errorHandler.handleSafe({ error, preferences });
@ -129,7 +129,7 @@ export class AuthenticatedLdpHandler extends HttpHandler {
try {
return await this.handleOperation(request, operation);
} catch (error: unknown) {
assertNativeError(error);
assertError(error);
return await this.errorHandler.handleSafe({ error, preferences: operation.preferences });
}
}

View File

@ -1,7 +1,6 @@
import { getLoggerFor } from '../../logging/LogUtil';
import type { HttpResponse } from '../../server/HttpResponse';
import { INTERNAL_QUADS } from '../../util/ContentTypes';
import { isNativeError } from '../../util/errors/ErrorUtil';
import { isInternalContentType } from '../../storage/conversion/ConversionUtil';
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
import { pipeSafely } from '../../util/StreamUtil';
import type { MetadataWriter } from './metadata/MetadataWriter';
@ -20,9 +19,10 @@ export class BasicResponseWriter extends ResponseWriter {
this.metadataWriter = metadataWriter;
}
public async canHandle(input: { response: HttpResponse; result: ResponseDescription | Error }): Promise<void> {
if (isNativeError(input.result) || input.result.metadata?.contentType === INTERNAL_QUADS) {
throw new NotImplementedHttpError('Only successful binary responses are supported');
public async canHandle(input: { response: HttpResponse; result: ResponseDescription }): Promise<void> {
const contentType = input.result.metadata?.contentType;
if (isInternalContentType(contentType)) {
throw new NotImplementedHttpError(`Cannot serialize the internal content type ${contentType}`);
}
}

View File

@ -3,7 +3,7 @@ import { translate } from 'sparqlalgebrajs';
import { getLoggerFor } from '../../logging/LogUtil';
import { APPLICATION_SPARQL_UPDATE } from '../../util/ContentTypes';
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
import { isNativeError } from '../../util/errors/ErrorUtil';
import { createErrorMessage } from '../../util/errors/ErrorUtil';
import { UnsupportedMediaTypeHttpError } from '../../util/errors/UnsupportedMediaTypeHttpError';
import { guardedStreamFrom, readableToString } from '../../util/StreamUtil';
import type { BodyParserArgs } from './BodyParser';
@ -29,10 +29,7 @@ export class SparqlUpdateBodyParser extends BodyParser {
algebra = translate(sparql, { quads: true, baseIRI: metadata.identifier.value });
} catch (error: unknown) {
this.logger.warn('Could not translate SPARQL query to SPARQL algebra', { error });
if (isNativeError(error)) {
throw new BadRequestHttpError(error.message);
}
throw new BadRequestHttpError();
throw new BadRequestHttpError(createErrorMessage(error));
}
// Prevent body from being requested again

View File

@ -4,7 +4,7 @@ import { createServer as createHttpServer } from 'http';
import { createServer as createHttpsServer } from 'https';
import { URL } from 'url';
import { getLoggerFor } from '../logging/LogUtil';
import { isNativeError } from '../util/errors/ErrorUtil';
import { isError } from '../util/errors/ErrorUtil';
import { guardStream } from '../util/GuardedStream';
import type { HttpHandler } from './HttpHandler';
import type { HttpServerFactory } from './HttpServerFactory';
@ -67,8 +67,8 @@ export class BaseHttpServerFactory implements HttpServerFactory {
await this.handler.handleSafe({ request: guardStream(request), response });
} catch (error: unknown) {
let errMsg: string;
if (!isNativeError(error)) {
errMsg = 'Unknown error.\n';
if (!isError(error)) {
errMsg = `Unknown error: ${error}.\n`;
} else if (this.options.showStackTrace && error.stack) {
errMsg = `${error.stack}\n`;
} else {

View File

@ -11,7 +11,7 @@ import { getLoggerFor } from '../logging/LogUtil';
import { INTERNAL_QUADS } from '../util/ContentTypes';
import { BadRequestHttpError } from '../util/errors/BadRequestHttpError';
import { ConflictHttpError } from '../util/errors/ConflictHttpError';
import { isNativeError } from '../util/errors/ErrorUtil';
import { createErrorMessage } from '../util/errors/ErrorUtil';
import { ForbiddenHttpError } from '../util/errors/ForbiddenHttpError';
import { MethodNotAllowedHttpError } from '../util/errors/MethodNotAllowedHttpError';
import { NotFoundHttpError } from '../util/errors/NotFoundHttpError';
@ -344,10 +344,7 @@ export class DataAccessorBasedStore implements ResourceStore {
quads = await parseQuads(representation.data, { format: contentType, baseIRI: identifier.value });
}
} catch (error: unknown) {
if (isNativeError(error)) {
throw new BadRequestHttpError(`Can only create containers with RDF data. ${error.message}`);
}
throw error;
throw new BadRequestHttpError(`Can only create containers with RDF data. ${createErrorMessage(error)}`);
}
// Solid, §5.3: "Servers MUST NOT allow HTTP POST, PUT and PATCH to update a containers containment triples;
@ -481,8 +478,7 @@ export class DataAccessorBasedStore implements ResourceStore {
deleted.push(identifier);
} catch (error: unknown) {
if (!NotFoundHttpError.isInstance(error)) {
const errorMsg = isNativeError(error) ? error.message : error;
this.logger.error(`Problem deleting auxiliary resource ${identifier.path}: ${errorMsg}`);
this.logger.error(`Error deleting auxiliary resource ${identifier.path}: ${createErrorMessage(error)}`);
}
}
}));

View File

@ -19,7 +19,7 @@ import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdenti
import { getLoggerFor } from '../../logging/LogUtil';
import { INTERNAL_QUADS } from '../../util/ContentTypes';
import { ConflictHttpError } from '../../util/errors/ConflictHttpError';
import { isNativeError } from '../../util/errors/ErrorUtil';
import { createErrorMessage } from '../../util/errors/ErrorUtil';
import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError';
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
import { UnsupportedMediaTypeHttpError } from '../../util/errors/UnsupportedMediaTypeHttpError';
@ -300,9 +300,7 @@ export class SparqlDataAccessor implements DataAccessor {
try {
return guardStream(await this.fetcher.fetchTriples(this.endpoint, query));
} catch (error: unknown) {
if (isNativeError(error)) {
this.logger.error(`SPARQL endpoint ${this.endpoint} error: ${error.message}`);
}
this.logger.error(`SPARQL endpoint ${this.endpoint} error: ${createErrorMessage(error)}`);
throw error;
}
}
@ -317,9 +315,7 @@ export class SparqlDataAccessor implements DataAccessor {
try {
return await this.fetcher.fetchUpdate(this.endpoint, query);
} catch (error: unknown) {
if (isNativeError(error)) {
this.logger.error(`SPARQL endpoint ${this.endpoint} error: ${error.message}`);
}
this.logger.error(`SPARQL endpoint ${this.endpoint} error: ${createErrorMessage(error)}`);
throw error;
}
}

View File

@ -156,3 +156,15 @@ export function matchesMediaType(mediaA: string, mediaB: string): boolean {
}
return subTypeA === subTypeB;
}
/**
* Checks if the given content type is an internal content type such as internal/quads.
* Response will be `false` if the input type is undefined.
*
* Do not use this for media ranges.
*
* @param contentType - Type to check.
*/
export function isInternalContentType(contentType?: string): boolean {
return typeof contentType !== 'undefined' && matchesMediaType(contentType, INTERNAL_ALL);
}

View File

@ -4,20 +4,28 @@ import { HttpError } from './HttpError';
/**
* Checks if the input is an {@link Error}.
*/
export function isNativeError(error: any): error is Error {
return types.isNativeError(error);
export function isError(error: any): error is Error {
return types.isNativeError(error) ||
(error &&
typeof error.name === 'string' &&
typeof error.message === 'string' &&
(typeof error.stack === 'undefined' || typeof error.stack === 'string'));
}
/**
* Asserts that the input is a native error.
* If not the input will be re-thrown.
*/
export function assertNativeError(error: any): asserts error is Error {
if (!isNativeError(error)) {
export function assertError(error: unknown): asserts error is Error {
if (!isError(error)) {
throw error;
}
}
export function createErrorMessage(error: unknown): string {
return isError(error) ? error.message : `Unknown error: ${error}`;
}
/**
* Returns the HTTP status code corresponding to the error.
*/

View File

@ -1,4 +1,4 @@
import { isNativeError } from './ErrorUtil';
import { isError } from './ErrorUtil';
/**
* A class for all errors that could be thrown by Solid.
@ -21,6 +21,6 @@ export class HttpError extends Error {
}
public static isInstance(error: any): error is HttpError {
return isNativeError(error) && typeof (error as any).statusCode === 'number';
return isError(error) && typeof (error as any).statusCode === 'number';
}
}

View File

@ -1,6 +1,6 @@
import { getLoggerFor } from '../../logging/LogUtil';
import { BadRequestHttpError } from '../errors/BadRequestHttpError';
import { isNativeError } from '../errors/ErrorUtil';
import { createErrorMessage } from '../errors/ErrorUtil';
import { HttpError } from '../errors/HttpError';
import { InternalServerError } from '../errors/InternalServerError';
import type { AsyncHandler } from './AsyncHandler';
@ -87,10 +87,8 @@ export class WaterfallHandler<TIn, TOut> implements AsyncHandler<TIn, TOut> {
} catch (error: unknown) {
if (HttpError.isInstance(error)) {
errors.push(error);
} else if (isNativeError(error)) {
errors.push(new InternalServerError(error.message));
} else {
errors.push(new InternalServerError('Unknown error'));
errors.push(new InternalServerError(createErrorMessage(error)));
}
}
}

View File

@ -45,7 +45,7 @@ describe('EmailPasswordUtil', (): void => {
const error = 'Error!';
expect((): never => throwIdpInteractionError(error, prefilled)).toThrow(expect.objectContaining({
statusCode: 500,
message: 'Unknown Error',
message: 'Unknown error: Error!',
prefilled,
}));
});

View File

@ -86,7 +86,7 @@ describe('A ResetPasswordHandler', (): void => {
});
it('has a default error for non-native errors.', async(): Promise<void> => {
const errorMessage = 'An unknown error occurred';
const errorMessage = 'Unknown error: not native';
request = createPostFormRequest({ recordId, password: 'password!', confirmPassword: 'password!' });
(accountStore.getForgotPasswordRecord as jest.Mock).mockRejectedValueOnce('not native');
await expect(handler.handle({ request, response })).resolves.toBeUndefined();

View File

@ -74,7 +74,7 @@ describe('An IdpRouteController', (): void => {
expect(renderHandler.handleSafe).toHaveBeenCalledTimes(1);
expect(renderHandler.handleSafe).toHaveBeenLastCalledWith({
response,
props: { errorMessage: 'An unknown error occurred', prefilled: {}},
props: { errorMessage: 'Unknown error: apple!', prefilled: {}},
});
});

View File

@ -22,19 +22,13 @@ describe('FetchUtil', (): void => {
it('errors if there was an issue fetching.', async(): Promise<void> => {
fetchMock.mockRejectedValueOnce(new Error('Invalid webId!'));
await expect(fetchDataset(url)).rejects.toThrow(`Cannot fetch ${url}: Invalid webId!`);
await expect(fetchDataset(url)).rejects.toThrow(`Cannot fetch ${url}`);
expect(fetchMock).toHaveBeenCalledWith(url);
fetchMock.mockRejectedValueOnce('apple');
await expect(fetchDataset(url)).rejects.toThrow(`Cannot fetch ${url}: Unknown error`);
});
it('errors if there was an issue parsing the returned RDF.', async(): Promise<void> => {
(datasetResponse.dataset as jest.Mock).mockRejectedValueOnce(new Error('Invalid RDF!'));
await expect(fetchDataset(url)).rejects.toThrow(`Could not parse RDF in ${url}: Invalid RDF!`);
(datasetResponse.dataset as jest.Mock).mockRejectedValueOnce('apple');
await expect(fetchDataset(url)).rejects.toThrow(`Could not parse RDF in ${url}: Unknown error`);
await expect(fetchDataset(url)).rejects.toThrow(`Cannot fetch ${url}`);
});
it('returns the resulting Dataset.', async(): Promise<void> => {

View File

@ -25,12 +25,13 @@ describe('A BasicResponseWriter', (): void => {
});
it('requires the input to be a binary ResponseDescription.', async(): Promise<void> => {
await expect(writer.canHandle({ response, result: new Error('error') }))
.rejects.toThrow(NotImplementedHttpError);
const metadata = new RepresentationMetadata(INTERNAL_QUADS);
await expect(writer.canHandle({ response, result: { statusCode: 201, metadata }}))
.rejects.toThrow(NotImplementedHttpError);
await expect(writer.canHandle({ response, result }))
metadata.contentType = 'text/turtle';
await expect(writer.canHandle({ response, result: { statusCode: 201, metadata }}))
.resolves.toBeUndefined();
await expect(writer.canHandle({ response, result: { statusCode: 201 }}))
.resolves.toBeUndefined();
});

View File

@ -88,7 +88,7 @@ describe('A BaseHttpServerFactory', (): void => {
handler.handleSafe.mockRejectedValueOnce('apple');
const res = await request(server).get('/').expect(500);
expect(res.text).toContain('Unknown error.');
expect(res.text).toContain('Unknown error: apple.');
});
});
@ -107,7 +107,7 @@ describe('A BaseHttpServerFactory', (): void => {
server.close();
});
it('throws unknown errors if its handler throw non-Error objects.', async(): Promise<void> => {
it('does not print the stack if that option is disabled.', async(): Promise<void> => {
const error = new Error('dummyError');
handler.handleSafe.mockRejectedValueOnce(error);

View File

@ -20,7 +20,6 @@ import { NotImplementedHttpError } from '../../../src/util/errors/NotImplemented
import type { Guarded } from '../../../src/util/GuardedStream';
import { SingleRootIdentifierStrategy } from '../../../src/util/identifiers/SingleRootIdentifierStrategy';
import { trimTrailingSlashes } from '../../../src/util/PathUtil';
import * as quadUtil from '../../../src/util/QuadUtil';
import { guardedStreamFrom } from '../../../src/util/StreamUtil';
import { CONTENT_TYPE, SOLID_HTTP, LDP, PIM, RDF } from '../../../src/util/Vocabularies';
import quad = DataFactory.quad;
@ -236,16 +235,6 @@ describe('A DataAccessorBasedStore', (): void => {
await expect(store.addResource(resourceID, representation)).rejects.toThrow(BadRequestHttpError);
});
it('passes the result along if the MetadataController throws a non-Error.', async(): Promise<void> => {
const resourceID = { path: root };
const mock = jest.spyOn(quadUtil, 'parseQuads').mockImplementationOnce(async(): Promise<any> => {
throw 'apple';
});
representation.metadata.add(RDF.type, LDP.terms.Container);
await expect(store.addResource(resourceID, representation)).rejects.toBe('apple');
mock.mockRestore();
});
it('can write resources.', async(): Promise<void> => {
const resourceID = { path: root };
representation.metadata.removeAll(RDF.type);
@ -584,7 +573,7 @@ describe('A DataAccessorBasedStore', (): void => {
expect(accessor.data[`${root}resource.dummy`]).not.toBeUndefined();
expect(logger.error).toHaveBeenCalledTimes(1);
expect(logger.error).toHaveBeenLastCalledWith(
'Problem deleting auxiliary resource http://test.com/resource.dummy: auxiliary error!',
'Error deleting auxiliary resource http://test.com/resource.dummy: auxiliary error!',
);
});
@ -607,7 +596,7 @@ describe('A DataAccessorBasedStore', (): void => {
expect(accessor.data[`${root}resource.dummy`]).not.toBeUndefined();
expect(logger.error).toHaveBeenCalledTimes(1);
expect(logger.error).toHaveBeenLastCalledWith(
'Problem deleting auxiliary resource http://test.com/resource.dummy: auxiliary error!',
'Error deleting auxiliary resource http://test.com/resource.dummy: Unknown error: auxiliary error!',
);
});
});

View File

@ -4,7 +4,7 @@ import {
getBestPreference,
getConversionTarget,
getTypeWeight,
getWeightedPreferences,
getWeightedPreferences, isInternalContentType,
matchesMediaPreferences,
matchesMediaType,
} from '../../../../src/storage/conversion/ConversionUtil';
@ -144,4 +144,13 @@ describe('ConversionUtil', (): void => {
expect(matchesMediaType('text/plain', 'text/turtle')).toBeFalsy();
});
});
describe('#isInternalContentType', (): void => {
it('only returns true on internal types.', async(): Promise<void> => {
expect(isInternalContentType('internal/quads')).toBeTruthy();
expect(isInternalContentType()).toBeFalsy();
expect(isInternalContentType('text/turtle')).toBeFalsy();
});
});
});

View File

@ -1,24 +1,42 @@
import { assertNativeError, getStatusCode, isNativeError } from '../../../../src/util/errors/ErrorUtil';
import { assertError, createErrorMessage, getStatusCode, isError } from '../../../../src/util/errors/ErrorUtil';
import { NotFoundHttpError } from '../../../../src/util/errors/NotFoundHttpError';
describe('ErrorUtil', (): void => {
describe('#isNativeError', (): void => {
describe('#isError', (): void => {
it('returns true on native errors.', async(): Promise<void> => {
expect(isNativeError(new Error('error'))).toBe(true);
expect(isError(new Error('error'))).toBe(true);
});
it('returns true on error-like objects.', async(): Promise<void> => {
expect(isError({ name: 'name', message: 'message', stack: 'stack' })).toBe(true);
});
it('returns true on errors without a stack.', async(): Promise<void> => {
expect(isError({ name: 'name', message: 'message' })).toBe(true);
});
it('returns false on other values.', async(): Promise<void> => {
expect(isNativeError('apple')).toBe(false);
expect(isError('apple')).toBe(false);
});
});
describe('#assertNativeError', (): void => {
describe('#assertError', (): void => {
it('returns undefined on native errors.', async(): Promise<void> => {
expect(assertNativeError(new Error('error'))).toBeUndefined();
expect(assertError(new Error('error'))).toBeUndefined();
});
it('throws on other values.', async(): Promise<void> => {
expect((): void => assertNativeError('apple')).toThrow('apple');
expect((): void => assertError('apple')).toThrow('apple');
});
});
describe('#createErrorMessage', (): void => {
it('returns the given message for normal Errors.', async(): Promise<void> => {
expect(createErrorMessage(new Error('error msg'))).toBe('error msg');
});
it('tries to put the object in a string .', async(): Promise<void> => {
expect(createErrorMessage('apple')).toBe('Unknown error: apple');
});
});

View File

@ -53,7 +53,7 @@ describe('A WaterfallHandler', (): void => {
};
const handler = new WaterfallHandler([ handlerFalse, handlerFalse ]);
await expect(handler.canHandle(null)).rejects.toThrow('[Unknown error, Unknown error]');
await expect(handler.canHandle(null)).rejects.toThrow('[Unknown error: apple, Unknown error: apple]');
});
it('handles data if a handler supports it.', async(): Promise<void> => {