mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Dynamically generate Allow and Accept-* headers
This commit is contained in:
@@ -93,10 +93,12 @@ describe('An http server with middleware', (): void => {
|
||||
expect(res.header).toEqual(expect.objectContaining({ 'access-control-allow-origin': 'test.com' }));
|
||||
});
|
||||
|
||||
it('exposes the Accept-Patch header via CORS.', async(): Promise<void> => {
|
||||
it('exposes the Accept-[Method] header via CORS.', async(): Promise<void> => {
|
||||
const res = await request(server).get('/').expect(200);
|
||||
const exposed = res.header['access-control-expose-headers'];
|
||||
expect(exposed.split(/\s*,\s*/u)).toContain('Accept-Patch');
|
||||
expect(exposed.split(/\s*,\s*/u)).toContain('Accept-Post');
|
||||
expect(exposed.split(/\s*,\s*/u)).toContain('Accept-Put');
|
||||
});
|
||||
|
||||
it('exposes the Last-Modified and ETag headers via CORS.', async(): Promise<void> => {
|
||||
|
||||
115
test/unit/http/output/metadata/AllowAcceptHeaderWriter.test.ts
Normal file
115
test/unit/http/output/metadata/AllowAcceptHeaderWriter.test.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { createResponse } from 'node-mocks-http';
|
||||
import { AllowAcceptHeaderWriter } from '../../../../../src/http/output/metadata/AllowAcceptHeaderWriter';
|
||||
import type { MetadataRecord } from '../../../../../src/http/representation/RepresentationMetadata';
|
||||
import { RepresentationMetadata } from '../../../../../src/http/representation/RepresentationMetadata';
|
||||
import type { HttpResponse } from '../../../../../src/server/HttpResponse';
|
||||
import { MethodNotAllowedHttpError } from '../../../../../src/util/errors/MethodNotAllowedHttpError';
|
||||
import { NotFoundHttpError } from '../../../../../src/util/errors/NotFoundHttpError';
|
||||
import { UnsupportedMediaTypeHttpError } from '../../../../../src/util/errors/UnsupportedMediaTypeHttpError';
|
||||
import { LDP, PIM, RDF, SOLID_ERROR } from '../../../../../src/util/Vocabularies';
|
||||
|
||||
describe('An AllowAcceptHeaderWriter', (): void => {
|
||||
const document = new RepresentationMetadata({ path: 'http://example.com/foo/bar' },
|
||||
{ [RDF.type]: LDP.terms.Resource });
|
||||
const emptyContainer = new RepresentationMetadata({ path: 'http://example.com/foo/' },
|
||||
{ [RDF.type]: [ LDP.terms.Resource, LDP.terms.Container ]});
|
||||
const fullContainer = new RepresentationMetadata({ path: 'http://example.com/foo/' },
|
||||
{
|
||||
[RDF.type]: [ LDP.terms.Resource, LDP.terms.Container ],
|
||||
[LDP.contains]: [ document.identifier ],
|
||||
// Typescript doesn't find the correct constructor without the cast
|
||||
} as MetadataRecord);
|
||||
const storageContainer = new RepresentationMetadata({ path: 'http://example.com/foo/' },
|
||||
{ [RDF.type]: [ LDP.terms.Resource, LDP.terms.Container, PIM.terms.Storage ]});
|
||||
const error404 = new RepresentationMetadata({ [SOLID_ERROR.errorResponse]: NotFoundHttpError.uri });
|
||||
const error405 = new RepresentationMetadata(
|
||||
{ [SOLID_ERROR.errorResponse]: MethodNotAllowedHttpError.uri, [SOLID_ERROR.disallowedMethod]: 'PUT' },
|
||||
);
|
||||
const error415 = new RepresentationMetadata({ [SOLID_ERROR.errorResponse]: UnsupportedMediaTypeHttpError.uri });
|
||||
let response: HttpResponse;
|
||||
let writer: AllowAcceptHeaderWriter;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
response = createResponse();
|
||||
|
||||
writer = new AllowAcceptHeaderWriter(
|
||||
[ 'OPTIONS', 'GET', 'HEAD', 'PUT', 'POST', 'PATCH', 'DELETE' ],
|
||||
{ patch: [ 'text/n3', 'application/sparql-update' ], post: [ '*/*' ], put: [ '*/*' ]},
|
||||
);
|
||||
});
|
||||
|
||||
it('returns all methods except POST for a document.', async(): Promise<void> => {
|
||||
await expect(writer.handleSafe({ response, metadata: document })).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', 'PATCH', 'DELETE' ]));
|
||||
expect(headers['accept-patch']).toBe('text/n3, application/sparql-update');
|
||||
expect(headers['accept-put']).toBe('*/*');
|
||||
expect(headers['accept-post']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns all methods 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' ]));
|
||||
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> => {
|
||||
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' ]));
|
||||
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> => {
|
||||
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' ]));
|
||||
expect(headers['accept-patch']).toBe('text/n3, application/sparql-update');
|
||||
expect(headers['accept-put']).toBe('*/*');
|
||||
expect(headers['accept-post']).toBe('*/*');
|
||||
});
|
||||
|
||||
it('returns PATCH and PUT if the target does not exist.', async(): Promise<void> => {
|
||||
await expect(writer.handleSafe({ response, metadata: error404 })).resolves.toBeUndefined();
|
||||
const headers = response.getHeaders();
|
||||
expect(typeof headers.allow).toBe('string');
|
||||
expect(new Set((headers.allow as string).split(', ')))
|
||||
.toEqual(new Set([ 'PUT', 'PATCH' ]));
|
||||
expect(headers['accept-patch']).toBe('text/n3, application/sparql-update');
|
||||
expect(headers['accept-put']).toBe('*/*');
|
||||
expect(headers['accept-post']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('removes methods that are not allowed by a 405 error.', async(): Promise<void> => {
|
||||
await expect(writer.handleSafe({ response, metadata: error405 })).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', 'POST', 'PATCH', 'DELETE' ]));
|
||||
expect(headers['accept-patch']).toBe('text/n3, application/sparql-update');
|
||||
expect(headers['accept-put']).toBeUndefined();
|
||||
expect(headers['accept-post']).toBe('*/*');
|
||||
});
|
||||
|
||||
it('only returns Accept- headers in case of a 415.', async(): Promise<void> => {
|
||||
await expect(writer.handleSafe({ response, metadata: error415 })).resolves.toBeUndefined();
|
||||
const headers = response.getHeaders();
|
||||
expect(headers.allow).toBeUndefined();
|
||||
expect(headers['accept-patch']).toBe('text/n3, application/sparql-update');
|
||||
expect(headers['accept-put']).toBe('*/*');
|
||||
expect(headers['accept-post']).toBe('*/*');
|
||||
});
|
||||
});
|
||||
@@ -248,6 +248,13 @@ describe('A RepresentationMetadata', (): void => {
|
||||
);
|
||||
});
|
||||
|
||||
it('can check the existence of a triple.', async(): Promise<void> => {
|
||||
expect(metadata.has(namedNode('has'), literal('data'))).toBe(true);
|
||||
expect(metadata.has(namedNode('has'))).toBe(true);
|
||||
expect(metadata.has(undefined, literal('data'))).toBe(true);
|
||||
expect(metadata.has(namedNode('has'), literal('wrongData'))).toBe(false);
|
||||
});
|
||||
|
||||
it('can get all values for a predicate.', async(): Promise<void> => {
|
||||
expect(metadata.getAll(namedNode('has'))).toEqualRdfTermArray(
|
||||
[ literal('data'), literal('moreData'), literal('data') ],
|
||||
|
||||
@@ -11,6 +11,7 @@ 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';
|
||||
|
||||
describe('A ParsingHttpHandler', (): void => {
|
||||
const request: HttpRequest = {} as any;
|
||||
@@ -78,4 +79,17 @@ describe('A ParsingHttpHandler', (): void => {
|
||||
expect(responseWriter.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(responseWriter.handleSafe).toHaveBeenLastCalledWith({ response, result: errorResponse });
|
||||
});
|
||||
|
||||
it('adds error metadata if able.', async(): Promise<void> => {
|
||||
const error = new HttpError(0, 'error');
|
||||
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, preferences });
|
||||
expect(responseWriter.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(responseWriter.handleSafe).toHaveBeenLastCalledWith({ response, result: metaResponse });
|
||||
expect(metaResponse.metadata?.quads()).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ export async function getResource(url: string,
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get('link')).toContain(`<${LDP.Resource}>; rel="type"`);
|
||||
expect(response.headers.get('link')).toContain(`<${url}.acl>; rel="acl"`);
|
||||
expect(response.headers.get('accept-patch')).toBe('application/sparql-update, text/n3');
|
||||
expect(response.headers.get('accept-patch')).toBe('text/n3, application/sparql-update');
|
||||
expect(response.headers.get('ms-author-via')).toBe('SPARQL');
|
||||
|
||||
if (isContainer) {
|
||||
|
||||
Reference in New Issue
Block a user