CommunitySolidServer/test/unit/http/input/body/N3PatchBodyParser.test.ts
2022-01-25 11:30:42 +01:00

205 lines
9.5 KiB
TypeScript

import 'jest-rdf';
import type { Term } from '@rdfjs/types';
import { DataFactory } from 'n3';
import type { BodyParserArgs } from '../../../../../src/http/input/body/BodyParser';
import { N3PatchBodyParser } from '../../../../../src/http/input/body/N3PatchBodyParser';
import { RepresentationMetadata } from '../../../../../src/http/representation/RepresentationMetadata';
import type { HttpRequest } from '../../../../../src/server/HttpRequest';
import { BadRequestHttpError } from '../../../../../src/util/errors/BadRequestHttpError';
import { UnsupportedMediaTypeHttpError } from '../../../../../src/util/errors/UnsupportedMediaTypeHttpError';
import { guardedStreamFrom } from '../../../../../src/util/StreamUtil';
const { defaultGraph, literal, namedNode, quad, variable } = DataFactory;
describe('An N3PatchBodyParser', (): void => {
let input: BodyParserArgs;
const parser = new N3PatchBodyParser();
beforeEach(async(): Promise<void> => {
input = {
request: { headers: {}} as HttpRequest,
metadata: new RepresentationMetadata({ path: 'http://example.com/foo' }, 'text/n3'),
};
});
it('can only handle N3 data.', async(): Promise<void> => {
input.metadata.contentType = 'text/plain';
await expect(parser.canHandle(input)).rejects.toThrow(UnsupportedMediaTypeHttpError);
input.metadata.contentType = 'text/n3';
await expect(parser.canHandle(input)).resolves.toBeUndefined();
});
it('errors on invalid N3.', async(): Promise<void> => {
input.request = guardedStreamFrom([ 'invalid syntax' ]) as HttpRequest;
await expect(parser.handle(input)).rejects.toThrow(BadRequestHttpError);
});
it('extracts the patch quads from the request.', async(): Promise<void> => {
const n3 = `@prefix solid: <http://www.w3.org/ns/solid/terms#>.
@prefix ex: <http://www.example.org/terms#>.
_:rename a solid:InsertDeletePatch;
solid:where { ?person ex:familyName "Garcia"; ex:nickName "Garry". };
solid:inserts { ?person ex:givenName "Alex". };
solid:deletes { ?person ex:givenName "Claudia". }.`;
input.request = guardedStreamFrom([ n3 ]) as HttpRequest;
const patch = await parser.handle(input);
expect(patch.conditions).toBeRdfIsomorphic([
quad(variable('person'), namedNode('http://www.example.org/terms#familyName'), literal('Garcia')),
quad(variable('person'), namedNode('http://www.example.org/terms#nickName'), literal('Garry')),
]);
expect(patch.inserts).toBeRdfIsomorphic([
quad(variable('person'), namedNode('http://www.example.org/terms#givenName'), literal('Alex')),
]);
expect(patch.deletes).toBeRdfIsomorphic([
quad(variable('person'), namedNode('http://www.example.org/terms#givenName'), literal('Claudia')),
]);
});
it('strips the graph from the result quads.', async(): Promise<void> => {
const n3 = `@prefix solid: <http://www.w3.org/ns/solid/terms#>.
@prefix ex: <http://www.example.org/terms#>.
_:rename a solid:InsertDeletePatch;
solid:where { ?person ex:familyName "Garcia"; ex:nickName "Garry". };
solid:inserts { ?person ex:givenName "Alex". };
solid:deletes { ?person ex:givenName "Claudia". }.`;
input.request = guardedStreamFrom([ n3 ]) as HttpRequest;
const patch = await parser.handle(input);
const quads = [ ...patch.deletes, ...patch.inserts, ...patch.conditions ];
const uniqueGraphs = [ ...new Set(quads.map((entry): Term => entry.graph)) ];
expect(uniqueGraphs).toHaveLength(1);
expect(uniqueGraphs[0]).toEqualRdfTerm(defaultGraph());
});
it('errors if no solid:InsertDeletePatch is found.', async(): Promise<void> => {
const n3 = `@prefix solid: <http://www.w3.org/ns/solid/terms#>.
@prefix ex: <http://www.example.org/terms#>.
_:rename
solid:where { ?person ex:familyName "Garcia"; ex:nickName "Garry". };
solid:inserts { ?person ex:givenName "Alex". };
solid:deletes { ?person ex:givenName "Claudia". }.`;
input.request = guardedStreamFrom([ n3 ]) as HttpRequest;
await expect(parser.handle(input)).rejects.toThrow(
'This patcher only supports N3 Patch documents with exactly 1 solid:InsertDeletePatch entry, but received 0.',
);
});
it('errors if multiple solid:InsertDeletePatch entries are found.', async(): Promise<void> => {
const n3 = `@prefix solid: <http://www.w3.org/ns/solid/terms#>.
@prefix ex: <http://www.example.org/terms#>.
_:other a solid:InsertDeletePatch.
_:rename a solid:InsertDeletePatch;
solid:where { ?person ex:familyName "Garcia"; ex:nickName "Garry". };
solid:inserts { ?person ex:givenName "Alex". };
solid:deletes { ?person ex:givenName "Claudia". }.`;
input.request = guardedStreamFrom([ n3 ]) as HttpRequest;
await expect(parser.handle(input)).rejects.toThrow(
'This patcher only supports N3 Patch documents with exactly 1 solid:InsertDeletePatch entry, but received 2.',
);
});
it('errors if the patch subject is not a blank or named node.', async(): Promise<void> => {
const n3 = `@prefix solid: <http://www.w3.org/ns/solid/terms#>.
@prefix ex: <http://www.example.org/terms#>.
?rename a solid:InsertDeletePatch;
solid:where { ?person ex:familyName "Garcia"; ex:nickName "Garry". };
solid:inserts { ?person ex:givenName "Alex". };
solid:deletes { ?person ex:givenName "Claudia". }.`;
input.request = guardedStreamFrom([ n3 ]) as HttpRequest;
await expect(parser.handle(input)).rejects
.toThrow('An N3 Patch subject needs to be a blank or named node.');
});
it('errors if there are multiple where entries.', async(): Promise<void> => {
const n3 = `@prefix solid: <http://www.w3.org/ns/solid/terms#>.
@prefix ex: <http://www.example.org/terms#>.
_:rename a solid:InsertDeletePatch;
solid:where { ?person ex:familyName "Garcia"; ex:nickName "Garry". };
solid:where { ?person ex:givenName "Alex". }.`;
input.request = guardedStreamFrom([ n3 ]) as HttpRequest;
await expect(parser.handle(input)).rejects
.toThrow('An N3 Patch can have at most 1 http://www.w3.org/ns/solid/terms#where.');
});
it('errors if there are multiple delete entries.', async(): Promise<void> => {
const n3 = `@prefix solid: <http://www.w3.org/ns/solid/terms#>.
@prefix ex: <http://www.example.org/terms#>.
_:rename a solid:InsertDeletePatch;
solid:deletes { ex:person ex:familyName "Garcia". };
solid:deletes { ex:person ex:givenName "Alex". }.`;
input.request = guardedStreamFrom([ n3 ]) as HttpRequest;
await expect(parser.handle(input)).rejects
.toThrow('An N3 Patch can have at most 1 http://www.w3.org/ns/solid/terms#deletes.');
});
it('errors if there are multiple insert entries.', async(): Promise<void> => {
const n3 = `@prefix solid: <http://www.w3.org/ns/solid/terms#>.
@prefix ex: <http://www.example.org/terms#>.
_:rename a solid:InsertDeletePatch;
solid:inserts { ex:person ex:familyName "Garcia". };
solid:inserts { ex:person ex:givenName "Alex". }.`;
input.request = guardedStreamFrom([ n3 ]) as HttpRequest;
await expect(parser.handle(input)).rejects
.toThrow('An N3 Patch can have at most 1 http://www.w3.org/ns/solid/terms#inserts.');
});
it('errors if there are blank nodes in the delete formula.', async(): Promise<void> => {
const n3 = `@prefix solid: <http://www.w3.org/ns/solid/terms#>.
@prefix ex: <http://www.example.org/terms#>.
_:rename a solid:InsertDeletePatch;
solid:where { ?person ex:familyName "Garcia"; ex:nickName "Garry". };
solid:inserts { ?person ex:givenName "Alex". };
solid:deletes { _:person ex:givenName "Claudia". }.`;
input.request = guardedStreamFrom([ n3 ]) as HttpRequest;
await expect(parser.handle(input)).rejects
.toThrow('An N3 Patch delete/insert formula can not contain blank nodes.');
});
it('errors if there are blank nodes in the insert formula.', async(): Promise<void> => {
const n3 = `@prefix solid: <http://www.w3.org/ns/solid/terms#>.
@prefix ex: <http://www.example.org/terms#>.
_:rename a solid:InsertDeletePatch;
solid:where { ?person ex:familyName "Garcia"; ex:nickName "Garry". };
solid:inserts { _:person ex:givenName "Alex". };
solid:deletes { ?person ex:givenName "Claudia". }.`;
input.request = guardedStreamFrom([ n3 ]) as HttpRequest;
await expect(parser.handle(input)).rejects
.toThrow('An N3 Patch delete/insert formula can not contain blank nodes.');
});
it('errors if there are unknown variables in the delete formula.', async(): Promise<void> => {
const n3 = `@prefix solid: <http://www.w3.org/ns/solid/terms#>.
@prefix ex: <http://www.example.org/terms#>.
_:rename a solid:InsertDeletePatch;
solid:where { ?person ex:familyName "Garcia"; ex:nickName "Garry". };
solid:inserts { ?person ex:givenName "Alex". };
solid:deletes { ?person ex:givenName ?name. }.`;
input.request = guardedStreamFrom([ n3 ]) as HttpRequest;
await expect(parser.handle(input)).rejects
.toThrow('An N3 Patch delete/insert formula can only contain variables found in the conditions formula.');
});
it('errors if there are unknown variables in the insert formula.', async(): Promise<void> => {
const n3 = `@prefix solid: <http://www.w3.org/ns/solid/terms#>.
@prefix ex: <http://www.example.org/terms#>.
_:rename a solid:InsertDeletePatch;
solid:where { ?person ex:familyName "Garcia"; ex:nickName "Garry". };
solid:inserts { ?person ex:givenName ?name. };
solid:deletes { ?person ex:givenName "Claudia". }.`;
input.request = guardedStreamFrom([ n3 ]) as HttpRequest;
await expect(parser.handle(input)).rejects
.toThrow('An N3 Patch delete/insert formula can only contain variables found in the conditions formula.');
});
});