mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Add metadata to errors
This commit is contained in:
@@ -9,6 +9,7 @@ import { RepresentationMetadata } from '../../../../../src/http/representation/R
|
||||
import type { HttpRequest } from '../../../../../src/server/HttpRequest';
|
||||
import { BadRequestHttpError } from '../../../../../src/util/errors/BadRequestHttpError';
|
||||
import { UnsupportedMediaTypeHttpError } from '../../../../../src/util/errors/UnsupportedMediaTypeHttpError';
|
||||
import { ContentType } from '../../../../../src/util/Header';
|
||||
import { guardedStreamFrom } from '../../../../../src/util/StreamUtil';
|
||||
const { namedNode, quad } = DataFactory;
|
||||
|
||||
@@ -24,11 +25,10 @@ describe('A SparqlUpdateBodyParser', (): void => {
|
||||
await expect(bodyParser.canHandle(input)).rejects.toThrow(UnsupportedMediaTypeHttpError);
|
||||
input.metadata.contentType = 'text/plain';
|
||||
await expect(bodyParser.canHandle(input)).rejects.toThrow(UnsupportedMediaTypeHttpError);
|
||||
input.metadata.contentType = 'application/sparql-update;charset=utf-8';
|
||||
const contentType = new ContentType('application/sparql-update');
|
||||
input.metadata.contentTypeObject = contentType;
|
||||
await expect(bodyParser.canHandle(input)).resolves.toBeUndefined();
|
||||
input.metadata.contentType = 'application/sparql-update ; foo=bar';
|
||||
await expect(bodyParser.canHandle(input)).resolves.toBeUndefined();
|
||||
input.metadata.contentType = 'application/sparql-update';
|
||||
contentType.parameters = { charset: 'utf-8' };
|
||||
await expect(bodyParser.canHandle(input)).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import type {
|
||||
RepresentationConverter,
|
||||
RepresentationConverterArgs,
|
||||
} from '../../../../../src/storage/conversion/RepresentationConverter';
|
||||
import type { HttpError } from '../../../../../src/util/errors/HttpError';
|
||||
import { NotFoundHttpError } from '../../../../../src/util/errors/NotFoundHttpError';
|
||||
import { HTTP, XSD } from '../../../../../src/util/Vocabularies';
|
||||
import literal = DataFactory.literal;
|
||||
@@ -33,7 +34,7 @@ async function expectValidArgs(args: RepresentationConverterArgs, stack?: string
|
||||
|
||||
describe('A ConvertingErrorHandler', (): void => {
|
||||
// The error object can get modified by the handler
|
||||
let error: Error;
|
||||
let error: HttpError;
|
||||
let stack: string | undefined;
|
||||
const request = {} as HttpRequest;
|
||||
let converter: jest.Mocked<RepresentationConverter>;
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createResponse } from 'node-mocks-http';
|
||||
import { ContentTypeMetadataWriter } from '../../../../../src/http/output/metadata/ContentTypeMetadataWriter';
|
||||
import { RepresentationMetadata } from '../../../../../src/http/representation/RepresentationMetadata';
|
||||
import type { HttpResponse } from '../../../../../src/server/HttpResponse';
|
||||
import { ContentType } from '../../../../../src/util/Header';
|
||||
|
||||
describe('A ContentTypeMetadataWriter', (): void => {
|
||||
const writer = new ContentTypeMetadataWriter();
|
||||
@@ -18,18 +19,12 @@ describe('A ContentTypeMetadataWriter', (): void => {
|
||||
});
|
||||
|
||||
it('adds a Content-Type header with parameters if present.', async(): Promise<void> => {
|
||||
const metadata = new RepresentationMetadata('text/plain; charset=utf-8');
|
||||
const metadata = new RepresentationMetadata(new ContentType('text/plain', { charset: 'utf-8' }));
|
||||
await expect(writer.handle({ response, metadata })).resolves.toBeUndefined();
|
||||
|
||||
expect(response.getHeaders()).toEqual({
|
||||
'content-type': 'text/plain; charset=utf-8',
|
||||
});
|
||||
|
||||
const metadata2 = new RepresentationMetadata('text/plain; charset="utf-8"');
|
||||
await expect(writer.handle({ response, metadata: metadata2 })).resolves.toBeUndefined();
|
||||
expect(response.getHeaders()).toEqual({
|
||||
'content-type': 'text/plain; charset=utf-8',
|
||||
});
|
||||
});
|
||||
|
||||
it('adds a Content-Type header without parameters.', async(): Promise<void> => {
|
||||
|
||||
@@ -8,6 +8,7 @@ describe('A RedirectResponseDescription', (): void => {
|
||||
it('has status the code and location of the error.', async(): Promise<void> => {
|
||||
const description = new RedirectResponseDescription(error);
|
||||
expect(description.metadata?.get(SOLID_HTTP.terms.location)?.value).toBe(error.location);
|
||||
expect(description.metadata).toBe(error.metadata);
|
||||
expect(description.statusCode).toBe(error.statusCode);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'jest-rdf';
|
||||
import type { BlankNode } from 'n3';
|
||||
import { DataFactory } from 'n3';
|
||||
import type { NamedNode, Quad } from 'rdf-js';
|
||||
import { ContentType } from '../../../../src';
|
||||
import { RepresentationMetadata } from '../../../../src/http/representation/RepresentationMetadata';
|
||||
import { ContentType } from '../../../../src/util/Header';
|
||||
import { CONTENT_TYPE_TERM, SOLID_META, RDFS } from '../../../../src/util/Vocabularies';
|
||||
const { defaultGraph, literal, namedNode, quad } = DataFactory;
|
||||
|
||||
@@ -308,14 +308,14 @@ describe('A RepresentationMetadata', (): void => {
|
||||
it('has a shorthand for Content-Type as string.', async(): Promise<void> => {
|
||||
expect(metadata.contentType).toBeUndefined();
|
||||
expect(metadata.contentTypeObject).toBeUndefined();
|
||||
metadata.contentType = 'text/plain; charset=utf-8; test=value1';
|
||||
expect(metadata.contentTypeObject).toEqual({
|
||||
value: 'text/plain',
|
||||
parameters: {
|
||||
charset: 'utf-8',
|
||||
test: 'value1',
|
||||
},
|
||||
});
|
||||
metadata.contentType = 'text/plain';
|
||||
expect(metadata.contentTypeObject).toEqual({ value: 'text/plain', parameters: {}});
|
||||
});
|
||||
|
||||
it('errors trying to set a Content-Type with parameters using a string.', async(): Promise<void> => {
|
||||
expect((): void => {
|
||||
metadata.contentType = 'text/plain; charset=utf-8; test=value1';
|
||||
}).toThrow(Error);
|
||||
});
|
||||
|
||||
it('has a shorthand for Content-Type as object.', async(): Promise<void> => {
|
||||
@@ -341,7 +341,10 @@ describe('A RepresentationMetadata', (): void => {
|
||||
it('can properly clear the Content-Type parameters explicitly.', async(): Promise<void> => {
|
||||
expect(metadata.contentType).toBeUndefined();
|
||||
expect(metadata.contentTypeObject).toBeUndefined();
|
||||
metadata.contentType = 'text/plain; charset=utf-8; test=value1';
|
||||
metadata.contentTypeObject = new ContentType('text/plain', {
|
||||
charset: 'utf-8',
|
||||
test: 'value1',
|
||||
});
|
||||
metadata.contentType = undefined;
|
||||
expect(metadata.contentType).toBeUndefined();
|
||||
expect(metadata.contentTypeObject).toBeUndefined();
|
||||
@@ -353,7 +356,10 @@ describe('A RepresentationMetadata', (): void => {
|
||||
it('can properly clear the Content-Type parameters implicitly.', async(): Promise<void> => {
|
||||
expect(metadata.contentType).toBeUndefined();
|
||||
expect(metadata.contentTypeObject).toBeUndefined();
|
||||
metadata.contentType = 'text/plain; charset=utf-8; test=value1';
|
||||
metadata.contentTypeObject = new ContentType('text/plain', {
|
||||
charset: 'utf-8',
|
||||
test: 'value1',
|
||||
});
|
||||
metadata.contentType = 'text/turtle';
|
||||
expect(metadata.contentType).toBe('text/turtle');
|
||||
expect(metadata.contentTypeObject).toEqual({
|
||||
@@ -368,7 +374,10 @@ describe('A RepresentationMetadata', (): void => {
|
||||
it('can return invalid parameters when too many quads are present.', async(): Promise<void> => {
|
||||
expect(metadata.contentType).toBeUndefined();
|
||||
expect(metadata.contentTypeObject).toBeUndefined();
|
||||
metadata.contentType = 'text/plain; charset=utf-8; test=value1';
|
||||
metadata.contentTypeObject = new ContentType('text/plain', {
|
||||
charset: 'utf-8',
|
||||
test: 'value1',
|
||||
});
|
||||
const param = metadata.quads(null, SOLID_META.terms.value)[0].subject;
|
||||
metadata.addQuad(param as BlankNode, SOLID_META.terms.value, 'anomaly');
|
||||
expect(metadata.contentTypeObject?.parameters).toMatchObject({ invalid: '' });
|
||||
|
||||
@@ -14,6 +14,7 @@ import type { Interaction, InteractionHandler } from '../../../../src/identity/i
|
||||
import type { AdapterFactory } from '../../../../src/identity/storage/AdapterFactory';
|
||||
import type { KeyValueStorage } from '../../../../src/storage/keyvalue/KeyValueStorage';
|
||||
import { FoundHttpError } from '../../../../src/util/errors/FoundHttpError';
|
||||
import { extractErrorTerms } from '../../../../src/util/errors/HttpErrorUtil';
|
||||
import { OAuthHttpError } from '../../../../src/util/errors/OAuthHttpError';
|
||||
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
@@ -269,12 +270,12 @@ describe('An IdentityProviderFactory', (): void => {
|
||||
name: 'BadRequestHttpError',
|
||||
message: 'Unknown client, you might need to clear the local storage on the client.',
|
||||
errorCode: 'E0003',
|
||||
details: {
|
||||
client_id: 'CLIENT_ID',
|
||||
redirect_uri: 'REDIRECT_URI',
|
||||
},
|
||||
}),
|
||||
request: ctx.req });
|
||||
expect(extractErrorTerms(errorHandler.handleSafe.mock.calls[0][0].error.metadata)).toEqual({
|
||||
client_id: 'CLIENT_ID',
|
||||
redirect_uri: 'REDIRECT_URI',
|
||||
});
|
||||
expect(responseWriter.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(responseWriter.handleSafe).toHaveBeenLastCalledWith({ response: ctx.res, result: { statusCode: 500 }});
|
||||
});
|
||||
|
||||
@@ -5,6 +5,8 @@ import rdfDereferencer from 'rdf-dereference';
|
||||
import { v4 } from 'uuid';
|
||||
import { TokenOwnershipValidator } from '../../../../src/identity/ownership/TokenOwnershipValidator';
|
||||
import type { ExpiringStorage } from '../../../../src/storage/keyvalue/ExpiringStorage';
|
||||
import { BadRequestHttpError } from '../../../../src/util/errors/BadRequestHttpError';
|
||||
import { extractErrorTerms } from '../../../../src/util/errors/HttpErrorUtil';
|
||||
import { SOLID } from '../../../../src/util/Vocabularies';
|
||||
const { literal, namedNode, quad } = DataFactory;
|
||||
|
||||
@@ -57,10 +59,16 @@ describe('A TokenOwnershipValidator', (): void => {
|
||||
it('errors if no token is stored in the storage.', async(): Promise<void> => {
|
||||
// Even if the token is in the WebId, it will error since it's not in the storage
|
||||
mockDereference(tokenTriple);
|
||||
await expect(validator.handle({ webId })).rejects.toThrow(expect.objectContaining({
|
||||
message: expect.stringContaining(tokenString),
|
||||
details: { quad: tokenString },
|
||||
}));
|
||||
let error: unknown;
|
||||
try {
|
||||
await validator.handle({ webId });
|
||||
} catch (err: unknown) {
|
||||
error = err;
|
||||
}
|
||||
expect(error).toEqual(expect.objectContaining({ message: expect.stringContaining(tokenString) }));
|
||||
expect(BadRequestHttpError.isInstance(error)).toBe(true);
|
||||
expect(extractErrorTerms((error as BadRequestHttpError).metadata))
|
||||
.toEqual({ quad: tokenString });
|
||||
expect(rdfDereferenceMock.dereference).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
|
||||
@@ -4,12 +4,11 @@ import type { ErrorHandler } from '../../../src/http/output/error/ErrorHandler';
|
||||
import { ResponseDescription } from '../../../src/http/output/response/ResponseDescription';
|
||||
import type { ResponseWriter } from '../../../src/http/output/ResponseWriter';
|
||||
import { BasicRepresentation } from '../../../src/http/representation/BasicRepresentation';
|
||||
import { RepresentationMetadata } from '../../../src/http/representation/RepresentationMetadata';
|
||||
import type { HttpRequest } from '../../../src/server/HttpRequest';
|
||||
import type { HttpResponse } from '../../../src/server/HttpResponse';
|
||||
import type { OperationHttpHandler } from '../../../src/server/OperationHttpHandler';
|
||||
import { ParsingHttpHandler } from '../../../src/server/ParsingHttpHandler';
|
||||
import { HttpError } from '../../../src/util/errors/HttpError';
|
||||
import { BadRequestHttpError } from '../../../src/util/errors/BadRequestHttpError';
|
||||
|
||||
describe('A ParsingHttpHandler', (): void => {
|
||||
const request: HttpRequest = {} as any;
|
||||
@@ -57,7 +56,7 @@ describe('A ParsingHttpHandler', (): void => {
|
||||
});
|
||||
|
||||
it('calls the error handler if something goes wrong.', async(): Promise<void> => {
|
||||
const error = new Error('bad data');
|
||||
const error = new BadRequestHttpError('bad data');
|
||||
source.handleSafe.mockRejectedValueOnce(error);
|
||||
await expect(handler.handle({ request, response })).resolves.toBeUndefined();
|
||||
expect(errorHandler.handleSafe).toHaveBeenCalledTimes(1);
|
||||
@@ -66,16 +65,14 @@ describe('A ParsingHttpHandler', (): void => {
|
||||
expect(responseWriter.handleSafe).toHaveBeenLastCalledWith({ response, result: errorResponse });
|
||||
});
|
||||
|
||||
it('adds error metadata if able.', async(): Promise<void> => {
|
||||
const error = new HttpError(0, 'error');
|
||||
it('creates an InternalServerError if th error was not an HttpError.', async(): Promise<void> => {
|
||||
const error = new Error('bad data');
|
||||
source.handleSafe.mockRejectedValueOnce(error);
|
||||
const metaResponse = new ResponseDescription(0, new RepresentationMetadata());
|
||||
errorHandler.handleSafe.mockResolvedValueOnce(metaResponse);
|
||||
await expect(handler.handle({ request, response })).resolves.toBeUndefined();
|
||||
expect(errorHandler.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(errorHandler.handleSafe).toHaveBeenLastCalledWith({ error, request });
|
||||
expect(errorHandler.handleSafe).toHaveBeenLastCalledWith(expect.objectContaining({ request }));
|
||||
expect(errorHandler.handleSafe.mock.calls[0][0].error.cause).toBe(error);
|
||||
expect(responseWriter.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(responseWriter.handleSafe).toHaveBeenLastCalledWith({ response, result: metaResponse });
|
||||
expect(metaResponse.metadata?.quads()).toHaveLength(1);
|
||||
expect(responseWriter.handleSafe).toHaveBeenLastCalledWith({ response, result: errorResponse });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ import { NotFoundHttpError } from '../../../src/util/errors/NotFoundHttpError';
|
||||
import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError';
|
||||
import { PreconditionFailedHttpError } from '../../../src/util/errors/PreconditionFailedHttpError';
|
||||
import type { Guarded } from '../../../src/util/GuardedStream';
|
||||
import { ContentType } from '../../../src/util/Header';
|
||||
import { SingleRootIdentifierStrategy } from '../../../src/util/identifiers/SingleRootIdentifierStrategy';
|
||||
import { trimTrailingSlashes } from '../../../src/util/PathUtil';
|
||||
import { guardedStreamFrom } from '../../../src/util/StreamUtil';
|
||||
@@ -673,7 +674,7 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
representation.metadata.add(
|
||||
SOLID_META.terms.preserve, namedNode(metaResourceID.path), SOLID_META.terms.ResponseMetadata,
|
||||
);
|
||||
representation.metadata.contentType = 'text/plain; charset=UTF-8';
|
||||
representation.metadata.contentTypeObject = new ContentType('text/plain', { charset: 'UTF-8' });
|
||||
await store.setRepresentation(resourceID, representation);
|
||||
const { metadata } = accessor.data[resourceID.path];
|
||||
expect(metadata.quads(null, RDF.terms.type)).toHaveLength(2);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { BasicRepresentation } from '../../../../src/http/representation/BasicRepresentation';
|
||||
import { ErrorToJsonConverter } from '../../../../src/storage/conversion/ErrorToJsonConverter';
|
||||
import { BadRequestHttpError } from '../../../../src/util/errors/BadRequestHttpError';
|
||||
import { errorTermsToMetadata } from '../../../../src/util/errors/HttpErrorUtil';
|
||||
import type { OAuthErrorFields } from '../../../../src/util/errors/OAuthHttpError';
|
||||
import { OAuthHttpError } from '../../../../src/util/errors/OAuthHttpError';
|
||||
import { readJsonStream } from '../../../../src/util/StreamUtil';
|
||||
@@ -28,11 +29,13 @@ describe('An ErrorToJsonConverter', (): void => {
|
||||
statusCode: 400,
|
||||
errorCode: 'H400',
|
||||
stack: error.stack,
|
||||
details: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('copies the HttpError details.', async(): Promise<void> => {
|
||||
const error = new BadRequestHttpError('error text', { details: { important: 'detail' }});
|
||||
const metadata = errorTermsToMetadata({ important: 'detail' });
|
||||
const error = new BadRequestHttpError('error text', { metadata });
|
||||
const representation = new BasicRepresentation([ error ], 'internal/error', false);
|
||||
const prom = converter.handle({ identifier, representation, preferences });
|
||||
await expect(prom).resolves.toBeDefined();
|
||||
@@ -75,11 +78,13 @@ describe('An ErrorToJsonConverter', (): void => {
|
||||
error_description: 'error_description',
|
||||
scope: 'scope',
|
||||
state: 'state',
|
||||
details: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('does not copy the details if they are not serializable.', async(): Promise<void> => {
|
||||
const error = new BadRequestHttpError('error text', { details: { object: BigInt(1) }});
|
||||
it('only adds stack if it is defined.', async(): Promise<void> => {
|
||||
const error = new BadRequestHttpError('error text');
|
||||
delete error.stack;
|
||||
const representation = new BasicRepresentation([ error ], 'internal/error', false);
|
||||
const prom = converter.handle({ identifier, representation, preferences });
|
||||
await expect(prom).resolves.toBeDefined();
|
||||
@@ -91,39 +96,7 @@ describe('An ErrorToJsonConverter', (): void => {
|
||||
message: 'error text',
|
||||
statusCode: 400,
|
||||
errorCode: 'H400',
|
||||
stack: error.stack,
|
||||
});
|
||||
});
|
||||
|
||||
it('defaults to status code 500 for non-HTTP errors.', async(): Promise<void> => {
|
||||
const error = new Error('error text');
|
||||
const representation = new BasicRepresentation([ error ], 'internal/error', false);
|
||||
const prom = converter.handle({ identifier, representation, preferences });
|
||||
await expect(prom).resolves.toBeDefined();
|
||||
const result = await prom;
|
||||
expect(result.binary).toBe(true);
|
||||
expect(result.metadata.contentType).toBe('application/json');
|
||||
await expect(readJsonStream(result.data)).resolves.toEqual({
|
||||
name: 'Error',
|
||||
message: 'error text',
|
||||
statusCode: 500,
|
||||
stack: error.stack,
|
||||
});
|
||||
});
|
||||
|
||||
it('only adds stack if it is defined.', async(): Promise<void> => {
|
||||
const error = new Error('error text');
|
||||
delete error.stack;
|
||||
const representation = new BasicRepresentation([ error ], 'internal/error', false);
|
||||
const prom = converter.handle({ identifier, representation, preferences });
|
||||
await expect(prom).resolves.toBeDefined();
|
||||
const result = await prom;
|
||||
expect(result.binary).toBe(true);
|
||||
expect(result.metadata.contentType).toBe('application/json');
|
||||
await expect(readJsonStream(result.data)).resolves.toEqual({
|
||||
name: 'Error',
|
||||
message: 'error text',
|
||||
statusCode: 500,
|
||||
details: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { BasicRepresentation } from '../../../../src/http/representation/BasicRepresentation';
|
||||
import { ErrorToTemplateConverter } from '../../../../src/storage/conversion/ErrorToTemplateConverter';
|
||||
import { BadRequestHttpError } from '../../../../src/util/errors/BadRequestHttpError';
|
||||
import { errorTermsToMetadata } from '../../../../src/util/errors/HttpErrorUtil';
|
||||
import { resolveModulePath } from '../../../../src/util/PathUtil';
|
||||
import { readableToString } from '../../../../src/util/StreamUtil';
|
||||
import type { TemplateEngine } from '../../../../src/util/templates/TemplateEngine';
|
||||
@@ -91,7 +92,8 @@ describe('An ErrorToTemplateConverter', (): void => {
|
||||
});
|
||||
|
||||
it('adds additional information if an error code description is found.', async(): Promise<void> => {
|
||||
const error = new BadRequestHttpError('error text', { errorCode, details: { key: 'val' }});
|
||||
const metadata = errorTermsToMetadata({ key: 'val' });
|
||||
const error = new BadRequestHttpError('error text', { errorCode, metadata });
|
||||
const representation = new BasicRepresentation([ error ], 'internal/error', false);
|
||||
const prom = converter.handle({ identifier, representation, preferences });
|
||||
await expect(prom).resolves.toBeDefined();
|
||||
@@ -154,8 +156,9 @@ describe('An ErrorToTemplateConverter', (): void => {
|
||||
});
|
||||
|
||||
it('has default template options.', async(): Promise<void> => {
|
||||
const metadata = errorTermsToMetadata({ key: 'val' });
|
||||
converter = new ErrorToTemplateConverter(templateEngine);
|
||||
const error = new BadRequestHttpError('error text', { errorCode, details: { key: 'val' }});
|
||||
const error = new BadRequestHttpError('error text', { errorCode, metadata });
|
||||
const representation = new BasicRepresentation([ error ], 'internal/error', false);
|
||||
const prom = converter.handle({ identifier, representation, preferences });
|
||||
await expect(prom).resolves.toBeDefined();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { HttpResponse } from '../../../src/server/HttpResponse';
|
||||
import { BadRequestHttpError } from '../../../src/util/errors/BadRequestHttpError';
|
||||
import { ContentType,
|
||||
addHeader,
|
||||
import { ContentType } from '../../../src/util/Header';
|
||||
import { addHeader,
|
||||
hasScheme,
|
||||
matchesAuthorizationScheme,
|
||||
parseAccept,
|
||||
|
||||
@@ -2,6 +2,8 @@ import { promises as fsPromises } from 'fs';
|
||||
import type { TargetExtractor } from '../../../src/http/input/identifier/TargetExtractor';
|
||||
import type { ResourceIdentifier } from '../../../src/http/representation/ResourceIdentifier';
|
||||
import type { HttpRequest } from '../../../src/server/HttpRequest';
|
||||
import { BadRequestHttpError } from '../../../src/util/errors/BadRequestHttpError';
|
||||
import { extractErrorTerms } from '../../../src/util/errors/HttpErrorUtil';
|
||||
import {
|
||||
absoluteFilePath,
|
||||
createSubdomainRegexp,
|
||||
@@ -218,8 +220,15 @@ describe('PathUtil', (): void => {
|
||||
|
||||
it('errors if the target is outside of the server scope.', async(): Promise<void> => {
|
||||
targetExtractor.handleSafe.mockResolvedValueOnce({ path: 'http://somewhere.else/resource' });
|
||||
await expect(getRelativeUrl(baseUrl, request, targetExtractor)).rejects
|
||||
.toThrow(expect.objectContaining({ errorCode: 'E0001', details: { path: 'http://somewhere.else/resource' }}));
|
||||
let error: unknown;
|
||||
try {
|
||||
await getRelativeUrl(baseUrl, request, targetExtractor);
|
||||
} catch (err: unknown) {
|
||||
error = err;
|
||||
}
|
||||
expect(error).toEqual(expect.objectContaining({ errorCode: 'E0001' }));
|
||||
expect(BadRequestHttpError.isInstance(error)).toBe(true);
|
||||
expect(extractErrorTerms((error as BadRequestHttpError).metadata)).toEqual({ path: 'http://somewhere.else/resource' });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { assertError, createErrorMessage, isError } from '../../../../src/util/errors/ErrorUtil';
|
||||
import { createErrorMessage, isError } from '../../../../src/util/errors/ErrorUtil';
|
||||
|
||||
describe('ErrorUtil', (): void => {
|
||||
describe('#isError', (): void => {
|
||||
@@ -19,16 +19,6 @@ describe('ErrorUtil', (): void => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#assertError', (): void => {
|
||||
it('returns undefined on native errors.', async(): Promise<void> => {
|
||||
expect(assertError(new Error('error'))).toBeUndefined();
|
||||
});
|
||||
|
||||
it('throws on other values.', async(): Promise<void> => {
|
||||
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');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'jest-rdf';
|
||||
import { DataFactory } from 'n3';
|
||||
import { RepresentationMetadata } from '../../../../src/http/representation/RepresentationMetadata';
|
||||
import { BadRequestHttpError } from '../../../../src/util/errors/BadRequestHttpError';
|
||||
import { ConflictHttpError } from '../../../../src/util/errors/ConflictHttpError';
|
||||
import { ForbiddenHttpError } from '../../../../src/util/errors/ForbiddenHttpError';
|
||||
@@ -15,9 +15,7 @@ import { PreconditionFailedHttpError } from '../../../../src/util/errors/Precond
|
||||
import { UnauthorizedHttpError } from '../../../../src/util/errors/UnauthorizedHttpError';
|
||||
import { UnprocessableEntityHttpError } from '../../../../src/util/errors/UnprocessableEntityHttpError';
|
||||
import { UnsupportedMediaTypeHttpError } from '../../../../src/util/errors/UnsupportedMediaTypeHttpError';
|
||||
import { SOLID_ERROR } from '../../../../src/util/Vocabularies';
|
||||
|
||||
const { literal, namedNode, quad } = DataFactory;
|
||||
import { HTTP, SOLID_ERROR } from '../../../../src/util/Vocabularies';
|
||||
|
||||
describe('HttpError', (): void => {
|
||||
const errors: [string, number, HttpErrorClass][] = [
|
||||
@@ -39,7 +37,7 @@ describe('HttpError', (): void => {
|
||||
const options = {
|
||||
cause: new Error('cause'),
|
||||
errorCode: 'E1234',
|
||||
details: {},
|
||||
metadata: new RepresentationMetadata(),
|
||||
};
|
||||
const instance = new constructor('my message', options);
|
||||
|
||||
@@ -75,15 +73,11 @@ describe('HttpError', (): void => {
|
||||
expect(new constructor().errorCode).toBe(`H${statusCode}`);
|
||||
});
|
||||
|
||||
it('sets the details.', (): void => {
|
||||
expect(instance.details).toBe(options.details);
|
||||
});
|
||||
|
||||
it('generates metadata.', (): void => {
|
||||
const subject = namedNode('subject');
|
||||
expect(instance.generateMetadata(subject)).toBeRdfIsomorphic([
|
||||
quad(subject, SOLID_ERROR.terms.errorResponse, constructor.uri),
|
||||
]);
|
||||
it('sets the metadata.', (): void => {
|
||||
expect(instance.metadata).toBe(options.metadata);
|
||||
expect(instance.metadata.get(SOLID_ERROR.terms.errorResponse)?.value)
|
||||
.toBe(`${SOLID_ERROR.namespace}H${statusCode}`);
|
||||
expect(instance.metadata.get(HTTP.terms.statusCodeNumber)?.value).toBe(`${statusCode}`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -92,7 +86,6 @@ describe('HttpError', (): void => {
|
||||
const options = {
|
||||
cause: new Error('cause'),
|
||||
errorCode: 'E1234',
|
||||
details: { some: 'detail' },
|
||||
};
|
||||
const instance = new MethodNotAllowedHttpError([ 'GET' ], 'my message', options);
|
||||
|
||||
@@ -107,11 +100,10 @@ describe('HttpError', (): void => {
|
||||
expect(instance.errorCode).toBe(options.errorCode);
|
||||
expect(new MethodNotAllowedHttpError([ 'GET' ]).errorCode).toBe(`H${405}`);
|
||||
|
||||
const subject = namedNode('subject');
|
||||
expect(instance.generateMetadata(subject)).toBeRdfIsomorphic([
|
||||
quad(subject, SOLID_ERROR.terms.errorResponse, MethodNotAllowedHttpError.uri),
|
||||
quad(subject, SOLID_ERROR.terms.disallowedMethod, literal('GET')),
|
||||
]);
|
||||
expect(instance.metadata.get(SOLID_ERROR.terms.errorResponse)?.value)
|
||||
.toBe(`${SOLID_ERROR.namespace}H405`);
|
||||
expect(instance.metadata.get(HTTP.terms.statusCodeNumber)?.value).toBe('405');
|
||||
expect(instance.metadata.get(SOLID_ERROR.terms.disallowedMethod)?.value).toBe('GET');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,53 @@
|
||||
import { RepresentationMetadata } from '../../../../src/http/representation/RepresentationMetadata';
|
||||
import { HttpError } from '../../../../src/util/errors/HttpError';
|
||||
import { createAggregateError, getStatusCode } from '../../../../src/util/errors/HttpErrorUtil';
|
||||
import { NotFoundHttpError } from '../../../../src/util/errors/NotFoundHttpError';
|
||||
import {
|
||||
createAggregateError,
|
||||
errorTermsToMetadata,
|
||||
extractErrorTerms,
|
||||
} from '../../../../src/util/errors/HttpErrorUtil';
|
||||
import { toPredicateTerm } from '../../../../src/util/TermUtil';
|
||||
|
||||
describe('HttpErrorUtil', (): void => {
|
||||
describe('#errorTermsToMetadata', (): void => {
|
||||
it('creates a metadata object with the necessary triples.', async(): Promise<void> => {
|
||||
const metadata = errorTermsToMetadata({
|
||||
test: 'apple',
|
||||
test2: 'pear',
|
||||
not: undefined,
|
||||
});
|
||||
expect(metadata.quads()).toHaveLength(2);
|
||||
expect(metadata.get(toPredicateTerm('urn:npm:solid:community-server:error-term:test'))?.value).toBe('apple');
|
||||
expect(metadata.get(toPredicateTerm('urn:npm:solid:community-server:error-term:test2'))?.value).toBe('pear');
|
||||
});
|
||||
|
||||
it('can add the necessary triples to existing metadata.', async(): Promise<void> => {
|
||||
const metadata = new RepresentationMetadata();
|
||||
const response = errorTermsToMetadata({
|
||||
test: 'apple',
|
||||
test2: 'pear',
|
||||
not: undefined,
|
||||
}, metadata);
|
||||
expect(response).toBe(metadata);
|
||||
expect(metadata.quads()).toHaveLength(2);
|
||||
expect(metadata.get(toPredicateTerm('urn:npm:solid:community-server:error-term:test'))?.value).toBe('apple');
|
||||
expect(metadata.get(toPredicateTerm('urn:npm:solid:community-server:error-term:test2'))?.value).toBe('pear');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#extractErrorTerms', (): void => {
|
||||
it('returns an object describing the terms.', async(): Promise<void> => {
|
||||
const metadata = new RepresentationMetadata({
|
||||
'urn:npm:solid:community-server:error-term:test': 'apple',
|
||||
'urn:npm:solid:community-server:error-term:test2': 'pear',
|
||||
'urn:npm:solid:community-server:other:test3': 'mango',
|
||||
});
|
||||
expect(extractErrorTerms(metadata)).toEqual({
|
||||
test: 'apple',
|
||||
test2: 'pear',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ErrorUtil', (): void => {
|
||||
describe('#createAggregateError', (): void => {
|
||||
const error401 = new HttpError(401, 'UnauthorizedHttpError');
|
||||
const error415 = new HttpError(415, 'UnsupportedMediaTypeHttpError');
|
||||
@@ -50,14 +95,4 @@ describe('ErrorUtil', (): void => {
|
||||
.toBe('Multiple handler errors: noStatusCode, noStatusCode');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getStatusCode', (): void => {
|
||||
it('returns the corresponding status code for HttpErrors.', async(): Promise<void> => {
|
||||
expect(getStatusCode(new NotFoundHttpError())).toBe(404);
|
||||
});
|
||||
|
||||
it('returns 500 for other errors.', async(): Promise<void> => {
|
||||
expect(getStatusCode(new Error('404'))).toBe(500);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import { RedirectHttpError } from '../../../../src/util/errors/RedirectHttpError
|
||||
import type { RedirectHttpErrorClass } from '../../../../src/util/errors/RedirectHttpError';
|
||||
import { SeeOtherHttpError } from '../../../../src/util/errors/SeeOtherHttpError';
|
||||
import { TemporaryRedirectHttpError } from '../../../../src/util/errors/TemporaryRedirectHttpError';
|
||||
import { HTTP, SOLID_ERROR, SOLID_HTTP } from '../../../../src/util/Vocabularies';
|
||||
|
||||
// Used to make sure the RedirectHttpError constructor also gets called in a test.
|
||||
class FixedRedirectHttpError extends RedirectHttpError {
|
||||
@@ -70,7 +71,10 @@ describe('RedirectHttpError', (): void => {
|
||||
});
|
||||
|
||||
it('sets the details.', (): void => {
|
||||
expect(instance.details).toBe(options.details);
|
||||
expect(instance.metadata.get(SOLID_ERROR.terms.errorResponse)?.value)
|
||||
.toBe(`${SOLID_ERROR.namespace}H${statusCode}`);
|
||||
expect(instance.metadata.get(HTTP.terms.statusCodeNumber)?.value).toBe(`${statusCode}`);
|
||||
expect(instance.metadata.get(SOLID_HTTP.terms.location)?.value).toBe(location);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { ResourceIdentifier } from '../../../../src/http/representation/ResourceIdentifier';
|
||||
import { extractErrorTerms } from '../../../../src/util/errors/HttpErrorUtil';
|
||||
import { InternalServerError } from '../../../../src/util/errors/InternalServerError';
|
||||
import { BaseIdentifierStrategy } from '../../../../src/util/identifiers/BaseIdentifierStrategy';
|
||||
|
||||
class DummyStrategy extends BaseIdentifierStrategy {
|
||||
@@ -21,10 +23,16 @@ describe('A BaseIdentifierStrategy', (): void => {
|
||||
});
|
||||
|
||||
it('errors when attempting to get the parent of an unsupported identifier.', async(): Promise<void> => {
|
||||
expect((): any => strategy.getParentContainer({ path: '/unsupported' }))
|
||||
.toThrow('The identifier /unsupported is outside the configured identifier space.');
|
||||
expect((): any => strategy.getParentContainer({ path: '/unsupported' }))
|
||||
.toThrow(expect.objectContaining({ errorCode: 'E0001', details: { path: '/unsupported' }}));
|
||||
let error: unknown;
|
||||
try {
|
||||
strategy.getParentContainer({ path: '/unsupported' });
|
||||
} catch (err: unknown) {
|
||||
error = err;
|
||||
}
|
||||
expect(error).toEqual(expect.objectContaining({ errorCode: 'E0001',
|
||||
message: 'The identifier /unsupported is outside the configured identifier space.' }));
|
||||
expect(InternalServerError.isInstance(error)).toBe(true);
|
||||
expect(extractErrorTerms((error as InternalServerError).metadata)).toEqual({ path: '/unsupported' });
|
||||
});
|
||||
|
||||
it('errors when attempting to get the parent of a root container.', async(): Promise<void> => {
|
||||
|
||||
Reference in New Issue
Block a user