Skip to content

Commit a986f99

Browse files
committed
test: add unit integration tests
1 parent 83c01f8 commit a986f99

File tree

9 files changed

+759
-3
lines changed

9 files changed

+759
-3
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
- [x] make https://github.com/woutslabbinck/community-server/tree/feat/shape-support work in this directory
66
- [x] override link header attempt
7-
- [ ] make the tests work
7+
- [x] make the tests work
8+
- [x] unit
9+
- [x] integration
810
- [ ] add README.md
911
- [ ] check everything at https://trello.com/c/JzMVInXw/81-shape-support-pr-after-metadata-editing
1012
- [ ] ask feedback Joachim (zal pas na Januari zijn)
11-
- [ ] publish op npm: https://www.npmjs.com/org/community-solid-server
13+
- [ ] publish op npm: https://www.npmjs.com/org/community-solid-server

package-lock.json

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"@types/node-fetch": "^2.6.2",
3939
"componentsjs-generator": "^3.1.0",
4040
"jest": "^29.1.1",
41+
"jest-rdf": "^1.7.0",
4142
"node-fetch": "^2.6.7",
4243
"ts-jest": "^29.0.3",
4344
"typescript": "^4.7.4"

src/util/Vocabularies.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ export const LDP = createUriAndTermNamespace('http://www.w3.org/ns/ldp#',
1717
'Container',
1818
'Resource',
1919
'constrainedBy',
20-
);
20+
);

test/integration/Config.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import type { IModuleState } from 'componentsjs';
2+
import { ComponentsManager } from 'componentsjs';
3+
import { remove } from 'fs-extra';
4+
import { joinFilePath } from '@solid/community-server';
5+
6+
let cachedModuleState: IModuleState;
7+
8+
/**
9+
* Returns a component instantiated from a Components.js configuration.
10+
*/
11+
export async function instantiateFromConfig(componentUrl: string, configPaths: string | string[],
12+
variables?: Record<string, any>): Promise<any> {
13+
// Initialize the Components.js loader
14+
const mainModulePath = joinFilePath(__dirname, '../../');
15+
const manager = await ComponentsManager.build({
16+
mainModulePath,
17+
logLevel: 'error',
18+
moduleState: cachedModuleState,
19+
typeChecking: false,
20+
});
21+
cachedModuleState = manager.moduleState;
22+
23+
if (!Array.isArray(configPaths)) {
24+
configPaths = [ configPaths ];
25+
}
26+
27+
// Instantiate the component from the config(s)
28+
for (const configPath of configPaths) {
29+
await manager.configRegistry.register(configPath);
30+
}
31+
return await manager.instantiate(componentUrl, { variables });
32+
}
33+
34+
export function getTestConfigPath(configFile: string): string {
35+
return joinFilePath(__dirname, 'config', configFile);
36+
}
37+
38+
export function getPresetConfigPath(configFile: string): string {
39+
// points to CSS config directory in node modules
40+
return joinFilePath(__dirname, '../../node_modules/@solid/community-server/config', configFile);
41+
}
42+
43+
export function getTestFolder(name: string): string {
44+
return joinFilePath(__dirname, '../tmp', name);
45+
}
46+
47+
export async function removeFolder(folder: string): Promise<void> {
48+
await remove(folder);
49+
}
50+
51+
export function getDefaultVariables(port: number, baseUrl?: string): Record<string, any> {
52+
return {
53+
'urn:solid-server:default:variable:baseUrl': baseUrl ?? `http://localhost:${port}/`,
54+
'urn:solid-server:default:variable:port': port,
55+
'urn:solid-server:default:variable:loggingLevel': 'off',
56+
'urn:solid-server:default:variable:showStackTrace': true,
57+
'urn:solid-server:default:variable:seededPodConfigJson': null,
58+
'urn:solid-server:default:variable:workers': 1,
59+
};
60+
}
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { createReadStream } from 'fs';
2+
import fetch from 'cross-fetch';
3+
import type { Quad } from 'n3';
4+
import { DataFactory, Parser, Store } from 'n3';
5+
import { joinFilePath, PIM, RDF } from '@solid/community-server';
6+
import type { App } from '@solid/community-server';
7+
import {
8+
deleteResource,
9+
expectQuads,
10+
getResource,
11+
patchResource,
12+
postResource,
13+
putResource,
14+
} from '../util/FetchUtil';
15+
import { getPort } from '../util/Util';
16+
import {
17+
getDefaultVariables,
18+
getPresetConfigPath,
19+
getTestConfigPath,
20+
getTestFolder,
21+
instantiateFromConfig,
22+
removeFolder,
23+
} from './Config';
24+
const { literal, namedNode, quad } = DataFactory;
25+
26+
const port = getPort('LpdHandlerWithoutAuth');
27+
const baseUrl = `http://localhost:${port}/`;
28+
const metaSuffix = '.meta';
29+
const rootFilePath = getTestFolder('full-config-no-auth');
30+
const stores: [string, any][] = [
31+
[ 'in-memory storage', {
32+
storeConfig: 'storage/backend/memory.json',
33+
teardown: jest.fn(),
34+
}],
35+
[ 'on-disk storage', {
36+
storeConfig: 'storage/backend/file.json',
37+
teardown: async(): Promise<void> => removeFolder(rootFilePath),
38+
}],
39+
];
40+
41+
describe.each(stores)('An LDP handler allowing all requests %s', (name, { storeConfig, teardown }): void => {
42+
let app: App;
43+
44+
beforeAll(async(): Promise<void> => {
45+
const variables = {
46+
...getDefaultVariables(port, baseUrl),
47+
'urn:solid-server:default:variable:rootFilePath': rootFilePath,
48+
};
49+
50+
// Create and start the server
51+
const instances = await instantiateFromConfig(
52+
'urn:solid-server:test:Instances',
53+
[
54+
getPresetConfigPath(storeConfig),
55+
getTestConfigPath('ldp-with-auth.json'),
56+
],
57+
variables,
58+
) as Record<string, any>;
59+
({ app } = instances);
60+
61+
await app.start();
62+
});
63+
64+
afterAll(async(): Promise<void> => {
65+
await teardown();
66+
await app.stop();
67+
});
68+
69+
const shape = `<http://example.org/exampleshape> a <http://www.w3.org/ns/shacl#NodeShape>.
70+
<http://example.org/exampleshape> <http://www.w3.org/ns/shacl#targetClass> <http://example.org/c>.
71+
<http://example.org/exampleshape> <http://www.w3.org/ns/shacl#property> <http://example.org/property>.
72+
<http://example.org/property> <http://www.w3.org/ns/shacl#path> <http://xmlns.com/foaf/0.1/name>.
73+
<http://example.org/property> <http://www.w3.org/ns/shacl#minCount> 1.
74+
<http://example.org/property> <http://www.w3.org/ns/shacl#maxCount> 1.
75+
<http://example.org/property> <http://www.w3.org/ns/shacl#datatype> <http://www.w3.org/2001/XMLSchema#string>.`;
76+
const conformingResource = `<a> a <http://example.org/c>.
77+
<a> <http://xmlns.com/foaf/0.1/name> "test".`;
78+
79+
it('can validate RDF resources using SHACL shapes.', async(): Promise<void> => {
80+
const shapeURL = `${baseUrl}shape`;
81+
const constrainedContainerURL = `${baseUrl}constrained/`;
82+
83+
// PUT shape
84+
await putResource(shapeURL, { contentType: 'text/turtle', body: shape });
85+
86+
// PUT container for constrained resources
87+
await putResource(constrainedContainerURL, { contentType: 'text/turtle' });
88+
89+
// PATCH: Add shape constraint to container
90+
await patchResource(
91+
constrainedContainerURL + metaSuffix,
92+
`INSERT DATA { <${constrainedContainerURL}> <http://www.w3.org/ns/ldp#constrainedBy> <${shapeURL}>}`,
93+
'sparql',
94+
true,
95+
);
96+
97+
const constrainedContainerResponse = await getResource(constrainedContainerURL);
98+
expect(constrainedContainerResponse.headers.get('link'))
99+
.toContain(`<${shapeURL}>; rel="http://www.w3.org/ns/ldp#constrainedBy"`);
100+
101+
// POST: Add resource to constrained container which is valid
102+
const postResponse = await postResource(
103+
constrainedContainerURL, { contentType: 'text/turtle', body: conformingResource },
104+
);
105+
106+
// POST: Add resource to constrained container that is not valid
107+
const response1 = await fetch(constrainedContainerURL, {
108+
method: 'POST',
109+
headers: { 'content-type': 'text/turtle' },
110+
body: '<a> <b> <c>.',
111+
});
112+
expect(response1.status).toBe(400);
113+
114+
// PUT: Add container resource (invalid)
115+
const response2 = await fetch(`${constrainedContainerURL}container/`, {
116+
method: 'PUT',
117+
headers: { 'content-type': 'text/turtle' },
118+
});
119+
expect(response2.status).toBe(400);
120+
121+
// DELETE
122+
expect(await deleteResource(postResponse.headers.get('location')!)).toBeUndefined();
123+
expect(await deleteResource(constrainedContainerURL)).toBeUndefined();
124+
expect(await deleteResource(shapeURL)).toBeUndefined();
125+
});
126+
127+
it('can not validate non-RDF resources using SHACL shapes.', async(): Promise<void> => {
128+
const shapeURL = `${baseUrl}shape`;
129+
const constrainedContainerURL = `${baseUrl}constrained/`;
130+
131+
// PUT shape
132+
await putResource(shapeURL, { contentType: 'text/turtle', body: shape });
133+
134+
// PUT container for constrained resources
135+
await putResource(constrainedContainerURL, { contentType: 'text/turtle' });
136+
137+
// PATCH: Add shape constraint to container
138+
await patchResource(
139+
constrainedContainerURL + metaSuffix,
140+
`INSERT DATA { <${constrainedContainerURL}> <http://www.w3.org/ns/ldp#constrainedBy> <${shapeURL}>}`,
141+
'sparql',
142+
true,
143+
);
144+
145+
const constrainedContainerResponse = await getResource(constrainedContainerURL);
146+
expect(constrainedContainerResponse.headers.get('link'))
147+
.toContain(`<${shapeURL}>; rel="http://www.w3.org/ns/ldp#constrainedBy"`);
148+
149+
// POST non-RDF resource
150+
const response1 = await fetch(constrainedContainerURL, {
151+
method: 'POST',
152+
headers: { 'content-type': 'text/plain' },
153+
body: 'Hello world!',
154+
});
155+
expect(response1.status).toBe(400);
156+
157+
// DELETE
158+
expect(await deleteResource(constrainedContainerURL)).toBeUndefined();
159+
expect(await deleteResource(shapeURL)).toBeUndefined();
160+
});
161+
});
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"@context":[
3+
"https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^5.0.0/components/context.jsonld",
4+
"https://linkedsoftwaredependencies.org/bundles/npm/shape-validation-component/^5.0.0/components/context.jsonld"
5+
],
6+
"import": [
7+
"css:config/app/main/default.json",
8+
"css:config/app/init/initialize-root.json",
9+
"css:config/app/setup/disabled.json",
10+
"css:config/http/handler/simple.json",
11+
"css:config/http/middleware/no-websockets.json",
12+
"css:config/http/server-factory/no-websockets.json",
13+
"css:config/http/static/default.json",
14+
"css:config/identity/access/public.json",
15+
"css:config/identity/handler/default.json",
16+
"css:config/identity/ownership/token.json",
17+
"css:config/identity/pod/static.json",
18+
"css:config/ldp/authentication/debug-auth-header.json",
19+
"css:config/ldp/authorization/webacl.json",
20+
"css:config/ldp/handler/default.json",
21+
"css:config/ldp/metadata-parser/default.json",
22+
"css:config/ldp/metadata-writer/default.json",
23+
"css:config/ldp/modes/default.json",
24+
"css:config/storage/key-value/memory.json",
25+
"css:config/storage/middleware/default.json",
26+
"css:config/util/auxiliary/acl.json",
27+
"css:config/util/identifiers/suffix.json",
28+
"css:config/util/index/default.json",
29+
"css:config/util/logging/winston.json",
30+
"css:config/util/representation-conversion/default.json",
31+
"css:config/util/resource-locker/memory.json",
32+
"css:config/util/variables/default.json",
33+
34+
"shape-validation:config/shape-validation.json"
35+
],
36+
"@graph": [
37+
{
38+
"comment": "An HTTP server with only the LDP handler as HttpHandler and an unsecure authenticator.",
39+
"@id": "urn:solid-server:test:Instances",
40+
"@type": "RecordObject",
41+
"record": [
42+
{
43+
"RecordObject:_record_key": "app",
44+
"RecordObject:_record_value": { "@id": "urn:solid-server:default:App" }
45+
},
46+
{
47+
"RecordObject:_record_key": "store",
48+
"RecordObject:_record_value": { "@id": "urn:solid-server:default:ResourceStore" }
49+
}
50+
]
51+
}
52+
]
53+
}

0 commit comments

Comments
 (0)