diff --git a/config/ldp/handler/components/error-handler.json b/config/ldp/handler/components/error-handler.json index 71565948e..44e4baef6 100644 --- a/config/ldp/handler/components/error-handler.json +++ b/config/ldp/handler/components/error-handler.json @@ -17,6 +17,7 @@ "comment": "Converts an Error object into a representation for an HTTP response.", "@type": "ConvertingErrorHandler", "converter": { "@id": "urn:solid-server:default:UiEnabledConverter" }, + "preferenceParser": { "@id": "urn:solid-server:default:PreferenceParser" }, "showStackTrace": { "@id": "urn:solid-server:default:variable:showStackTrace" } } ] diff --git a/config/ldp/handler/components/preferences.json b/config/ldp/handler/components/preferences.json new file mode 100644 index 000000000..779fb4bd2 --- /dev/null +++ b/config/ldp/handler/components/preferences.json @@ -0,0 +1,9 @@ +{ + "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^5.0.0/components/context.jsonld", + "@graph": [ + { + "@id": "urn:solid-server:default:PreferenceParser", + "@type": "AcceptPreferenceParser" + } + ] +} diff --git a/config/ldp/handler/components/request-parser.json b/config/ldp/handler/components/request-parser.json index b16a6cb64..ad4818bf9 100644 --- a/config/ldp/handler/components/request-parser.json +++ b/config/ldp/handler/components/request-parser.json @@ -11,7 +11,7 @@ "args_identifierStrategy": { "@id": "urn:solid-server:default:IdentifierStrategy" }, "args_includeQueryString": false }, - "args_preferenceParser": { "@type": "AcceptPreferenceParser" }, + "args_preferenceParser": { "@id": "urn:solid-server:default:PreferenceParser" }, "args_metadataParser": { "@id": "urn:solid-server:default:MetadataParser" }, "args_conditionsParser": { "@type": "BasicConditionsParser" }, "args_bodyParser": { diff --git a/config/ldp/handler/default.json b/config/ldp/handler/default.json index 1a2b8e754..7c346f00e 100644 --- a/config/ldp/handler/default.json +++ b/config/ldp/handler/default.json @@ -5,6 +5,7 @@ "css:config/ldp/handler/components/error-handler.json", "css:config/ldp/handler/components/operation-handler.json", "css:config/ldp/handler/components/operation-metadata.json", + "css:config/ldp/handler/components/preferences.json", "css:config/ldp/handler/components/request-parser.json", "css:config/ldp/handler/components/response-writer.json" ], diff --git a/src/http/input/BasicRequestParser.ts b/src/http/input/BasicRequestParser.ts index 5698057cb..0accd357c 100644 --- a/src/http/input/BasicRequestParser.ts +++ b/src/http/input/BasicRequestParser.ts @@ -22,19 +22,23 @@ export interface BasicRequestParserArgs { /** * Creates an {@link Operation} from an incoming {@link HttpRequest} by aggregating the results - * of a {@link TargetExtractor}, {@link PreferenceParser}, {@link MetadataParser}, + * of a {@link TargetExtractor}, {@link MetadataParser}, * {@link ConditionsParser} and {@link BodyParser}. */ export class BasicRequestParser extends RequestParser { - private readonly targetExtractor!: TargetExtractor; - private readonly preferenceParser!: PreferenceParser; - private readonly metadataParser!: MetadataParser; - private readonly conditionsParser!: ConditionsParser; - private readonly bodyParser!: BodyParser; + private readonly targetExtractor: TargetExtractor; + private readonly preferenceParser: PreferenceParser; + private readonly metadataParser: MetadataParser; + private readonly conditionsParser: ConditionsParser; + private readonly bodyParser: BodyParser; public constructor(args: BasicRequestParserArgs) { super(); - Object.assign(this, args); + this.targetExtractor = args.targetExtractor; + this.preferenceParser = args.preferenceParser; + this.metadataParser = args.metadataParser; + this.conditionsParser = args.conditionsParser; + this.bodyParser = args.bodyParser; } public async handle(request: HttpRequest): Promise { diff --git a/src/http/output/error/ConvertingErrorHandler.ts b/src/http/output/error/ConvertingErrorHandler.ts index 7bd6faecd..e3645a60f 100644 --- a/src/http/output/error/ConvertingErrorHandler.ts +++ b/src/http/output/error/ConvertingErrorHandler.ts @@ -6,6 +6,7 @@ import { INTERNAL_ERROR } from '../../../util/ContentTypes'; import { getStatusCode } from '../../../util/errors/HttpErrorUtil'; import { toLiteral } from '../../../util/TermUtil'; import { HTTP, XSD } from '../../../util/Vocabularies'; +import type { PreferenceParser } from '../../input/preferences/PreferenceParser'; import { BasicRepresentation } from '../../representation/BasicRepresentation'; import type { Representation } from '../../representation/Representation'; import { RepresentationMetadata } from '../../representation/RepresentationMetadata'; @@ -25,22 +26,25 @@ type PreparedArguments = { */ export class ConvertingErrorHandler extends ErrorHandler { private readonly converter: RepresentationConverter; + private readonly preferenceParser: PreferenceParser; private readonly showStackTrace: boolean; - public constructor(converter: RepresentationConverter, showStackTrace = false) { + public constructor(converter: RepresentationConverter, preferenceParser: PreferenceParser, showStackTrace = false) { super(); this.converter = converter; + this.preferenceParser = preferenceParser; this.showStackTrace = showStackTrace; } public async canHandle(input: ErrorHandlerArgs): Promise { - const { conversionArgs } = this.prepareArguments(input); + await this.preferenceParser.canHandle({ request: input.request }); + const { conversionArgs } = await this.extractErrorDetails(input); await this.converter.canHandle(conversionArgs); } public async handle(input: ErrorHandlerArgs): Promise { - const { statusCode, conversionArgs } = this.prepareArguments(input); + const { statusCode, conversionArgs } = await this.extractErrorDetails(input); const converted = await this.converter.handle(conversionArgs); @@ -48,7 +52,8 @@ export class ConvertingErrorHandler extends ErrorHandler { } public async handleSafe(input: ErrorHandlerArgs): Promise { - const { statusCode, conversionArgs } = this.prepareArguments(input); + await this.preferenceParser.canHandle({ request: input.request }); + const { statusCode, conversionArgs } = await this.extractErrorDetails(input); const converted = await this.converter.handleSafe(conversionArgs); @@ -58,10 +63,11 @@ export class ConvertingErrorHandler extends ErrorHandler { /** * Prepares the arguments used by all functions. */ - private prepareArguments({ error, preferences }: ErrorHandlerArgs): PreparedArguments { + private async extractErrorDetails({ error, request }: ErrorHandlerArgs): Promise { const statusCode = getStatusCode(error); const representation = this.toRepresentation(error, statusCode); const identifier = { path: representation.metadata.identifier.value }; + const preferences = await this.preferenceParser.handle({ request }); return { statusCode, conversionArgs: { identifier, representation, preferences }}; } diff --git a/src/http/output/error/ErrorHandler.ts b/src/http/output/error/ErrorHandler.ts index 9f10127b4..c78ab7864 100644 --- a/src/http/output/error/ErrorHandler.ts +++ b/src/http/output/error/ErrorHandler.ts @@ -1,10 +1,10 @@ +import type { HttpRequest } from '../../../server/HttpRequest'; import { AsyncHandler } from '../../../util/handlers/AsyncHandler'; -import type { RepresentationPreferences } from '../../representation/RepresentationPreferences'; import type { ResponseDescription } from '../response/ResponseDescription'; export interface ErrorHandlerArgs { error: Error; - preferences: RepresentationPreferences; + request: HttpRequest; } /** diff --git a/src/identity/configuration/IdentityProviderFactory.ts b/src/identity/configuration/IdentityProviderFactory.ts index a3cdec659..1fee6c6fb 100644 --- a/src/identity/configuration/IdentityProviderFactory.ts +++ b/src/identity/configuration/IdentityProviderFactory.ts @@ -19,6 +19,7 @@ import { BasicRepresentation } from '../../http/representation/BasicRepresentati import type { KeyValueStorage } from '../../storage/keyvalue/KeyValueStorage'; import { InternalServerError } from '../../util/errors/InternalServerError'; import { RedirectHttpError } from '../../util/errors/RedirectHttpError'; +import { guardStream } from '../../util/GuardedStream'; import { joinUrl } from '../../util/PathUtil'; import type { ClientCredentials } from '../interaction/email-password/credentials/ClientCredentialsAdapterFactory'; import type { InteractionHandler } from '../interaction/InteractionHandler'; @@ -325,7 +326,7 @@ export class IdentityProviderFactory implements ProviderFactory { error.message += ` - ${out.error_description}`; } - const result = await this.errorHandler.handleSafe({ error, preferences: { type: { 'text/plain': 1 }}}); + const result = await this.errorHandler.handleSafe({ error, request: guardStream(ctx.req) }); await this.responseWriter.handleSafe({ response: ctx.res, result }); }; } diff --git a/src/server/ParsingHttpHandler.ts b/src/server/ParsingHttpHandler.ts index ded1c64e9..0e9d2e83f 100644 --- a/src/server/ParsingHttpHandler.ts +++ b/src/server/ParsingHttpHandler.ts @@ -3,12 +3,13 @@ import type { OperationMetadataCollector } from '../http/ldp/metadata/OperationM import type { ErrorHandler } from '../http/output/error/ErrorHandler'; import type { ResponseDescription } from '../http/output/response/ResponseDescription'; import type { ResponseWriter } from '../http/output/ResponseWriter'; -import type { RepresentationPreferences } from '../http/representation/RepresentationPreferences'; import { getLoggerFor } from '../logging/LogUtil'; import { assertError } from '../util/errors/ErrorUtil'; import { HttpError } from '../util/errors/HttpError'; import type { HttpHandlerInput } from './HttpHandler'; import { HttpHandler } from './HttpHandler'; +import type { HttpRequest } from './HttpRequest'; +import type { HttpResponse } from './HttpResponse'; import type { OperationHttpHandler } from './OperationHttpHandler'; export interface ParsingHttpHandlerArgs { @@ -35,9 +36,9 @@ export interface ParsingHttpHandlerArgs { } /** - * Parses requests and sends the resulting Operation to wrapped operationHandler. - * Errors are caught and handled by the Errorhandler. - * In case the operationHandler returns a result it will be sent to the ResponseWriter. + * Parses requests and sends the resulting {@link Operation} to the wrapped {@link OperationHttpHandler}. + * Errors are caught and handled by the {@link ErrorHandler}. + * In case the {@link OperationHttpHandler} returns a result it will be sent to the {@link ResponseWriter}. */ export class ParsingHttpHandler extends HttpHandler { private readonly logger = getLoggerFor(this); @@ -58,30 +59,45 @@ export class ParsingHttpHandler extends HttpHandler { } public async handle({ request, response }: HttpHandlerInput): Promise { - let result: ResponseDescription | undefined; - let preferences: RepresentationPreferences = { type: { 'text/plain': 1 }}; + let result: ResponseDescription; try { - const operation = await this.requestParser.handleSafe(request); - ({ preferences } = operation); - result = await this.operationHandler.handleSafe({ operation, request, response }); - - if (result?.metadata) { - await this.metadataCollector.handleSafe({ operation, metadata: result.metadata }); - } - - this.logger.verbose(`Parsed ${operation.method} operation on ${operation.target.path}`); + result = await this.handleRequest(request, response); } catch (error: unknown) { - assertError(error); - result = await this.errorHandler.handleSafe({ error, preferences }); - if (HttpError.isInstance(error) && result.metadata) { - const quads = error.generateMetadata(result.metadata.identifier); - result.metadata.addQuads(quads); - } + result = await this.handleError(error, request); } if (result) { await this.responseWriter.handleSafe({ response, result }); } } + + /** + * Interprets the request and passes the generated Operation object to the stored OperationHttpHandler. + */ + protected async handleRequest(request: HttpRequest, response: HttpResponse): + Promise { + const operation = await this.requestParser.handleSafe(request); + const result = await this.operationHandler.handleSafe({ operation, request, response }); + + if (result?.metadata) { + await this.metadataCollector.handleSafe({ operation, metadata: result.metadata }); + } + + this.logger.verbose(`Parsed ${operation.method} operation on ${operation.target.path}`); + return result; + } + + /** + * Handles the error output correctly based on the preferences. + */ + protected async handleError(error: unknown, request: HttpRequest): Promise { + assertError(error); + const result = await this.errorHandler.handleSafe({ error, request }); + if (HttpError.isInstance(error) && result.metadata) { + const quads = error.generateMetadata(result.metadata.identifier); + result.metadata.addQuads(quads); + } + return result; + } } diff --git a/test/unit/http/output/error/ConvertingErrorHandler.test.ts b/test/unit/http/output/error/ConvertingErrorHandler.test.ts index 6a359beed..8602455ad 100644 --- a/test/unit/http/output/error/ConvertingErrorHandler.test.ts +++ b/test/unit/http/output/error/ConvertingErrorHandler.test.ts @@ -1,10 +1,12 @@ import 'jest-rdf'; import arrayifyStream from 'arrayify-stream'; import { DataFactory } from 'n3'; +import type { PreferenceParser } from '../../../../../src/http/input/preferences/PreferenceParser'; import { ConvertingErrorHandler } from '../../../../../src/http/output/error/ConvertingErrorHandler'; import { BasicRepresentation } from '../../../../../src/http/representation/BasicRepresentation'; import type { Representation } from '../../../../../src/http/representation/Representation'; import type { RepresentationPreferences } from '../../../../../src/http/representation/RepresentationPreferences'; +import type { HttpRequest } from '../../../../../src/server/HttpRequest'; import type { RepresentationConverter, RepresentationConverterArgs, @@ -33,7 +35,9 @@ describe('A ConvertingErrorHandler', (): void => { // The error object can get modified by the handler let error: Error; let stack: string | undefined; - let converter: RepresentationConverter; + const request = {} as HttpRequest; + let converter: jest.Mocked; + let preferenceParser: jest.Mocked; let handler: ConvertingErrorHandler; beforeEach(async(): Promise => { @@ -45,20 +49,33 @@ describe('A ConvertingErrorHandler', (): void => { handleSafe: jest.fn((): Representation => new BasicRepresentation('serialization', 'text/turtle', true)), } as any; - handler = new ConvertingErrorHandler(converter, true); + preferenceParser = { + canHandle: jest.fn(), + handle: jest.fn().mockResolvedValue(preferences), + } as any; + + handler = new ConvertingErrorHandler(converter, preferenceParser, true); }); it('rejects input not supported by the converter.', async(): Promise => { - (converter.canHandle as jest.Mock).mockRejectedValueOnce(new Error('rejected')); - await expect(handler.canHandle({ error, preferences })).rejects.toThrow('rejected'); + converter.canHandle.mockRejectedValueOnce(new Error('rejected')); + await expect(handler.canHandle({ error, request })).rejects.toThrow('rejected'); expect(converter.canHandle).toHaveBeenCalledTimes(1); - const args = (converter.canHandle as jest.Mock).mock.calls[0][0] as RepresentationConverterArgs; + const args = converter.canHandle.mock.calls[0][0]; expect(args.preferences).toBe(preferences); expect(args.representation.metadata.contentType).toBe('internal/error'); }); + it('rejects input not supported by the preference parser.', async(): Promise => { + preferenceParser.canHandle.mockRejectedValueOnce(new Error('rejected')); + await expect(handler.canHandle({ error, request })).rejects.toThrow('rejected'); + expect(preferenceParser.canHandle).toHaveBeenCalledTimes(1); + expect(preferenceParser.canHandle).toHaveBeenLastCalledWith({ request }); + expect(converter.canHandle).toHaveBeenCalledTimes(0); + }); + it('accepts input supported by the converter.', async(): Promise => { - await expect(handler.canHandle({ error, preferences })).resolves.toBeUndefined(); + await expect(handler.canHandle({ error, request })).resolves.toBeUndefined(); expect(converter.canHandle).toHaveBeenCalledTimes(1); const args = (converter.canHandle as jest.Mock).mock.calls[0][0] as RepresentationConverterArgs; expect(args.preferences).toBe(preferences); @@ -66,7 +83,7 @@ describe('A ConvertingErrorHandler', (): void => { }); it('returns the converted error response.', async(): Promise => { - const prom = handler.handle({ error, preferences }); + const prom = handler.handle({ error, request }); await expect(prom).resolves.toMatchObject({ statusCode: 404 }); expect((await prom).metadata?.contentType).toBe('text/turtle'); expect(converter.handle).toHaveBeenCalledTimes(1); @@ -75,7 +92,7 @@ describe('A ConvertingErrorHandler', (): void => { }); it('uses the handleSafe function of the converter during its own handleSafe call.', async(): Promise => { - const prom = handler.handleSafe({ error, preferences }); + const prom = handler.handleSafe({ error, request }); await expect(prom).resolves.toMatchObject({ statusCode: 404 }); expect((await prom).metadata?.contentType).toBe('text/turtle'); expect(converter.handleSafe).toHaveBeenCalledTimes(1); @@ -84,8 +101,8 @@ describe('A ConvertingErrorHandler', (): void => { }); it('hides the stack trace if the option is disabled.', async(): Promise => { - handler = new ConvertingErrorHandler(converter); - const prom = handler.handle({ error, preferences }); + handler = new ConvertingErrorHandler(converter, preferenceParser); + const prom = handler.handle({ error, request }); await expect(prom).resolves.toMatchObject({ statusCode: 404 }); expect((await prom).metadata?.contentType).toBe('text/turtle'); expect(converter.handle).toHaveBeenCalledTimes(1); diff --git a/test/unit/http/output/error/RedirectingErrorHandler.test.ts b/test/unit/http/output/error/RedirectingErrorHandler.test.ts index 1cdd2046b..afa5020cb 100644 --- a/test/unit/http/output/error/RedirectingErrorHandler.test.ts +++ b/test/unit/http/output/error/RedirectingErrorHandler.test.ts @@ -1,24 +1,25 @@ import { RedirectingErrorHandler } from '../../../../../src/http/output/error/RedirectingErrorHandler'; +import type { HttpRequest } from '../../../../../src/server/HttpRequest'; import { BadRequestHttpError } from '../../../../../src/util/errors/BadRequestHttpError'; import { FoundHttpError } from '../../../../../src/util/errors/FoundHttpError'; import { NotImplementedHttpError } from '../../../../../src/util/errors/NotImplementedHttpError'; import { SOLID_HTTP } from '../../../../../src/util/Vocabularies'; describe('A RedirectingErrorHandler', (): void => { - const preferences = {}; + const request = {} as HttpRequest; const handler = new RedirectingErrorHandler(); it('only accepts redirect errors.', async(): Promise => { const unsupportedError = new BadRequestHttpError(); - await expect(handler.canHandle({ error: unsupportedError, preferences })).rejects.toThrow(NotImplementedHttpError); + await expect(handler.canHandle({ error: unsupportedError, request })).rejects.toThrow(NotImplementedHttpError); const supportedError = new FoundHttpError('http://test.com/foo/bar'); - await expect(handler.canHandle({ error: supportedError, preferences })).resolves.toBeUndefined(); + await expect(handler.canHandle({ error: supportedError, request })).resolves.toBeUndefined(); }); it('creates redirect responses.', async(): Promise => { const error = new FoundHttpError('http://test.com/foo/bar'); - const result = await handler.handle({ error, preferences }); + const result = await handler.handle({ error, request }); expect(result.statusCode).toBe(error.statusCode); expect(result.metadata?.get(SOLID_HTTP.terms.location)?.value).toBe(error.location); }); diff --git a/test/unit/identity/configuration/IdentityProviderFactory.test.ts b/test/unit/identity/configuration/IdentityProviderFactory.test.ts index 1786d6add..92ef028f1 100644 --- a/test/unit/identity/configuration/IdentityProviderFactory.test.ts +++ b/test/unit/identity/configuration/IdentityProviderFactory.test.ts @@ -1,3 +1,4 @@ +import { Readable } from 'stream'; import type { Configuration, KoaContextWithOIDC } from 'oidc-provider'; import type { ErrorHandler } from '../../../../src/http/output/error/ErrorHandler'; import type { ResponseWriter } from '../../../../src/http/output/ResponseWriter'; @@ -8,7 +9,6 @@ import type { } from '../../../../src/identity/interaction/email-password/credentials/ClientCredentialsAdapterFactory'; import type { Interaction, InteractionHandler } from '../../../../src/identity/interaction/InteractionHandler'; import type { AdapterFactory } from '../../../../src/identity/storage/AdapterFactory'; -import type { HttpResponse } from '../../../../src/server/HttpResponse'; import type { KeyValueStorage } from '../../../../src/storage/keyvalue/KeyValueStorage'; import { FoundHttpError } from '../../../../src/util/errors/FoundHttpError'; @@ -53,6 +53,8 @@ describe('An IdentityProviderFactory', (): void => { ctx = { method: 'GET', + req: Readable.from('data'), + res: {}, request: { href: 'http://example.com/idp/', }, @@ -143,13 +145,12 @@ describe('An IdentityProviderFactory', (): void => { }); // Test the renderError function - const response = { } as HttpResponse; - await expect((config.renderError as any)({ res: response }, {}, 'error!')).resolves.toBeUndefined(); + await expect((config.renderError as any)(ctx, {}, 'error!')).resolves.toBeUndefined(); expect(errorHandler.handleSafe).toHaveBeenCalledTimes(1); expect(errorHandler.handleSafe) - .toHaveBeenLastCalledWith({ error: 'error!', preferences: { type: { 'text/plain': 1 }}}); + .toHaveBeenLastCalledWith({ error: 'error!', request: ctx.req }); expect(responseWriter.handleSafe).toHaveBeenCalledTimes(1); - expect(responseWriter.handleSafe).toHaveBeenLastCalledWith({ response, result: { statusCode: 500 }}); + expect(responseWriter.handleSafe).toHaveBeenLastCalledWith({ response: ctx.res, result: { statusCode: 500 }}); }); it('errors if there is no valid interaction redirect.', async(): Promise => { @@ -212,17 +213,16 @@ describe('An IdentityProviderFactory', (): void => { it('updates errors if there is more information.', async(): Promise => { const provider = await factory.getProvider() as any; const { config } = provider as { config: Configuration }; - const response = { } as HttpResponse; const error = new Error('bad data'); const out = { error_description: 'more info' }; - await expect((config.renderError as any)({ res: response }, out, error)).resolves.toBeUndefined(); + await expect((config.renderError as any)(ctx, out, error)).resolves.toBeUndefined(); expect(errorHandler.handleSafe).toHaveBeenCalledTimes(1); expect(errorHandler.handleSafe) - .toHaveBeenLastCalledWith({ error, preferences: { type: { 'text/plain': 1 }}}); + .toHaveBeenLastCalledWith({ error, request: ctx.req }); expect(responseWriter.handleSafe).toHaveBeenCalledTimes(1); - expect(responseWriter.handleSafe).toHaveBeenLastCalledWith({ response, result: { statusCode: 500 }}); + expect(responseWriter.handleSafe).toHaveBeenLastCalledWith({ response: ctx.res, result: { statusCode: 500 }}); expect(error.message).toBe('bad data - more info'); expect(error.stack).toContain('Error: bad data - more info'); }); diff --git a/test/unit/server/ParsingHttpHandler.test.ts b/test/unit/server/ParsingHttpHandler.test.ts index 476d7940c..013eba5ca 100644 --- a/test/unit/server/ParsingHttpHandler.test.ts +++ b/test/unit/server/ParsingHttpHandler.test.ts @@ -16,9 +16,8 @@ import { HttpError } from '../../../src/util/errors/HttpError'; describe('A ParsingHttpHandler', (): void => { const request: HttpRequest = {} as any; const response: HttpResponse = {} as any; - const preferences = { type: { 'text/html': 1 }}; const body = new BasicRepresentation(); - const operation: Operation = { method: 'GET', target: { path: 'http://test.com/foo' }, preferences, body }; + const operation: Operation = { method: 'GET', target: { path: 'http://test.com/foo' }, preferences: {}, body }; const errorResponse = new ResponseDescription(400); let requestParser: jest.Mocked; let metadataCollector: jest.Mocked; @@ -75,7 +74,7 @@ describe('A ParsingHttpHandler', (): void => { source.handleSafe.mockRejectedValueOnce(error); await expect(handler.handle({ request, response })).resolves.toBeUndefined(); expect(errorHandler.handleSafe).toHaveBeenCalledTimes(1); - expect(errorHandler.handleSafe).toHaveBeenLastCalledWith({ error, preferences }); + expect(errorHandler.handleSafe).toHaveBeenLastCalledWith({ error, request }); expect(responseWriter.handleSafe).toHaveBeenCalledTimes(1); expect(responseWriter.handleSafe).toHaveBeenLastCalledWith({ response, result: errorResponse }); }); @@ -87,7 +86,7 @@ describe('A ParsingHttpHandler', (): void => { errorHandler.handleSafe.mockResolvedValueOnce(metaResponse); await expect(handler.handle({ request, response })).resolves.toBeUndefined(); expect(errorHandler.handleSafe).toHaveBeenCalledTimes(1); - expect(errorHandler.handleSafe).toHaveBeenLastCalledWith({ error, preferences }); + expect(errorHandler.handleSafe).toHaveBeenLastCalledWith({ error, request }); expect(responseWriter.handleSafe).toHaveBeenCalledTimes(1); expect(responseWriter.handleSafe).toHaveBeenLastCalledWith({ response, result: metaResponse }); expect(metaResponse.metadata?.quads()).toHaveLength(1);