fix: Support missing type preferences in ChainedConverter

This commit is contained in:
Joachim Van Herwegen 2021-05-12 08:50:02 +02:00
parent d9b641c2b0
commit 52a3b84ee0
5 changed files with 62 additions and 16 deletions

View File

@ -32,8 +32,12 @@ export class AcceptPreferenceParser extends PreferenceParser {
for (const { name, header, parse } of parsers) {
const value = headers[header];
if (typeof value === 'string') {
preferences[name] = Object.fromEntries(parse(value)
const result = Object.fromEntries(parse(value)
.map(({ range, weight }): [string, number] => [ range, weight ]));
// Interpret empty headers (or headers with no valid values) the same as missing headers
if (Object.keys(result).length > 0) {
preferences[name] = result;
}
}
}
return preferences;

View File

@ -174,15 +174,11 @@ export class ChainedConverter extends RepresentationConverter {
if (!type) {
throw new BadRequestHttpError('Missing Content-Type header.');
}
let preferences = input.preferences.type;
if (!preferences) {
throw new BadRequestHttpError('Missing type preferences.');
}
preferences = cleanPreferences(preferences);
const preferences = cleanPreferences(input.preferences.type);
const weight = getTypeWeight(type, preferences);
if (weight > 0) {
this.logger.debug(`No conversion required: ${type} already matches ${Object.keys(input.preferences.type!)}`);
this.logger.debug(`No conversion required: ${type} already matches ${Object.keys(preferences)}`);
return { value: type, weight };
}

View File

@ -36,17 +36,50 @@ describe('A Solid server', (): void => {
});
});
it('can GET results from a container.', async(): Promise<void> => {
it('can do a successful HEAD request to a container.', async(): Promise<void> => {
const res = await fetch(baseUrl, { method: 'HEAD' });
expect(res.status).toBe(200);
});
it('can do a successful HEAD request to a container without accept headers.', async(): Promise<void> => {
const res = await fetch(baseUrl, { method: 'HEAD', headers: { accept: '' }});
expect(res.status).toBe(200);
});
it('can do a successful HEAD request to a document.', async(): Promise<void> => {
const url = `${baseUrl}.acl`;
const res = await fetch(url, { method: 'HEAD' });
expect(res.status).toBe(200);
});
it('can do a successful HEAD request to a document without accept headers.', async(): Promise<void> => {
const url = `${baseUrl}.acl`;
const res = await fetch(url, { method: 'HEAD', headers: { accept: '' }});
expect(res.status).toBe(200);
});
it('can do a successful GET request to a container.', async(): Promise<void> => {
const res = await fetch(baseUrl);
expect(res.status).toBe(200);
});
it('can GET results from a resource.', async(): Promise<void> => {
it('can do a successful GET request to a container without accept headers.', async(): Promise<void> => {
const res = await fetch(baseUrl, { headers: { accept: '' }});
expect(res.status).toBe(200);
});
it('can do a successful GET request to a document.', async(): Promise<void> => {
const url = `${baseUrl}.acl`;
const res = await fetch(url);
expect(res.status).toBe(200);
});
it('can do a successful GET request to a document without accept headers.', async(): Promise<void> => {
const url = `${baseUrl}.acl`;
const res = await fetch(url, { headers: { accept: '' }});
expect(res.status).toBe(200);
});
it('can PUT to containers.', async(): Promise<void> => {
const url = `${baseUrl}containerPUT/`;
const res = await fetch(url, {
@ -96,7 +129,7 @@ describe('A Solid server', (): void => {
expect(res.headers.get('location')).toBe(`${baseUrl}containerPOST/`);
});
it('can POST to create a resource.', async(): Promise<void> => {
it('can POST to create a document.', async(): Promise<void> => {
const res = await fetch(baseUrl, {
method: 'POST',
headers: {
@ -122,7 +155,7 @@ describe('A Solid server', (): void => {
expect(res.status).toBe(205);
});
it('can DELETE resources.', async(): Promise<void> => {
it('can DELETE documents.', async(): Promise<void> => {
const url = `${baseUrl}resourceDELETE`;
await fetch(url, {
method: 'PUT',
@ -135,7 +168,7 @@ describe('A Solid server', (): void => {
expect(res.status).toBe(205);
});
it('can PATCH resources.', async(): Promise<void> => {
it('can PATCH documents.', async(): Promise<void> => {
const url = `${baseUrl}resourcePATCH`;
await fetch(url, {
method: 'PUT',

View File

@ -14,6 +14,9 @@ describe('An AcceptPreferenceParser', (): void => {
it('returns an empty result if there is no relevant input.', async(): Promise<void> => {
await expect(preferenceParser.handle({ request })).resolves.toEqual({});
request.headers = { accept: '' };
await expect(preferenceParser.handle({ request })).resolves.toEqual({});
});
it('parses accept headers.', async(): Promise<void> => {

View File

@ -70,10 +70,6 @@ describe('A ChainedConverter', (): void => {
const converters = [ new DummyConverter({ 'a/a': 1 }, { 'x/x': 1 }) ];
const converter = new ChainedConverter(converters);
await expect(converter.canHandle(args)).rejects.toThrow('Missing Content-Type header.');
args.representation.metadata.contentType = 'a/a';
args.preferences = { };
await expect(converter.canHandle(args)).rejects.toThrow('Missing type preferences.');
});
it('errors if no path can be found.', async(): Promise<void> => {
@ -95,6 +91,20 @@ describe('A ChainedConverter', (): void => {
expect(result.metadata.contentType).toBe('b/b');
});
it('interprets no preferences as */*.', async(): Promise<void> => {
const converters = [ new DummyConverter({ 'a/a': 1 }, { 'x/x': 1 }) ];
const converter = new ChainedConverter(converters);
args.representation.metadata.contentType = 'b/b';
args.preferences.type = undefined;
let result = await converter.handle(args);
expect(result.metadata.contentType).toBe('b/b');
args.preferences.type = { };
result = await converter.handle(args);
expect(result.metadata.contentType).toBe('b/b');
});
it('can find paths of length 1.', async(): Promise<void> => {
const converters = [ new DummyConverter({ 'a/a': 1 }, { 'x/x': 1 }) ];
const converter = new ChainedConverter(converters);