mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Parse Accept headers as early as possible
This commit is contained in:
@@ -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<RepresentationConverter>;
|
||||
let preferenceParser: jest.Mocked<PreferenceParser>;
|
||||
let handler: ConvertingErrorHandler;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
@@ -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<void> => {
|
||||
(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<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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<void> => {
|
||||
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);
|
||||
|
||||
@@ -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<void> => {
|
||||
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<void> => {
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -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<void> => {
|
||||
@@ -212,17 +213,16 @@ describe('An IdentityProviderFactory', (): void => {
|
||||
it('updates errors if there is more information.', async(): Promise<void> => {
|
||||
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');
|
||||
});
|
||||
|
||||
@@ -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<RequestParser>;
|
||||
let metadataCollector: jest.Mocked<OperationMetadataCollector>;
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user