mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
399 lines
16 KiB
TypeScript
399 lines
16 KiB
TypeScript
import 'jest-rdf';
|
|
import { fetch } from 'cross-fetch';
|
|
import { Parser } from 'n3';
|
|
import type { AclPermission } from '../../src/authorization/permissions/AclPermission';
|
|
import { BasicRepresentation } from '../../src/http/representation/BasicRepresentation';
|
|
import type { App } from '../../src/init/App';
|
|
import type { ResourceStore } from '../../src/storage/ResourceStore';
|
|
import { joinUrl } from '../../src/util/PathUtil';
|
|
import { AclHelper } from '../util/AclHelper';
|
|
import { getPort } from '../util/Util';
|
|
import {
|
|
getDefaultVariables,
|
|
getPresetConfigPath,
|
|
getTestConfigPath,
|
|
instantiateFromConfig,
|
|
} from './Config';
|
|
|
|
const port = getPort('N3Patch');
|
|
const baseUrl = `http://localhost:${port}/`;
|
|
|
|
let store: ResourceStore;
|
|
let aclHelper: AclHelper;
|
|
|
|
async function expectPatch(
|
|
input: { path: string; contentType?: string; body: string },
|
|
expected: { status: number; message?: string; turtle?: string },
|
|
): Promise<void> {
|
|
const message = expected.message ?? '';
|
|
const contentType = input.contentType ?? 'text/n3';
|
|
|
|
const body = `@prefix solid: <http://www.w3.org/ns/solid/terms#>.
|
|
${input.body}`;
|
|
|
|
const url = joinUrl(baseUrl, input.path);
|
|
const res = await fetch(url, {
|
|
method: 'PATCH',
|
|
headers: { 'content-type': contentType },
|
|
body,
|
|
});
|
|
await expect(res.text()).resolves.toContain(message);
|
|
expect(res.status).toBe(expected.status);
|
|
|
|
// Verify if the resource has the expected RDF data
|
|
if (expected.turtle) {
|
|
// Might not have read permissions so need to update
|
|
await aclHelper.setSimpleAcl(url, { permissions: { read: true }, agentClass: 'agent', accessTo: true });
|
|
const get = await fetch(url, {
|
|
method: 'GET',
|
|
headers: { accept: 'text/turtle' },
|
|
});
|
|
const expectedTurtle = `@prefix solid: <http://www.w3.org/ns/solid/terms#>.
|
|
${expected.turtle}`;
|
|
|
|
expect(get.status).toBe(200);
|
|
const parser = new Parser({ format: 'text/turtle', baseIRI: url });
|
|
const actualTriples = parser.parse(await get.text());
|
|
expect(actualTriples).toBeRdfIsomorphic(parser.parse(expectedTurtle));
|
|
}
|
|
}
|
|
|
|
// Creates/updates a resource with the given data and permissions
|
|
async function setResource(path: string, turtle: string, permissions: AclPermission): Promise<void> {
|
|
const url = joinUrl(baseUrl, path);
|
|
await store.setRepresentation({ path: url }, new BasicRepresentation(turtle, 'text/turtle'));
|
|
await aclHelper.setSimpleAcl(url, { permissions, agentClass: 'agent', accessTo: true });
|
|
}
|
|
|
|
describe('A Server supporting N3 Patch', (): void => {
|
|
let app: App;
|
|
|
|
beforeAll(async(): Promise<void> => {
|
|
// Create and start the server
|
|
const instances = await instantiateFromConfig(
|
|
'urn:solid-server:test:Instances',
|
|
[
|
|
getPresetConfigPath('storage/backend/memory.json'),
|
|
getTestConfigPath('ldp-with-auth.json'),
|
|
],
|
|
getDefaultVariables(port, baseUrl),
|
|
) as Record<string, any>;
|
|
({ app, store } = instances);
|
|
|
|
await app.start();
|
|
|
|
// Create test helper for manipulating acl
|
|
aclHelper = new AclHelper(store);
|
|
});
|
|
|
|
afterAll(async(): Promise<void> => {
|
|
await app.stop();
|
|
});
|
|
|
|
describe('with an invalid patch document', (): void => {
|
|
it('requires text/n3 content-type.', async(): Promise<void> => {
|
|
await expectPatch(
|
|
{ path: '/invalid', contentType: 'text/other', body: '' },
|
|
{ status: 415 },
|
|
);
|
|
});
|
|
|
|
it('requires valid syntax.', async(): Promise<void> => {
|
|
await expectPatch(
|
|
{ path: '/invalid', body: 'invalid syntax' },
|
|
{ status: 400, message: 'Invalid N3' },
|
|
);
|
|
});
|
|
|
|
it('requires a solid:InsertDeletePatch.', async(): Promise<void> => {
|
|
await expectPatch(
|
|
{ path: '/invalid', body: '<> a solid:Patch.' },
|
|
{
|
|
status: 422,
|
|
message: 'This patcher only supports N3 Patch documents with exactly 1 solid:InsertDeletePatch entry',
|
|
},
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('inserting data', (): void => {
|
|
it('succeeds if there is no resource.', async(): Promise<void> => {
|
|
await expectPatch(
|
|
{ path: '/new-insert', body: `<> a solid:InsertDeletePatch; solid:inserts { <x> <y> <z>. }.` },
|
|
{ status: 201, turtle: '<x> <y> <z>.' },
|
|
);
|
|
});
|
|
|
|
it('fails if there is only read access.', async(): Promise<void> => {
|
|
await setResource('/read-only', '<a> <b> <c>.', { read: true });
|
|
await expectPatch(
|
|
{ path: '/read-only', body: `<> a solid:InsertDeletePatch; solid:inserts { <x> <y> <z>. }.` },
|
|
{ status: 401 },
|
|
);
|
|
});
|
|
|
|
it('succeeds if there is only read access.', async(): Promise<void> => {
|
|
await setResource('/append-only', '<a> <b> <c>.', { append: true });
|
|
await expectPatch(
|
|
{ path: '/append-only', body: `<> a solid:InsertDeletePatch; solid:inserts { <x> <y> <z>. }.` },
|
|
{ status: 205, turtle: '<a> <b> <c>. <x> <y> <z>.' },
|
|
);
|
|
});
|
|
|
|
it('succeeds if there is only write access.', async(): Promise<void> => {
|
|
await setResource('/write-only', '<a> <b> <c>.', { write: true });
|
|
await expectPatch(
|
|
{ path: '/write-only', body: `<> a solid:InsertDeletePatch; solid:inserts { <x> <y> <z>. }.` },
|
|
{ status: 205, turtle: '<a> <b> <c>. <x> <y> <z>.' },
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('inserting conditional data', (): void => {
|
|
it('fails if there is no resource.', async(): Promise<void> => {
|
|
await expectPatch(
|
|
{ path: '/new-insert-where', body: `<> a solid:InsertDeletePatch;
|
|
solid:inserts { ?a <y> <z>. };
|
|
solid:where { ?a <b> <c>. }.` },
|
|
{ status: 409, message: 'The document does not contain any matches for the N3 Patch solid:where condition.' },
|
|
);
|
|
});
|
|
|
|
it('fails if there is only read access.', async(): Promise<void> => {
|
|
await setResource('/read-only', '<a> <b> <c>.', { read: true });
|
|
await expectPatch(
|
|
{ path: '/read-only', body: `<> a solid:InsertDeletePatch;
|
|
solid:inserts { ?a <y> <z>. };
|
|
solid:where { ?a <b> <c>. }.` },
|
|
{ status: 401 },
|
|
);
|
|
});
|
|
|
|
it('fails if there is only append access.', async(): Promise<void> => {
|
|
await setResource('/append-only', '<a> <b> <c>.', { append: true });
|
|
await expectPatch(
|
|
{ path: '/append-only', body: `<> a solid:InsertDeletePatch;
|
|
solid:inserts { ?a <y> <z>. };
|
|
solid:where { ?a <b> <c>. }.` },
|
|
{ status: 401 },
|
|
);
|
|
});
|
|
|
|
it('fails if there is only write access.', async(): Promise<void> => {
|
|
await setResource('/write-only', '<a> <b> <c>.', { write: true });
|
|
await expectPatch(
|
|
{ path: '/write-only', body: `<> a solid:InsertDeletePatch;
|
|
solid:inserts { ?a <y> <z>. };
|
|
solid:where { ?a <b> <c>. }.` },
|
|
{ status: 401 },
|
|
);
|
|
});
|
|
|
|
describe('with read/append access', (): void => {
|
|
it('succeeds if the conditions match.', async(): Promise<void> => {
|
|
await setResource('/read-append', '<a> <b> <c>.', { read: true, append: true });
|
|
await expectPatch(
|
|
{ path: '/read-append', body: `<> a solid:InsertDeletePatch;
|
|
solid:inserts { ?a <y> <z>. };
|
|
solid:where { ?a <b> <c>. }.` },
|
|
{ status: 205, turtle: '<a> <b> <c>. <a> <y> <z>.' },
|
|
);
|
|
});
|
|
|
|
it('rejects if there is no match.', async(): Promise<void> => {
|
|
await setResource('/read-append', '<a> <b> <c>.', { read: true, append: true });
|
|
await expectPatch(
|
|
{ path: '/read-append', body: `<> a solid:InsertDeletePatch;
|
|
solid:inserts { ?a <y> <z>. };
|
|
solid:where { ?a <y> <z>. }.` },
|
|
{ status: 409, message: 'The document does not contain any matches for the N3 Patch solid:where condition.' },
|
|
);
|
|
});
|
|
|
|
it('rejects if there are multiple matches.', async(): Promise<void> => {
|
|
await setResource('/read-append', '<a> <b> <c>. <c> <b> <c>.', { read: true, append: true });
|
|
await expectPatch(
|
|
{ path: '/read-append', body: `<> a solid:InsertDeletePatch;
|
|
solid:inserts { ?a <y> <z>. };
|
|
solid:where { ?a <b> <c>. }.` },
|
|
{ status: 409, message: 'The document contains multiple matches for the N3 Patch solid:where condition' },
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('with read/write access', (): void => {
|
|
it('succeeds if the conditions match.', async(): Promise<void> => {
|
|
await setResource('/read-write', '<a> <b> <c>.', { read: true, write: true });
|
|
await expectPatch(
|
|
{ path: '/read-write', body: `<> a solid:InsertDeletePatch;
|
|
solid:inserts { ?a <y> <z>. };
|
|
solid:where { ?a <b> <c>. }.` },
|
|
{ status: 205, turtle: '<a> <b> <c>. <a> <y> <z>.' },
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('deleting data', (): void => {
|
|
it('fails if there is no resource.', async(): Promise<void> => {
|
|
await expectPatch(
|
|
{ path: '/new-delete', body: `<> a solid:InsertDeletePatch;
|
|
solid:deletes { <x> <y> <z>. }.` },
|
|
{ status: 409, message: 'The document does not contain all triples the N3 Patch requests to delete' },
|
|
);
|
|
});
|
|
|
|
it('fails if there is only append access.', async(): Promise<void> => {
|
|
await setResource('/append-only', '<a> <b> <c>.', { append: true });
|
|
await expectPatch(
|
|
{ path: '/append-only', body: `<> a solid:InsertDeletePatch;
|
|
solid:deletes { <x> <y> <z>. }.` },
|
|
{ status: 401 },
|
|
);
|
|
});
|
|
|
|
it('fails if there is only write access.', async(): Promise<void> => {
|
|
await setResource('/write-only', '<a> <b> <c>.', { write: true });
|
|
await expectPatch(
|
|
{ path: '/write-only', body: `<> a solid:InsertDeletePatch;
|
|
solid:deletes { <x> <y> <z>. }.` },
|
|
{ status: 401 },
|
|
);
|
|
});
|
|
|
|
it('fails if there is only read/append access.', async(): Promise<void> => {
|
|
await setResource('/read-append', '<a> <b> <c>.', { read: true, append: true });
|
|
await expectPatch(
|
|
{ path: '/read-append', body: `<> a solid:InsertDeletePatch;
|
|
solid:deletes { <x> <y> <z>. }.` },
|
|
{ status: 401 },
|
|
);
|
|
});
|
|
|
|
describe('with read/write access', (): void => {
|
|
it('succeeds if the delete triples exist.', async(): Promise<void> => {
|
|
await setResource('/read-write', '<a> <b> <c>. <x> <y> <z>.', { read: true, write: true });
|
|
await expectPatch(
|
|
{ path: '/read-write', body: `<> a solid:InsertDeletePatch;
|
|
solid:deletes { <x> <y> <z>. }.` },
|
|
{ status: 205, turtle: '<a> <b> <c>.' },
|
|
);
|
|
});
|
|
|
|
it('fails if the delete triples do not exist.', async(): Promise<void> => {
|
|
await setResource('/read-write', '<a> <b> <c>. <x> <y> <z>.', { read: true, write: true });
|
|
await expectPatch(
|
|
{ path: '/read-write', body: `<> a solid:InsertDeletePatch;
|
|
solid:deletes { <a> <y> <z>. }.` },
|
|
{ status: 409, message: 'The document does not contain all triples the N3 Patch requests to delete' },
|
|
);
|
|
});
|
|
|
|
it('succeeds if the conditions match.', async(): Promise<void> => {
|
|
await setResource('/read-write', '<a> <b> <c>. <x> <y> <z>.', { read: true, write: true });
|
|
await expectPatch(
|
|
{ path: '/read-write', body: `<> a solid:InsertDeletePatch;
|
|
solid:where { ?a <y> <z>. };
|
|
solid:deletes { ?a <y> <z>. }.` },
|
|
{ status: 205, turtle: '<a> <b> <c>.' },
|
|
);
|
|
});
|
|
|
|
it('fails if the conditions do not match.', async(): Promise<void> => {
|
|
await setResource('/read-write', '<a> <b> <c>.', { read: true, write: true });
|
|
await expectPatch(
|
|
{ path: '/read-write', body: `<> a solid:InsertDeletePatch;
|
|
solid:where { ?a <y> <z>. };
|
|
solid:deletes { ?a <b> <c>. }.` },
|
|
{ status: 409, message: 'The document does not contain any matches for the N3 Patch solid:where condition.' },
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('deleting and inserting data', (): void => {
|
|
it('fails if there is no resource.', async(): Promise<void> => {
|
|
await expectPatch(
|
|
{ path: '/new-delete-insert', body: `<> a solid:InsertDeletePatch;
|
|
solid:inserts { <x> <y> <z>. };
|
|
solid:deletes { <a> <b> <c>. }.` },
|
|
{ status: 409, message: 'The document does not contain all triples the N3 Patch requests to delete' },
|
|
);
|
|
});
|
|
|
|
it('fails if there is only append access.', async(): Promise<void> => {
|
|
await setResource('/append-only', '<a> <b> <c>.', { append: true });
|
|
await expectPatch(
|
|
{ path: '/append-only', body: `<> a solid:InsertDeletePatch;
|
|
solid:inserts { <x> <y> <z>. };
|
|
solid:deletes { <a> <b> <c>. }.` },
|
|
{ status: 401 },
|
|
);
|
|
});
|
|
|
|
it('fails if there is only write access.', async(): Promise<void> => {
|
|
await setResource('/write-only', '<a> <b> <c>.', { write: true });
|
|
await expectPatch(
|
|
{ path: '/write-only', body: `<> a solid:InsertDeletePatch;
|
|
solid:inserts { <x> <y> <z>. };
|
|
solid:deletes { <a> <b> <c>. }.` },
|
|
{ status: 401 },
|
|
);
|
|
});
|
|
|
|
it('fails if there is only read/append access.', async(): Promise<void> => {
|
|
await setResource('/read-append', '<a> <b> <c>.', { read: true, append: true });
|
|
await expectPatch(
|
|
{ path: '/read-append', body: `<> a solid:InsertDeletePatch;
|
|
solid:inserts { <x> <y> <z>. };
|
|
solid:deletes { <a> <b> <c>. }.` },
|
|
{ status: 401 },
|
|
);
|
|
});
|
|
|
|
describe('with read/write access', (): void => {
|
|
it('executes deletes before inserts.', async(): Promise<void> => {
|
|
await setResource('/read-write', '<a> <b> <c>.', { read: true, write: true });
|
|
await expectPatch(
|
|
{ path: '/read-write', body: `<> a solid:InsertDeletePatch;
|
|
solid:inserts { <x> <y> <z>. };
|
|
solid:deletes { <x> <y> <z>. }.` },
|
|
{ status: 409, message: 'The document does not contain all triples the N3 Patch requests to delete' },
|
|
);
|
|
});
|
|
|
|
it('succeeds if the delete triples exist.', async(): Promise<void> => {
|
|
await setResource('/read-write', '<a> <b> <c>.', { read: true, write: true });
|
|
await expectPatch(
|
|
{ path: '/read-write', body: `<> a solid:InsertDeletePatch;
|
|
solid:inserts { <x> <y> <z>. };
|
|
solid:deletes { <a> <b> <c>. }.` },
|
|
{ status: 205, turtle: '<x> <y> <z>.' },
|
|
);
|
|
});
|
|
|
|
it('succeeds if the conditions match.', async(): Promise<void> => {
|
|
await setResource('/read-write', '<a> <b> <c>.', { read: true, write: true });
|
|
await expectPatch(
|
|
{ path: '/read-write', body: `<> a solid:InsertDeletePatch;
|
|
solid:where { ?a <b> <c>. };
|
|
solid:inserts { ?a <y> <z>. };
|
|
solid:deletes { ?a <b> <c>. }.` },
|
|
{ status: 205, turtle: '<a> <y> <z>.' },
|
|
);
|
|
});
|
|
|
|
it('fails if the conditions do not match.', async(): Promise<void> => {
|
|
await setResource('/read-write', '<a> <b> <c>.', { read: true, write: true });
|
|
await expectPatch(
|
|
{ path: '/read-write', body: `<> a solid:InsertDeletePatch;
|
|
solid:where { ?a <y> <z>. };
|
|
solid:inserts { ?a <y> <z>. };
|
|
solid:deletes { ?a <b> <c>. }.` },
|
|
{ status: 409, message: 'The document does not contain any matches for the N3 Patch solid:where condition.' },
|
|
);
|
|
});
|
|
});
|
|
});
|
|
});
|