Merge branch 'main' into versions/next-major

This commit is contained in:
Joachim Van Herwegen
2023-07-25 09:43:15 +02:00
39 changed files with 801 additions and 242 deletions

View File

@@ -49,36 +49,33 @@ describe('An AllowAcceptHeaderWriter', (): void => {
expect(headers['accept-post']).toBeUndefined();
});
it('returns all methods for an empty container.', async(): Promise<void> => {
it('returns all methods except PUT for an empty container.', async(): Promise<void> => {
await expect(writer.handleSafe({ response, metadata: emptyContainer })).resolves.toBeUndefined();
const headers = response.getHeaders();
expect(typeof headers.allow).toBe('string');
expect(new Set((headers.allow as string).split(', ')))
.toEqual(new Set([ 'OPTIONS', 'GET', 'HEAD', 'PUT', 'POST', 'PATCH', 'DELETE' ]));
.toEqual(new Set([ 'OPTIONS', 'GET', 'HEAD', 'POST', 'PATCH', 'DELETE' ]));
expect(headers['accept-patch']).toBe('text/n3, application/sparql-update');
expect(headers['accept-put']).toBe('*/*');
expect(headers['accept-post']).toBe('*/*');
});
it('returns all methods except DELETE for a non-empty container.', async(): Promise<void> => {
it('returns all methods except PUT/DELETE for a non-empty container.', async(): Promise<void> => {
await expect(writer.handleSafe({ response, metadata: fullContainer })).resolves.toBeUndefined();
const headers = response.getHeaders();
expect(typeof headers.allow).toBe('string');
expect(new Set((headers.allow as string).split(', ')))
.toEqual(new Set([ 'OPTIONS', 'GET', 'HEAD', 'PUT', 'POST', 'PATCH' ]));
.toEqual(new Set([ 'OPTIONS', 'GET', 'HEAD', 'POST', 'PATCH' ]));
expect(headers['accept-patch']).toBe('text/n3, application/sparql-update');
expect(headers['accept-put']).toBe('*/*');
expect(headers['accept-post']).toBe('*/*');
});
it('returns all methods except DELETE for a storage container.', async(): Promise<void> => {
it('returns all methods except PUT/DELETE for a storage container.', async(): Promise<void> => {
await expect(writer.handleSafe({ response, metadata: storageContainer })).resolves.toBeUndefined();
const headers = response.getHeaders();
expect(typeof headers.allow).toBe('string');
expect(new Set((headers.allow as string).split(', ')))
.toEqual(new Set([ 'OPTIONS', 'GET', 'HEAD', 'PUT', 'POST', 'PATCH' ]));
.toEqual(new Set([ 'OPTIONS', 'GET', 'HEAD', 'POST', 'PATCH' ]));
expect(headers['accept-patch']).toBe('text/n3, application/sparql-update');
expect(headers['accept-put']).toBe('*/*');
expect(headers['accept-post']).toBe('*/*');
});

View File

@@ -1,12 +1,24 @@
import { PassThrough } from 'stream';
import type { Logger } from 'winston';
import type * as Transport from 'winston-transport';
import { WinstonLogger } from '../../../src/logging/WinstonLogger';
import { WinstonLoggerFactory } from '../../../src/logging/WinstonLoggerFactory';
const now = new Date();
jest.useFakeTimers();
jest.setSystemTime(now);
describe('WinstonLoggerFactory', (): void => {
let factory: WinstonLoggerFactory;
let transport: jest.Mocked<Transport>;
beforeEach(async(): Promise<void> => {
factory = new WinstonLoggerFactory('debug');
// Create a dummy log transport
transport = new PassThrough({ objectMode: true }) as any;
transport.write = jest.fn();
transport.log = jest.fn();
});
it('creates WinstonLoggers.', async(): Promise<void> => {
@@ -19,10 +31,6 @@ describe('WinstonLoggerFactory', (): void => {
});
it('allows WinstonLoggers to be invoked.', async(): Promise<void> => {
// Create a dummy log transport
const transport: any = new PassThrough({ objectMode: true });
transport.write = jest.fn();
transport.log = jest.fn();
(factory as any).createTransports = (): any => [ transport ];
// Create logger, and log
@@ -30,15 +38,39 @@ describe('WinstonLoggerFactory', (): void => {
logger.log('debug', 'my message');
expect(transport.write).toHaveBeenCalledTimes(1);
// Need to check level like this as it has color tags
const { level } = transport.write.mock.calls[0][0];
expect(transport.write).toHaveBeenCalledWith({
label: 'MyLabel',
level: expect.stringContaining('debug'),
level,
message: 'my message',
timestamp: expect.any(String),
metadata: expect.any(Object),
timestamp: now.toISOString(),
metadata: {},
[Symbol.for('level')]: 'debug',
[Symbol.for('splat')]: [ undefined ],
[Symbol.for('message')]: expect.any(String),
[Symbol.for('message')]: `${now.toISOString()} [MyLabel] {W-???} ${level}: my message`,
});
});
it('allows extra metadata when logging to indicate the thread.', async(): Promise<void> => {
(factory as any).createTransports = (): any => [ transport ];
// Create logger, and log
const logger = factory.createLogger('MyLabel');
logger.log('debug', 'my message', { isPrimary: true, pid: 0 });
expect(transport.write).toHaveBeenCalledTimes(1);
// Need to check level like this as it has color tags
const { level } = transport.write.mock.calls[0][0];
expect(transport.write).toHaveBeenCalledWith(expect.objectContaining({
label: 'MyLabel',
level,
message: 'my message',
timestamp: now.toISOString(),
metadata: { isPrimary: true, pid: 0 },
[Symbol.for('level')]: 'debug',
[Symbol.for('splat')]: [ undefined ],
[Symbol.for('message')]: `${now.toISOString()} [MyLabel] {Primary} ${level}: my message`,
}));
});
});

View File

@@ -83,8 +83,8 @@ describe('A StorageDescriptionHandler', (): void => {
expect(result.metadata?.contentType).toBe('internal/quads');
expect(result.data).toBeDefined();
const quads = await readableToQuads(result.data!);
expect(quads.countQuads(operation.target.path, RDF.terms.type, PIM.terms.Storage, null)).toBe(1);
expect(quads.countQuads('http://example.com/', RDF.terms.type, PIM.terms.Storage, null)).toBe(1);
expect(describer.handle).toHaveBeenCalledTimes(1);
expect(describer.handle).toHaveBeenLastCalledWith(operation.target);
expect(describer.handle).toHaveBeenLastCalledWith({ path: 'http://example.com/' });
});
});

View File

@@ -43,14 +43,14 @@ describe('A WebhookChannel2023Type', (): void => {
beforeEach(async(): Promise<void> => {
data = new Store();
data.addQuad(quad(subject, RDF.terms.type, NOTIFY.terms.WebHookChannel2023));
data.addQuad(quad(subject, RDF.terms.type, NOTIFY.terms.WebhookChannel2023));
data.addQuad(quad(subject, NOTIFY.terms.topic, namedNode(topic)));
data.addQuad(quad(subject, NOTIFY.terms.sendTo, namedNode(sendTo)));
const id = 'http://example.com/webhooks/4c9b88c1-7502-4107-bb79-2a3a590c7aa3';
channel = {
id,
type: NOTIFY.WebHookChannel2023,
type: NOTIFY.WebhookChannel2023,
topic: 'https://storage.example/resource',
sendTo,
};
@@ -79,7 +79,7 @@ describe('A WebhookChannel2023Type', (): void => {
CONTEXT_NOTIFICATION,
],
id: channel.id,
type: NOTIFY.WebHookChannel2023,
type: NOTIFY.WebhookChannel2023,
sendTo,
topic,
sender: 'http://example.com/webhooks/webid',

View File

@@ -45,7 +45,7 @@ describe('A WebHookEmitter', (): void => {
const channel: WebhookChannel2023 = {
id: 'id',
topic: 'http://example.com/foo',
type: NOTIFY.WebHookChannel2023,
type: NOTIFY.WebhookChannel2023,
sendTo: 'http://example.org/somewhere-else',
};

View File

@@ -53,32 +53,63 @@ describe('HeaderUtil', (): void => {
]);
});
it('rejects Accept Headers with invalid types.', async(): Promise<void> => {
expect((): any => parseAccept('*')).toThrow('Invalid Accept range:');
expect((): any => parseAccept('"bad"/text')).toThrow('Invalid Accept range:');
expect((): any => parseAccept('*/\\bad')).toThrow('Invalid Accept range:');
expect((): any => parseAccept('*/*')).not.toThrow('Invalid Accept range:');
it('ignores Accept Headers with invalid types.', async(): Promise<void> => {
expect(parseAccept('*')).toEqual([]);
expect(parseAccept('"bad"/text')).toEqual([]);
expect(parseAccept('*/\\bad')).toEqual([]);
expect(parseAccept('*/*')).toEqual([{
parameters: { extension: {}, mediaType: {}}, range: '*/*', weight: 1,
}]);
});
it('rejects Accept Headers with invalid q values.', async(): Promise<void> => {
expect((): any => parseAccept('a/b; q=text')).toThrow('Invalid q value:');
expect((): any => parseAccept('a/b; q=0.1234')).toThrow('Invalid q value:');
expect((): any => parseAccept('a/b; q=1.1')).toThrow('Invalid q value:');
expect((): any => parseAccept('a/b; q=1.000')).not.toThrow();
expect((): any => parseAccept('a/b; q=0.123')).not.toThrow();
it('ignores the weight of Accept Headers with q values it can not parse.', async(): Promise<void> => {
expect(parseAccept('a/b; q=text')).toEqual([{
range: 'a/b', weight: 1, parameters: { extension: {}, mediaType: {}},
}]);
// Invalid Q value but can be parsed
expect(parseAccept('a/b; q=0.1234')).toEqual([{
range: 'a/b', weight: 0.1234, parameters: { extension: {}, mediaType: {}},
}]);
expect(parseAccept('a/b; q=1.1')).toEqual([{
range: 'a/b', weight: 1, parameters: { extension: {}, mediaType: {}},
}]);
expect(parseAccept('a/b; q=1.000')).toEqual([{
range: 'a/b', weight: 1, parameters: { extension: {}, mediaType: {}},
}]);
expect(parseAccept('a/b; q=-5')).toEqual([{
range: 'a/b', weight: 0, parameters: { extension: {}, mediaType: {}},
}]);
expect(parseAccept('a/b; q=0.123')).toEqual([{
range: 'a/b', weight: 0.123, parameters: { extension: {}, mediaType: {}},
}]);
});
it('rejects Accept Headers with invalid parameters.', async(): Promise<void> => {
expect((): any => parseAccept('a/b; a')).toThrow('Invalid Accept parameter');
expect((): any => parseAccept('a/b; a=\\')).toThrow('Invalid parameter value');
expect((): any => parseAccept('a/b; q=1 ; a=\\')).toThrow('Invalid parameter value');
expect((): any => parseAccept('a/b; q=1 ; a')).not.toThrow('Invalid Accept parameter');
it('ignores Accept Headers with invalid parameters.', async(): Promise<void> => {
expect(parseAccept('a/b; a')).toEqual([{
range: 'a/b', weight: 1, parameters: { extension: {}, mediaType: {}},
}]);
expect(parseAccept('a/b; a=\\')).toEqual([{
range: 'a/b', weight: 1, parameters: { extension: {}, mediaType: {}},
}]);
expect(parseAccept('a/b; q=1 ; a=\\')).toEqual([{
range: 'a/b', weight: 1, parameters: { extension: {}, mediaType: {}},
}]);
expect(parseAccept('a/b; q=1 ; a')).toEqual([{
// eslint-disable-next-line id-length
range: 'a/b', weight: 1, parameters: { extension: { a: '' }, mediaType: {}},
}]);
});
it('rejects Accept Headers with quoted parameters.', async(): Promise<void> => {
expect((): any => parseAccept('a/b; a="\\""')).not.toThrow();
expect((): any => parseAccept('a/b; a="\\\u007F"')).toThrow('Invalid quoted string in header:');
});
it('rejects invalid values when strict mode is enabled.', async(): Promise<void> => {
expect((): any => parseAccept('"bad"/text', true)).toThrow(BadRequestHttpError);
expect((): any => parseAccept('a/b; q=text', true)).toThrow(BadRequestHttpError);
expect((): any => parseAccept('a/b; a', true)).toThrow(BadRequestHttpError);
});
});
describe('#parseCharset', (): void => {
@@ -89,10 +120,14 @@ describe('HeaderUtil', (): void => {
]);
});
it('rejects invalid Accept-Charset Headers.', async(): Promise<void> => {
expect((): any => parseAcceptCharset('a/b')).toThrow('Invalid Accept-Charset range:');
expect((): any => parseAcceptCharset('a; q=text')).toThrow('Invalid q value:');
expect((): any => parseAcceptCharset('a; c=d')).toThrow('Only q parameters are allowed');
it('ignores invalid Accept-Charset Headers.', async(): Promise<void> => {
expect(parseAcceptCharset('a/b')).toEqual([]);
expect(parseAcceptCharset('a; q=text')).toEqual([{ range: 'a', weight: 1 }]);
expect(parseAcceptCharset('a; c=d')).toEqual([{ range: 'a', weight: 1 }]);
});
it('rejects invalid values when strict mode is enabled.', async(): Promise<void> => {
expect((): any => parseAcceptCharset('a/b', true)).toThrow(BadRequestHttpError);
});
});
@@ -109,10 +144,14 @@ describe('HeaderUtil', (): void => {
]);
});
it('rejects invalid Accept-Encoding Headers.', async(): Promise<void> => {
expect((): any => parseAcceptEncoding('a/b')).toThrow('Invalid Accept-Encoding range:');
expect((): any => parseAcceptEncoding('a; q=text')).toThrow('Invalid q value:');
expect((): any => parseAcceptCharset('a; c=d')).toThrow('Only q parameters are allowed');
it('ignores invalid Accept-Encoding Headers.', async(): Promise<void> => {
expect(parseAcceptEncoding('a/b')).toEqual([]);
expect(parseAcceptEncoding('a; q=text')).toEqual([{ range: 'a', weight: 1 }]);
expect(parseAcceptCharset('a; c=d')).toEqual([{ range: 'a', weight: 1 }]);
});
it('rejects invalid values when strict mode is enabled.', async(): Promise<void> => {
expect((): any => parseAcceptEncoding('a/b', true)).toThrow(BadRequestHttpError);
});
});
@@ -125,16 +164,20 @@ describe('HeaderUtil', (): void => {
]);
});
it('rejects invalid Accept-Language Headers.', async(): Promise<void> => {
expect((): any => parseAcceptLanguage('a/b')).toThrow('Invalid Accept-Language range:');
expect((): any => parseAcceptLanguage('05-a')).toThrow('Invalid Accept-Language range:');
expect((): any => parseAcceptLanguage('a--05')).toThrow('Invalid Accept-Language range:');
expect((): any => parseAcceptLanguage('a-"a"')).toThrow('Invalid Accept-Language range:');
expect((): any => parseAcceptLanguage('a-05')).not.toThrow('Invalid Accept-Language range:');
expect((): any => parseAcceptLanguage('a-b-c-d')).not.toThrow('Invalid Accept-Language range:');
it('ignores invalid Accept-Language Headers.', async(): Promise<void> => {
expect(parseAcceptLanguage('a/b')).toEqual([]);
expect(parseAcceptLanguage('05-a')).toEqual([]);
expect(parseAcceptLanguage('a--05')).toEqual([]);
expect(parseAcceptLanguage('a-"a"')).toEqual([]);
expect(parseAcceptLanguage('a-05')).toEqual([{ range: 'a-05', weight: 1 }]);
expect(parseAcceptLanguage('a-b-c-d')).toEqual([{ range: 'a-b-c-d', weight: 1 }]);
expect((): any => parseAcceptLanguage('a; q=text')).toThrow('Invalid q value:');
expect((): any => parseAcceptCharset('a; c=d')).toThrow('Only q parameters are allowed');
expect(parseAcceptLanguage('a; q=text')).toEqual([{ range: 'a', weight: 1 }]);
expect(parseAcceptCharset('a; c=d')).toEqual([{ range: 'a', weight: 1 }]);
});
it('rejects invalid values when strict mode is enabled.', async(): Promise<void> => {
expect((): any => parseAcceptLanguage('a/b', true)).toThrow(BadRequestHttpError);
});
});
@@ -150,9 +193,13 @@ describe('HeaderUtil', (): void => {
expect(parseAcceptDateTime(' ')).toEqual([]);
});
it('rejects invalid Accept-DateTime Headers.', async(): Promise<void> => {
expect((): any => parseAcceptDateTime('a/b')).toThrow('Invalid Accept-DateTime range:');
expect((): any => parseAcceptDateTime('30 May 2007')).toThrow('Invalid Accept-DateTime range:');
it('ignores invalid Accept-DateTime Headers.', async(): Promise<void> => {
expect(parseAcceptDateTime('a/b')).toEqual([]);
expect(parseAcceptDateTime('30 May 2007')).toEqual([]);
});
it('rejects invalid values when strict mode is enabled.', async(): Promise<void> => {
expect((): any => parseAcceptLanguage('a/b', true)).toThrow(BadRequestHttpError);
});
});

View File

@@ -96,7 +96,7 @@ describe('PathUtil', (): void => {
describe('#toCanonicalUriPath', (): void => {
it('encodes only the necessary parts.', (): void => {
expect(toCanonicalUriPath('/a%20path&/name')).toBe('/a%20path%26/name');
expect(toCanonicalUriPath('/a%20path&*/name')).toBe('/a%20path%26*/name');
});
it('leaves the query string untouched.', (): void => {
@@ -138,6 +138,11 @@ describe('PathUtil', (): void => {
expect(decodeUriPathComponents('/a%25252Fb')).toBe('/a%25252Fb');
expect(decodeUriPathComponents('/a%2525252Fb')).toBe('/a%2525252Fb');
});
it('ensures illegal path characters are encoded.', async(): Promise<void> => {
expect(decodeUriPathComponents('/a<path%3F%3E/*:name?abc=def&xyz'))
.toBe('/a%3Cpath%3F%3E/%2A%3Aname?abc=def&xyz');
});
});
describe('#encodeUriPathComponents', (): void => {