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

@@ -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> => {