Skip to content

Commit

Permalink
test: add integration test and unit test for creating containers with…
Browse files Browse the repository at this point in the history
…in constrained containers
  • Loading branch information
woutslabbinck committed Jan 18, 2023
1 parent 39d922f commit 8938b2f
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 58 deletions.
1 change: 0 additions & 1 deletion src/storage/ShapeValidationStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ export class ShapeValidationStore extends PassthroughStore {
const createdIdentifiers = Array.from(filter(updatedResources.keys(), (id: ResourceIdentifier) => id.path !== topIdentifier.path));
const sortedIdentifiers = createdIdentifiers.sort((a: ResourceIdentifier, b: ResourceIdentifier) => b.path.length - a.path.length);
for (const sortedIdentifier of sortedIdentifiers) {
console.log(sortedIdentifier)
await this.source.deleteResource(sortedIdentifier);
}
throw new BadRequestHttpError("Not allowed to create new containers within a constrained container");
Expand Down
23 changes: 13 additions & 10 deletions src/storage/validators/ShaclValidator.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import type { AuxiliaryStrategy, RepresentationConverter } from '@solid/community-server';
import type {AuxiliaryStrategy, RepresentationConverter} from '@solid/community-server';
import {
BadRequestHttpError,
BasicRepresentation,
cloneRepresentation,
fetchDataset,
getLoggerFor,
InternalServerError,
INTERNAL_QUADS,
NotImplementedHttpError,
readableToQuads,
BasicRepresentation
readableToQuads
} from '@solid/community-server';
import type { Store } from 'n3';
import type {Store} from 'n3';
import SHACLValidator from 'rdf-validate-shacl';
import { LDP, SH } from '../../util/Vocabularies';
import type { ShapeValidatorInput } from './ShapeValidator';
import { ShapeValidator } from './ShapeValidator';
import {LDP, SH} from '../../util/Vocabularies';
import type {ShapeValidatorInput} from './ShapeValidator';
import {ShapeValidator} from './ShapeValidator';

/**
* Validates a Representation against SHACL shapes using an external SHACL validator.
Expand All @@ -39,14 +38,18 @@ export class ShaclValidator extends ShapeValidator {
if (!shapeURL) {
throw new NotImplementedHttpError('No ldp:constrainedBy predicate.');
}

if (representation.isEmpty) {
throw new BadRequestHttpError('Data could not be validated as it could not be converted to rdf');
}
}

public async handle(input: ShapeValidatorInput): Promise<void> {
const { parentRepresentation, representation } = input;
// Check if the parent has ldp:constrainedBy in the metadata
const shapeURL = parentRepresentation.metadata.get(LDP.terms.constrainedBy)!.value;
let representationData: BasicRepresentation;

// Convert the RDF representation to a N3.Store
let representationData: BasicRepresentation;
const preferences = { type: { [INTERNAL_QUADS]: 1 }};
try {
// Creating a new representation as the data might be written later by DataAccessorBasedStore
Expand Down
97 changes: 55 additions & 42 deletions test/integration/LdpHandlerWithoutAuth.test.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,8 @@
import { createReadStream } from 'fs';
import fetch from 'cross-fetch';
import type { Quad } from 'n3';
import { DataFactory, Parser, Store } from 'n3';
import { joinFilePath, PIM, RDF } from '@solid/community-server';
import type { App } from '@solid/community-server';
import {
deleteResource,
expectQuads,
getResource,
patchResource,
postResource,
putResource,
} from '../util/FetchUtil';
import { getPort } from '../util/Util';
import {DataFactory} from 'n3';
import type {App} from '@solid/community-server';
import {deleteResource, getResource, patchResource, postResource, putResource,} from '../util/FetchUtil';
import {getPort} from '../util/Util';
import {
getDefaultVariables,
getPresetConfigPath,
Expand All @@ -21,7 +11,6 @@ import {
instantiateFromConfig,
removeFolder,
} from './Config';
const { literal, namedNode, quad } = DataFactory;

const port = getPort('LpdHandlerWithoutAuth');
const baseUrl = `http://localhost:${port}/`;
Expand Down Expand Up @@ -76,10 +65,7 @@ describe.each(stores)('An LDP handler allowing all requests %s', (name, { storeC
const conformingResource = `<a> a <http://example.org/c>.
<a> <http://xmlns.com/foaf/0.1/name> "test".`;

it('can validate RDF resources using SHACL shapes.', async(): Promise<void> => {
const shapeURL = `${baseUrl}shape`;
const constrainedContainerURL = `${baseUrl}constrained/`;

async function initialiseShapeContainer(shapeURL: string, constrainedContainerURL: string): Promise<void> {
// PUT shape
await putResource(shapeURL, { contentType: 'text/turtle', body: shape });

Expand All @@ -97,6 +83,18 @@ describe.each(stores)('An LDP handler allowing all requests %s', (name, { storeC
const constrainedContainerResponse = await getResource(constrainedContainerURL);
expect(constrainedContainerResponse.headers.get('link'))
.toContain(`<${shapeURL}>; rel="http://www.w3.org/ns/ldp#constrainedBy"`);
}

async function cleanupShapeContainer(shapeURL: string, constrainedContainerURL: string): Promise<void> {
expect(await deleteResource(constrainedContainerURL)).toBeUndefined();
expect(await deleteResource(shapeURL)).toBeUndefined();
}

it('can validate RDF resources using SHACL shapes.', async(): Promise<void> => {
const shapeURL = `${baseUrl}shape`;
const constrainedContainerURL = `${baseUrl}constrained/`;

await initialiseShapeContainer(shapeURL, constrainedContainerURL);

// POST: Add resource to constrained container which is valid
const postResponse = await postResource(
Expand All @@ -120,42 +118,57 @@ describe.each(stores)('An LDP handler allowing all requests %s', (name, { storeC

// DELETE
expect(await deleteResource(postResponse.headers.get('location')!)).toBeUndefined();
expect(await deleteResource(constrainedContainerURL)).toBeUndefined();
expect(await deleteResource(shapeURL)).toBeUndefined();
await cleanupShapeContainer(shapeURL, constrainedContainerURL);
});

it('can not validate non-RDF resources using SHACL shapes.', async(): Promise<void> => {
const shapeURL = `${baseUrl}shape`;
const constrainedContainerURL = `${baseUrl}constrained/`;

// PUT shape
await putResource(shapeURL, { contentType: 'text/turtle', body: shape });

// PUT container for constrained resources
await putResource(constrainedContainerURL, { contentType: 'text/turtle' });

// PATCH: Add shape constraint to container
await patchResource(
constrainedContainerURL + metaSuffix,
`INSERT DATA { <${constrainedContainerURL}> <http://www.w3.org/ns/ldp#constrainedBy> <${shapeURL}>}`,
'sparql',
true,
);

const constrainedContainerResponse = await getResource(constrainedContainerURL);
expect(constrainedContainerResponse.headers.get('link'))
.toContain(`<${shapeURL}>; rel="http://www.w3.org/ns/ldp#constrainedBy"`);
await initialiseShapeContainer(shapeURL, constrainedContainerURL);

// POST non-RDF resource
const response1 = await fetch(constrainedContainerURL, {
const response = await fetch(constrainedContainerURL, {
method: 'POST',
headers: { 'content-type': 'text/plain' },
body: 'Hello world!',
});
expect(response1.status).toBe(400);
expect(response.status).toBe(400);

// DELETE
expect(await deleteResource(constrainedContainerURL)).toBeUndefined();
expect(await deleteResource(shapeURL)).toBeUndefined();
await cleanupShapeContainer(shapeURL, constrainedContainerURL);
});

it('can not created containers within a constrained container.', async (): Promise<void> => {
const shapeURL = `${baseUrl}shape`;
const constrainedContainerURL = `${baseUrl}constrained/`;

await initialiseShapeContainer(shapeURL, constrainedContainerURL);

// new container directly
const newContainerURL = `${constrainedContainerURL}shouldNotBeAllowed/`;

const containerCreateResponse = await fetch(newContainerURL,{
method: "PUT"
});

expect(containerCreateResponse.status).toBe(400);
const containerGetResponse = await fetch(newContainerURL);
expect(containerGetResponse.status).toBe(404);

// new container indirectly
const resourceURL = `${newContainerURL}resource`;

const resourceCreateResponse = await fetch(resourceURL,{
method: "PUT",
headers: {'content-type':'text/turtle'},
body: '<a> <b> <c>.'
});
expect(resourceCreateResponse.status).toBe(400);
const resourceGetResponse = await fetch(resourceURL);
expect(resourceGetResponse.status).toBe(404);

// DELETE
await cleanupShapeContainer(shapeURL, constrainedContainerURL);
});
});
12 changes: 7 additions & 5 deletions test/unit/storage/validators/ShaclValidator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { ShaclValidator } from '../../../../src/storage/validators/ShaclValidato
import type { ShapeValidatorInput } from '../../../../src/storage/validators/ShapeValidator';
import { INTERNAL_QUADS } from '@solid/community-server';
import { BadRequestHttpError } from '@solid/community-server';
import { InternalServerError } from '@solid/community-server';
import { NotImplementedHttpError } from '@solid/community-server';
import { fetchDataset } from '@solid/community-server';
import { guardedStreamFrom } from '@solid/community-server';
Expand Down Expand Up @@ -74,10 +73,6 @@ describe('ShaclValidator', (): void => {
jest.clearAllMocks();
});

it('throws error if the parent container is not constrained by a shape.', async(): Promise<void> => {
input.parentRepresentation = new BasicRepresentation();
await expect(validator.canHandle(input)).rejects.toThrow(Error);
});

it('does not validate when the parent container is not constrained by a shape.', async(): Promise<void> => {
input.parentRepresentation = new BasicRepresentation();
Expand All @@ -86,6 +81,13 @@ describe('ShaclValidator', (): void => {
expect(fetchDataset).toHaveBeenCalledTimes(0);
});

it('does not validate when the representation is empty.', async(): Promise<void> => {
input.representation = new BasicRepresentation()
await expect(validator.handleSafe(input)).resolves.toBeUndefined();
expect(converter.handleSafe).toHaveBeenCalledTimes(0);
expect(fetchDataset).toHaveBeenCalledTimes(0);
});

it('fetches the shape and validates the representation.', async(): Promise<void> => {
await expect(validator.handleSafe(input)).resolves.toBeUndefined();
expect(converter.handleSafe).toHaveBeenCalledTimes(1);
Expand Down

0 comments on commit 8938b2f

Please sign in to comment.