mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: More integration tests and test configs (#154)
* add todo * Added configurations folder * Added config to index * changed /bin/server to use configfiles * initiate acl with Setup component * add last changes * reset serverconfig * move authenticatedLdpHandler configs to config files * failed to read testss * failed to read testss * removed import part * Fix merge conflicts * add first FileResTests * fix: fix fileresourcestore metadata error * fix unit tests * remove test files * Added test and changed callFile * Fix: metadata file error in FileResourceStore * fix: ensure full test coverage * added tests * Fix get tests * added testfiles * changed config to use PasstrueStore * to continue work on * refactor fileresourcestore config * refactor tests * fix content-types, update tests * replace sync methods with async methods * move acl function to util * added testfiles for Fileserver with acl * update tests * add first acl filestore test * refactor * add resource mapper * refactor config files * add more fileresourcestore acl tests * add locking resource store test files * move file mapping logic to resourcemapper * added beforeAll for a permanent file in Auht tests * make filestore dependent of resource mapper * moved configs to test/configs * set default contenttype * refactor fileresourcemapper * fix map function * refactor * fixed foldercreationtest * changed some tests so the files are cleaned up when done testing * add normalized parser * Auhtenticationtests clean up acl file * refactor unit test * lost changes added again * fix metadata problem * refactor names * reverse change * add getters * configs and start util * add comments * add comments, move code * added acl helper and changed tests * linter 7.7.0 -> 7.0.0 * moved test/tesfiles -> test/assets * removed configs/**/*.ts from tsconfig.json * Temporary changed threshold so cli test is ignored and commiting goes easier, will revert later * added FileResourceStore to index * Changed imports * Changed imports for all configs * Removed comment * Changed names of configs * added 'Config' to name and removed comment * removed unused testfile and added testfile 0 * changed beforeAll to just copy permanent file * change text/turtle to constant * fix converter issue * getHandler -> getHttpHandler, and updates to config * removed ',' * removed trailing / * changed imports for index.d.js problem * removed duplicate file and added line that got removed in mergeconflicts * add jest global teardown * add ignore for CliRunner * add changes * fix copyfile error * remove unused testfiles * adding test with image * add first util functions * relative paths to absolute paths * added 3 FileStoreTests * more refactoring * more absolute paths * fix mkdir path * added test * add util for easy configs * add comments * added some testhelpers and refactor first test * fix converter test error * refactor FileResTests * solved failing test because new converters * removed afterAll() * removed setAcl from util * removed config from Authorization.test.ts * changed strange linting * refactored AuthenticatedFileResourceStore tests * fixed unclear root variable * fix: Use absolute test paths * Mock fs correctly and remove teardown * Clean up after tests Co-authored-by: freyavs <freyavanspeybroeck@outlook.com> Co-authored-by: thdossch <dossche.thor@gmail.com> Co-authored-by: Freya <56410697+freyavs@users.noreply.github.com> Co-authored-by: thdossch <49074469+thdossch@users.noreply.github.com> Co-authored-by: Ruben Verborgh <ruben@verborgh.org>
This commit is contained in:
parent
383da24601
commit
b1991cb08a
@ -46,6 +46,7 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
'unused-imports/no-unused-imports-ts': 'error',
|
'unused-imports/no-unused-imports-ts': 'error',
|
||||||
'import/no-extraneous-dependencies': 'error'
|
'import/no-extraneous-dependencies': 'error',
|
||||||
|
'unicorn/import-index': 'off'
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
3
index.ts
3
index.ts
@ -79,6 +79,7 @@ export * from './src/storage/patch/SparqlUpdatePatchHandler';
|
|||||||
export * from './src/storage/AtomicResourceStore';
|
export * from './src/storage/AtomicResourceStore';
|
||||||
export * from './src/storage/Conditions';
|
export * from './src/storage/Conditions';
|
||||||
export * from './src/storage/ContainerManager';
|
export * from './src/storage/ContainerManager';
|
||||||
|
export * from './src/storage/FileResourceStore';
|
||||||
export * from './src/storage/InMemoryResourceStore';
|
export * from './src/storage/InMemoryResourceStore';
|
||||||
export * from './src/storage/Lock';
|
export * from './src/storage/Lock';
|
||||||
export * from './src/storage/LockingResourceStore';
|
export * from './src/storage/LockingResourceStore';
|
||||||
@ -103,4 +104,6 @@ export * from './src/util/errors/UnsupportedMediaTypeHttpError';
|
|||||||
export * from './src/util/AcceptParser';
|
export * from './src/util/AcceptParser';
|
||||||
export * from './src/util/AsyncHandler';
|
export * from './src/util/AsyncHandler';
|
||||||
export * from './src/util/CompositeAsyncHandler';
|
export * from './src/util/CompositeAsyncHandler';
|
||||||
|
export * from './src/util/InteractionController';
|
||||||
|
export * from './src/util/MetadataController';
|
||||||
export * from './src/util/Util';
|
export * from './src/util/Util';
|
||||||
|
@ -16,7 +16,8 @@ module.exports = {
|
|||||||
"setupFilesAfterEnv": ["jest-rdf"],
|
"setupFilesAfterEnv": ["jest-rdf"],
|
||||||
"collectCoverage": true,
|
"collectCoverage": true,
|
||||||
"coveragePathIgnorePatterns": [
|
"coveragePathIgnorePatterns": [
|
||||||
"/node_modules/"
|
"/node_modules/",
|
||||||
|
"/src/init/CliRunner.ts"
|
||||||
],
|
],
|
||||||
"coverageThreshold": {
|
"coverageThreshold": {
|
||||||
"./src": {
|
"./src": {
|
||||||
|
43
package-lock.json
generated
43
package-lock.json
generated
@ -1324,6 +1324,16 @@
|
|||||||
"@types/range-parser": "*"
|
"@types/range-parser": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/glob": {
|
||||||
|
"version": "7.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz",
|
||||||
|
"integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/minimatch": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/graceful-fs": {
|
"@types/graceful-fs": {
|
||||||
"version": "4.1.3",
|
"version": "4.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.3.tgz",
|
||||||
@ -1411,6 +1421,12 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.0.tgz",
|
||||||
"integrity": "sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM="
|
"integrity": "sha1-nKUs2jY/aZxpRmwqbM2q2RPqenM="
|
||||||
},
|
},
|
||||||
|
"@types/minimatch": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/minimist": {
|
"@types/minimist": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz",
|
||||||
@ -1471,6 +1487,16 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/rimraf": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-7WhJ0MdpFgYQPXlF4Dx+DhgvlPCfz/x5mHaeDQAKhcenvQP1KCpLQ18JklAqeGMYSAT2PxLpzd0g2/HE7fj7hQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/glob": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/serve-static": {
|
"@types/serve-static": {
|
||||||
"version": "1.13.5",
|
"version": "1.13.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.5.tgz",
|
||||||
@ -4349,6 +4375,17 @@
|
|||||||
"flatted": "^2.0.0",
|
"flatted": "^2.0.0",
|
||||||
"rimraf": "2.6.3",
|
"rimraf": "2.6.3",
|
||||||
"write": "1.0.3"
|
"write": "1.0.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"rimraf": {
|
||||||
|
"version": "2.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
|
||||||
|
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"glob": "^7.1.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flatted": {
|
"flatted": {
|
||||||
@ -8780,9 +8817,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"rimraf": {
|
"rimraf": {
|
||||||
"version": "2.6.3",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||||
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
|
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"glob": "^7.1.3"
|
"glob": "^7.1.3"
|
||||||
|
@ -91,6 +91,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^26.0.0",
|
"@types/jest": "^26.0.0",
|
||||||
|
"@types/rimraf": "^3.0.0",
|
||||||
"@types/supertest": "^2.0.10",
|
"@types/supertest": "^2.0.10",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.33.0",
|
"@typescript-eslint/eslint-plugin": "^2.33.0",
|
||||||
"@typescript-eslint/parser": "^2.33.0",
|
"@typescript-eslint/parser": "^2.33.0",
|
||||||
@ -108,6 +109,7 @@
|
|||||||
"manual-git-changelog": "^1.0.1",
|
"manual-git-changelog": "^1.0.1",
|
||||||
"node-mocks-http": "^1.8.1",
|
"node-mocks-http": "^1.8.1",
|
||||||
"nodemon": "^2.0.4",
|
"nodemon": "^2.0.4",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
"stream-to-string": "^1.1.0",
|
"stream-to-string": "^1.1.0",
|
||||||
"supertest": "^4.0.2",
|
"supertest": "^4.0.2",
|
||||||
"ts-jest": "^26.0.0",
|
"ts-jest": "^26.0.0",
|
||||||
|
1
test/assets/permanent.txt
Normal file
1
test/assets/permanent.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
TEST
|
1
test/assets/testfile0.txt
Normal file
1
test/assets/testfile0.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
TESTFILE0
|
1
test/assets/testfile1.txt
Normal file
1
test/assets/testfile1.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
TESTFILE1
|
1
test/assets/testfile2.txt
Normal file
1
test/assets/testfile2.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
TESTFILE2
|
BIN
test/assets/testimage.png
Normal file
BIN
test/assets/testimage.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 169 B |
66
test/configs/AuthenticatedFileResourceStoreConfig.ts
Normal file
66
test/configs/AuthenticatedFileResourceStoreConfig.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import {
|
||||||
|
AuthenticatedLdpHandler,
|
||||||
|
BasicResponseWriter,
|
||||||
|
CompositeAsyncHandler,
|
||||||
|
HttpHandler,
|
||||||
|
MethodPermissionsExtractor,
|
||||||
|
RdfToQuadConverter,
|
||||||
|
ResourceStore,
|
||||||
|
UnsecureWebIdExtractor,
|
||||||
|
QuadToRdfConverter,
|
||||||
|
RuntimeConfig,
|
||||||
|
} from '../../index';
|
||||||
|
import { ServerConfig } from './ServerConfig';
|
||||||
|
import {
|
||||||
|
getFileResourceStore,
|
||||||
|
getConvertingStore,
|
||||||
|
getBasicRequestParser,
|
||||||
|
getOperationHandler,
|
||||||
|
getWebAclAuthorizer,
|
||||||
|
} from './Util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AuthenticatedFileResourceStoreConfig works with
|
||||||
|
* - a WebAclAuthorizer
|
||||||
|
* - a FileResourceStore wrapped in a converting store (rdf to quad & quad to rdf)
|
||||||
|
* - GET, POST, PUT & DELETE operation handlers
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class AuthenticatedFileResourceStoreConfig implements ServerConfig {
|
||||||
|
private readonly runtimeConfig: RuntimeConfig;
|
||||||
|
public store: ResourceStore;
|
||||||
|
|
||||||
|
public constructor(runtimeConfig: RuntimeConfig) {
|
||||||
|
this.runtimeConfig = runtimeConfig;
|
||||||
|
this.store = getConvertingStore(
|
||||||
|
getFileResourceStore(runtimeConfig),
|
||||||
|
[ new QuadToRdfConverter(),
|
||||||
|
new RdfToQuadConverter() ],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getHttpHandler(): HttpHandler {
|
||||||
|
const requestParser = getBasicRequestParser();
|
||||||
|
|
||||||
|
const credentialsExtractor = new UnsecureWebIdExtractor();
|
||||||
|
const permissionsExtractor = new CompositeAsyncHandler([
|
||||||
|
new MethodPermissionsExtractor(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const operationHandler = getOperationHandler(this.store);
|
||||||
|
|
||||||
|
const responseWriter = new BasicResponseWriter();
|
||||||
|
const authorizer = getWebAclAuthorizer(this.store, this.runtimeConfig.base);
|
||||||
|
|
||||||
|
const handler = new AuthenticatedLdpHandler({
|
||||||
|
requestParser,
|
||||||
|
credentialsExtractor,
|
||||||
|
permissionsExtractor,
|
||||||
|
authorizer,
|
||||||
|
operationHandler,
|
||||||
|
responseWriter,
|
||||||
|
});
|
||||||
|
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
}
|
49
test/configs/BasicConfig.ts
Normal file
49
test/configs/BasicConfig.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import {
|
||||||
|
AllowEverythingAuthorizer,
|
||||||
|
AuthenticatedLdpHandler,
|
||||||
|
BasicResponseWriter,
|
||||||
|
HttpHandler,
|
||||||
|
MethodPermissionsExtractor,
|
||||||
|
ResourceStore,
|
||||||
|
UnsecureWebIdExtractor,
|
||||||
|
} from '../../index';
|
||||||
|
import { ServerConfig } from './ServerConfig';
|
||||||
|
import { getOperationHandler, getInMemoryResourceStore, getBasicRequestParser } from './Util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BasicConfig works with
|
||||||
|
* - an AllowEverythingAuthorizer (no acl)
|
||||||
|
* - an InMemoryResourceStore
|
||||||
|
* - GET, POST & DELETE operation handlers
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class BasicConfig implements ServerConfig {
|
||||||
|
public store: ResourceStore;
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
this.store = getInMemoryResourceStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getHttpHandler(): HttpHandler {
|
||||||
|
const requestParser = getBasicRequestParser();
|
||||||
|
|
||||||
|
const credentialsExtractor = new UnsecureWebIdExtractor();
|
||||||
|
const permissionsExtractor = new MethodPermissionsExtractor();
|
||||||
|
const authorizer = new AllowEverythingAuthorizer();
|
||||||
|
|
||||||
|
const operationHandler = getOperationHandler(this.store);
|
||||||
|
|
||||||
|
const responseWriter = new BasicResponseWriter();
|
||||||
|
|
||||||
|
const handler = new AuthenticatedLdpHandler({
|
||||||
|
requestParser,
|
||||||
|
credentialsExtractor,
|
||||||
|
permissionsExtractor,
|
||||||
|
authorizer,
|
||||||
|
operationHandler,
|
||||||
|
responseWriter,
|
||||||
|
});
|
||||||
|
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
}
|
69
test/configs/BasicHandlersConfig.ts
Normal file
69
test/configs/BasicHandlersConfig.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import {
|
||||||
|
AllowEverythingAuthorizer,
|
||||||
|
AuthenticatedLdpHandler,
|
||||||
|
BasicResponseWriter,
|
||||||
|
CompositeAsyncHandler,
|
||||||
|
HttpHandler,
|
||||||
|
MethodPermissionsExtractor,
|
||||||
|
QuadToRdfConverter,
|
||||||
|
RawBodyParser,
|
||||||
|
RdfToQuadConverter,
|
||||||
|
ResourceStore,
|
||||||
|
SparqlUpdateBodyParser,
|
||||||
|
SparqlPatchPermissionsExtractor,
|
||||||
|
UnsecureWebIdExtractor,
|
||||||
|
} from '../../index';
|
||||||
|
|
||||||
|
import { ServerConfig } from './ServerConfig';
|
||||||
|
import { getInMemoryResourceStore,
|
||||||
|
getOperationHandler,
|
||||||
|
getConvertingStore,
|
||||||
|
getPatchingStore, getBasicRequestParser } from './Util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BasicHandlersConfig works with
|
||||||
|
* - an AllowEverythingAuthorizer (no acl)
|
||||||
|
* - an InMemoryResourceStore wrapped in a converting store & wrapped in a patching store
|
||||||
|
* - GET, POST, PUT, PATCH & DELETE operation handlers
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class BasicHandlersConfig implements ServerConfig {
|
||||||
|
public store: ResourceStore;
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
const convertingStore = getConvertingStore(
|
||||||
|
getInMemoryResourceStore(),
|
||||||
|
[ new QuadToRdfConverter(), new RdfToQuadConverter() ],
|
||||||
|
);
|
||||||
|
this.store = getPatchingStore(convertingStore);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getHttpHandler(): HttpHandler {
|
||||||
|
const requestParser = getBasicRequestParser([
|
||||||
|
new SparqlUpdateBodyParser(),
|
||||||
|
new RawBodyParser(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const credentialsExtractor = new UnsecureWebIdExtractor();
|
||||||
|
const permissionsExtractor = new CompositeAsyncHandler([
|
||||||
|
new MethodPermissionsExtractor(),
|
||||||
|
new SparqlPatchPermissionsExtractor(),
|
||||||
|
]);
|
||||||
|
const authorizer = new AllowEverythingAuthorizer();
|
||||||
|
|
||||||
|
const operationHandler = getOperationHandler(this.store);
|
||||||
|
|
||||||
|
const responseWriter = new BasicResponseWriter();
|
||||||
|
|
||||||
|
const handler = new AuthenticatedLdpHandler({
|
||||||
|
requestParser,
|
||||||
|
credentialsExtractor,
|
||||||
|
permissionsExtractor,
|
||||||
|
authorizer,
|
||||||
|
operationHandler,
|
||||||
|
responseWriter,
|
||||||
|
});
|
||||||
|
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
}
|
63
test/configs/BasicHandlersWithAclConfig.ts
Normal file
63
test/configs/BasicHandlersWithAclConfig.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import {
|
||||||
|
AuthenticatedLdpHandler,
|
||||||
|
BasicResponseWriter,
|
||||||
|
CompositeAsyncHandler,
|
||||||
|
HttpHandler,
|
||||||
|
MethodPermissionsExtractor,
|
||||||
|
RdfToQuadConverter,
|
||||||
|
ResourceStore,
|
||||||
|
UnsecureWebIdExtractor,
|
||||||
|
QuadToRdfConverter,
|
||||||
|
} from '../../index';
|
||||||
|
import { ServerConfig } from './ServerConfig';
|
||||||
|
import {
|
||||||
|
getInMemoryResourceStore,
|
||||||
|
getConvertingStore,
|
||||||
|
getBasicRequestParser,
|
||||||
|
getOperationHandler,
|
||||||
|
getWebAclAuthorizer,
|
||||||
|
} from './Util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BasicHandlersWithAclConfig works with
|
||||||
|
* - an WebAclAuthorizer
|
||||||
|
* - an InMemoryResourceStore wrapped in a converting store & wrapped in a patching store
|
||||||
|
* - GET, POST, PUT, PATCH & DELETE operation handlers
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class BasicHandlersWithAclConfig implements ServerConfig {
|
||||||
|
public store: ResourceStore;
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
this.store = getConvertingStore(
|
||||||
|
getInMemoryResourceStore(),
|
||||||
|
[ new QuadToRdfConverter(),
|
||||||
|
new RdfToQuadConverter() ],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getHttpHandler(): HttpHandler {
|
||||||
|
const requestParser = getBasicRequestParser();
|
||||||
|
|
||||||
|
const credentialsExtractor = new UnsecureWebIdExtractor();
|
||||||
|
const permissionsExtractor = new CompositeAsyncHandler([
|
||||||
|
new MethodPermissionsExtractor(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const operationHandler = getOperationHandler(this.store);
|
||||||
|
|
||||||
|
const responseWriter = new BasicResponseWriter();
|
||||||
|
const authorizer = getWebAclAuthorizer(this.store);
|
||||||
|
|
||||||
|
const handler = new AuthenticatedLdpHandler({
|
||||||
|
requestParser,
|
||||||
|
credentialsExtractor,
|
||||||
|
permissionsExtractor,
|
||||||
|
authorizer,
|
||||||
|
operationHandler,
|
||||||
|
responseWriter,
|
||||||
|
});
|
||||||
|
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
}
|
59
test/configs/FileResourceStoreConfig.ts
Normal file
59
test/configs/FileResourceStoreConfig.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import {
|
||||||
|
AllowEverythingAuthorizer,
|
||||||
|
AuthenticatedLdpHandler,
|
||||||
|
BasicResponseWriter,
|
||||||
|
CompositeAsyncHandler,
|
||||||
|
HttpHandler,
|
||||||
|
MethodPermissionsExtractor,
|
||||||
|
QuadToRdfConverter,
|
||||||
|
RawBodyParser,
|
||||||
|
RdfToQuadConverter,
|
||||||
|
ResourceStore,
|
||||||
|
RuntimeConfig,
|
||||||
|
UnsecureWebIdExtractor,
|
||||||
|
} from '../../index';
|
||||||
|
import { ServerConfig } from './ServerConfig';
|
||||||
|
import { getFileResourceStore, getOperationHandler, getConvertingStore, getBasicRequestParser } from './Util';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FileResourceStoreConfig works with
|
||||||
|
* - an AllowEverythingAuthorizer (no acl)
|
||||||
|
* - a FileResourceStore wrapped in a converting store (rdf to quad & quad to rdf)
|
||||||
|
* - GET, POST, PUT & DELETE operation handlers
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class FileResourceStoreConfig implements ServerConfig {
|
||||||
|
public store: ResourceStore;
|
||||||
|
|
||||||
|
public constructor(runtimeConfig: RuntimeConfig) {
|
||||||
|
this.store = getConvertingStore(
|
||||||
|
getFileResourceStore(runtimeConfig),
|
||||||
|
[ new QuadToRdfConverter(), new RdfToQuadConverter() ],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getHttpHandler(): HttpHandler {
|
||||||
|
// This is for the sake of test coverage, as it could also be just getBasicRequestParser()
|
||||||
|
const requestParser = getBasicRequestParser([ new RawBodyParser() ]);
|
||||||
|
|
||||||
|
const credentialsExtractor = new UnsecureWebIdExtractor();
|
||||||
|
const permissionsExtractor = new CompositeAsyncHandler([
|
||||||
|
new MethodPermissionsExtractor(),
|
||||||
|
]);
|
||||||
|
const authorizer = new AllowEverythingAuthorizer();
|
||||||
|
|
||||||
|
const operationHandler = getOperationHandler(this.store);
|
||||||
|
const responseWriter = new BasicResponseWriter();
|
||||||
|
|
||||||
|
const handler = new AuthenticatedLdpHandler({
|
||||||
|
requestParser,
|
||||||
|
credentialsExtractor,
|
||||||
|
permissionsExtractor,
|
||||||
|
authorizer,
|
||||||
|
operationHandler,
|
||||||
|
responseWriter,
|
||||||
|
});
|
||||||
|
|
||||||
|
return handler;
|
||||||
|
}
|
||||||
|
}
|
5
test/configs/ServerConfig.ts
Normal file
5
test/configs/ServerConfig.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { HttpHandler } from '../../src/server/HttpHandler';
|
||||||
|
|
||||||
|
export interface ServerConfig {
|
||||||
|
getHttpHandler(): HttpHandler;
|
||||||
|
}
|
143
test/configs/Util.ts
Normal file
143
test/configs/Util.ts
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import { join } from 'path';
|
||||||
|
import {
|
||||||
|
AcceptPreferenceParser,
|
||||||
|
BasicRequestParser,
|
||||||
|
BasicTargetExtractor,
|
||||||
|
BodyParser,
|
||||||
|
CompositeAsyncHandler,
|
||||||
|
DeleteOperationHandler,
|
||||||
|
FileResourceStore,
|
||||||
|
GetOperationHandler,
|
||||||
|
HttpRequest,
|
||||||
|
InMemoryResourceStore,
|
||||||
|
InteractionController,
|
||||||
|
MetadataController,
|
||||||
|
Operation,
|
||||||
|
PatchingStore,
|
||||||
|
PatchOperationHandler,
|
||||||
|
PostOperationHandler,
|
||||||
|
PutOperationHandler,
|
||||||
|
RawBodyParser,
|
||||||
|
Representation,
|
||||||
|
RepresentationConverter,
|
||||||
|
RepresentationConvertingStore,
|
||||||
|
ResourceStore,
|
||||||
|
ResponseDescription,
|
||||||
|
RuntimeConfig,
|
||||||
|
SingleThreadedResourceLocker,
|
||||||
|
SparqlUpdatePatchHandler,
|
||||||
|
UrlBasedAclManager,
|
||||||
|
UrlContainerManager,
|
||||||
|
WebAclAuthorizer,
|
||||||
|
} from '../../index';
|
||||||
|
import { ExtensionBasedMapper } from '../../src/storage/ExtensionBasedMapper';
|
||||||
|
|
||||||
|
const BASE = 'http://test.com';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a RuntimeConfig with its rootFilePath set based on the given subfolder.
|
||||||
|
* @param subfolder - Folder to use in the global testData folder.
|
||||||
|
*/
|
||||||
|
export const getRuntimeConfig = (subfolder: string): RuntimeConfig => new RuntimeConfig({
|
||||||
|
base: BASE,
|
||||||
|
rootFilepath: join(__dirname, '../testData', subfolder),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives a file resource store based on (default) runtime config.
|
||||||
|
* @param runtimeConfig - Optional runtime config.
|
||||||
|
*
|
||||||
|
* @returns The file resource store.
|
||||||
|
*/
|
||||||
|
export const getFileResourceStore = (runtimeConfig: RuntimeConfig): FileResourceStore =>
|
||||||
|
new FileResourceStore(
|
||||||
|
new ExtensionBasedMapper(runtimeConfig),
|
||||||
|
new InteractionController(),
|
||||||
|
new MetadataController(),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives an in memory resource store based on (default) base url.
|
||||||
|
* @param base - Optional base parameter for the run time config.
|
||||||
|
*
|
||||||
|
* @returns The in memory resource store.
|
||||||
|
*/
|
||||||
|
export const getInMemoryResourceStore = (base = BASE): InMemoryResourceStore =>
|
||||||
|
new InMemoryResourceStore(new RuntimeConfig({ base }));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives a converting store given some converters.
|
||||||
|
* @param store - Initial store.
|
||||||
|
* @param converters - Converters to be used.
|
||||||
|
*
|
||||||
|
* @returns The converting store.
|
||||||
|
*/
|
||||||
|
export const getConvertingStore =
|
||||||
|
(store: ResourceStore, converters: RepresentationConverter[]): RepresentationConvertingStore =>
|
||||||
|
new RepresentationConvertingStore(store, new CompositeAsyncHandler(converters));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives a patching store based on initial store.
|
||||||
|
* @param store - Inital resource store.
|
||||||
|
*
|
||||||
|
* @returns The patching store.
|
||||||
|
*/
|
||||||
|
export const getPatchingStore = (store: ResourceStore): PatchingStore => {
|
||||||
|
const locker = new SingleThreadedResourceLocker();
|
||||||
|
const patcher = new SparqlUpdatePatchHandler(store, locker);
|
||||||
|
return new PatchingStore(store, patcher);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives an operation handler given a store with all the common operation handlers.
|
||||||
|
* @param store - Initial resource store.
|
||||||
|
*
|
||||||
|
* @returns The operation handler.
|
||||||
|
*/
|
||||||
|
export const getOperationHandler = (store: ResourceStore): CompositeAsyncHandler<Operation, ResponseDescription> => {
|
||||||
|
const handlers = [
|
||||||
|
new GetOperationHandler(store),
|
||||||
|
new PostOperationHandler(store),
|
||||||
|
new PutOperationHandler(store),
|
||||||
|
new PatchOperationHandler(store),
|
||||||
|
new DeleteOperationHandler(store),
|
||||||
|
];
|
||||||
|
return new CompositeAsyncHandler<Operation, ResponseDescription>(handlers);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives a basic request parser based on some body parses.
|
||||||
|
* @param bodyParsers - Optional list of body parsers, default is RawBodyParser.
|
||||||
|
*
|
||||||
|
* @returns The request parser.
|
||||||
|
*/
|
||||||
|
export const getBasicRequestParser = (bodyParsers: BodyParser[] = []): BasicRequestParser => {
|
||||||
|
let bodyParser: BodyParser;
|
||||||
|
if (bodyParsers.length === 1) {
|
||||||
|
bodyParser = bodyParsers[0];
|
||||||
|
} else if (bodyParsers.length === 0) {
|
||||||
|
// If no body parser is given (array is empty), default to RawBodyParser
|
||||||
|
bodyParser = new RawBodyParser();
|
||||||
|
} else {
|
||||||
|
bodyParser = new CompositeAsyncHandler<HttpRequest, Representation | undefined>(bodyParsers);
|
||||||
|
}
|
||||||
|
return new BasicRequestParser({
|
||||||
|
targetExtractor: new BasicTargetExtractor(),
|
||||||
|
preferenceParser: new AcceptPreferenceParser(),
|
||||||
|
bodyParser,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gives a web acl authorizer, using a UrlContainerManager & based on a (default) runtimeConfig.
|
||||||
|
* @param store - Initial resource store.
|
||||||
|
* @param base - Base URI of the pod.
|
||||||
|
* @param aclManager - Optional acl manager, default is UrlBasedAclManager.
|
||||||
|
*
|
||||||
|
* @returns The acl authorizer.
|
||||||
|
*/
|
||||||
|
export const getWebAclAuthorizer =
|
||||||
|
(store: ResourceStore, base = BASE, aclManager = new UrlBasedAclManager()): WebAclAuthorizer => {
|
||||||
|
const containerManager = new UrlContainerManager(new RuntimeConfig({ base }));
|
||||||
|
return new WebAclAuthorizer(aclManager, containerManager, store);
|
||||||
|
};
|
86
test/integration/AuthenticatedFileResourceStore.test.ts
Normal file
86
test/integration/AuthenticatedFileResourceStore.test.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { copyFileSync, mkdirSync } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
import * as rimraf from 'rimraf';
|
||||||
|
import { HttpHandler, ResourceStore, RuntimeConfig } from '../../index';
|
||||||
|
import { AuthenticatedFileResourceStoreConfig } from '../configs/AuthenticatedFileResourceStoreConfig';
|
||||||
|
import { getRuntimeConfig } from '../configs/Util';
|
||||||
|
import { AclTestHelper, FileTestHelper } from '../util/TestHelpers';
|
||||||
|
|
||||||
|
describe('A server using a AuthenticatedFileResourceStore', (): void => {
|
||||||
|
let config: AuthenticatedFileResourceStoreConfig;
|
||||||
|
let handler: HttpHandler;
|
||||||
|
let store: ResourceStore;
|
||||||
|
let aclHelper: AclTestHelper;
|
||||||
|
let fileHelper: FileTestHelper;
|
||||||
|
let runtimeConfig: RuntimeConfig;
|
||||||
|
|
||||||
|
beforeAll(async(): Promise<void> => {
|
||||||
|
runtimeConfig = getRuntimeConfig('AuthenticatedFileResourceStore');
|
||||||
|
config = new AuthenticatedFileResourceStoreConfig(runtimeConfig);
|
||||||
|
const { base, rootFilepath } = runtimeConfig;
|
||||||
|
handler = config.getHttpHandler();
|
||||||
|
({ store } = config);
|
||||||
|
aclHelper = new AclTestHelper(store, base);
|
||||||
|
fileHelper = new FileTestHelper(handler, new URL('http://test.com/'));
|
||||||
|
|
||||||
|
// Make sure the root directory exists
|
||||||
|
mkdirSync(rootFilepath, { recursive: true });
|
||||||
|
copyFileSync(join(__dirname, '../assets/permanent.txt'), `${rootFilepath}/permanent.txt`);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async(): Promise<void> => {
|
||||||
|
rimraf.sync(runtimeConfig.rootFilepath, { glob: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with acl', (): void => {
|
||||||
|
it('can add a file to the store, read it and delete it if allowed.', async(): Promise<
|
||||||
|
void
|
||||||
|
> => {
|
||||||
|
// Set acl
|
||||||
|
await aclHelper.setSimpleAcl({ read: true, write: true, append: true }, 'agent');
|
||||||
|
|
||||||
|
// Create file
|
||||||
|
let response = await fileHelper.createFile('../assets/testfile2.txt', 'testfile2.txt');
|
||||||
|
const id = response._getHeaders().location;
|
||||||
|
|
||||||
|
// Get file
|
||||||
|
response = await fileHelper.getFile(id);
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response._getHeaders().location).toBe(id);
|
||||||
|
expect(response._getBuffer().toString()).toContain('TESTFILE2');
|
||||||
|
|
||||||
|
// DELETE file
|
||||||
|
await fileHelper.deleteFile(id);
|
||||||
|
await fileHelper.shouldNotExist(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can not add a file to the store if not allowed.', async():
|
||||||
|
Promise<void> => {
|
||||||
|
// Set acl
|
||||||
|
await aclHelper.setSimpleAcl({ read: true, write: true, append: true }, 'authenticated');
|
||||||
|
|
||||||
|
// Try to create file
|
||||||
|
const response = await fileHelper.createFile('../assets/testfile2.txt', 'testfile2.txt', true);
|
||||||
|
expect(response.statusCode).toBe(401);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can not add/delete, but only read files if allowed.', async():
|
||||||
|
Promise<void> => {
|
||||||
|
// Set acl
|
||||||
|
await aclHelper.setSimpleAcl({ read: true, write: false, append: false }, 'agent');
|
||||||
|
|
||||||
|
// Try to create file
|
||||||
|
let response = await fileHelper.createFile('../assets/testfile2.txt', 'testfile2.txt', true);
|
||||||
|
expect(response.statusCode).toBe(401);
|
||||||
|
|
||||||
|
// GET permanent file
|
||||||
|
response = await fileHelper.getFile('http://test.com/permanent.txt');
|
||||||
|
expect(response._getHeaders().location).toBe('http://test.com/permanent.txt');
|
||||||
|
expect(response._getBuffer().toString()).toContain('TEST');
|
||||||
|
|
||||||
|
// Try to delete permanent file
|
||||||
|
response = await fileHelper.deleteFile('http://test.com/permanent.txt', true);
|
||||||
|
expect(response.statusCode).toBe(401);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -2,66 +2,13 @@ import * as url from 'url';
|
|||||||
import { namedNode, quad } from '@rdfjs/data-model';
|
import { namedNode, quad } from '@rdfjs/data-model';
|
||||||
import { Parser } from 'n3';
|
import { Parser } from 'n3';
|
||||||
import { MockResponse } from 'node-mocks-http';
|
import { MockResponse } from 'node-mocks-http';
|
||||||
import { UnsecureWebIdExtractor } from '../../src/authentication/UnsecureWebIdExtractor';
|
import { BasicConfig } from '../configs/BasicConfig';
|
||||||
import { AllowEverythingAuthorizer } from '../../src/authorization/AllowEverythingAuthorizer';
|
import { BasicHandlersConfig } from '../configs/BasicHandlersConfig';
|
||||||
import { RuntimeConfig } from '../../src/init/RuntimeConfig';
|
|
||||||
import { AuthenticatedLdpHandler } from '../../src/ldp/AuthenticatedLdpHandler';
|
|
||||||
import { AcceptPreferenceParser } from '../../src/ldp/http/AcceptPreferenceParser';
|
|
||||||
import { BasicRequestParser } from '../../src/ldp/http/BasicRequestParser';
|
|
||||||
import { BasicResponseWriter } from '../../src/ldp/http/BasicResponseWriter';
|
|
||||||
import { BasicTargetExtractor } from '../../src/ldp/http/BasicTargetExtractor';
|
|
||||||
import { BodyParser } from '../../src/ldp/http/BodyParser';
|
|
||||||
import { RawBodyParser } from '../../src/ldp/http/RawBodyParser';
|
|
||||||
import { SparqlUpdateBodyParser } from '../../src/ldp/http/SparqlUpdateBodyParser';
|
|
||||||
import { DeleteOperationHandler } from '../../src/ldp/operations/DeleteOperationHandler';
|
|
||||||
import { GetOperationHandler } from '../../src/ldp/operations/GetOperationHandler';
|
|
||||||
import { Operation } from '../../src/ldp/operations/Operation';
|
|
||||||
import { PatchOperationHandler } from '../../src/ldp/operations/PatchOperationHandler';
|
|
||||||
import { PostOperationHandler } from '../../src/ldp/operations/PostOperationHandler';
|
|
||||||
import { ResponseDescription } from '../../src/ldp/operations/ResponseDescription';
|
|
||||||
import { MethodPermissionsExtractor } from '../../src/ldp/permissions/MethodPermissionsExtractor';
|
|
||||||
import { SparqlPatchPermissionsExtractor } from '../../src/ldp/permissions/SparqlPatchPermissionsExtractor';
|
|
||||||
import { Representation } from '../../src/ldp/representation/Representation';
|
|
||||||
import { HttpRequest } from '../../src/server/HttpRequest';
|
|
||||||
import { QuadToTurtleConverter } from '../../src/storage/conversion/QuadToTurtleConverter';
|
|
||||||
import { TurtleToQuadConverter } from '../../src/storage/conversion/TurtleToQuadConverter';
|
|
||||||
import { InMemoryResourceStore } from '../../src/storage/InMemoryResourceStore';
|
|
||||||
import { SparqlUpdatePatchHandler } from '../../src/storage/patch/SparqlUpdatePatchHandler';
|
|
||||||
import { PatchingStore } from '../../src/storage/PatchingStore';
|
|
||||||
import { RepresentationConvertingStore } from '../../src/storage/RepresentationConvertingStore';
|
|
||||||
import { SingleThreadedResourceLocker } from '../../src/storage/SingleThreadedResourceLocker';
|
|
||||||
import { CompositeAsyncHandler } from '../../src/util/CompositeAsyncHandler';
|
|
||||||
import { call } from '../util/Util';
|
import { call } from '../util/Util';
|
||||||
|
|
||||||
describe('An integrated AuthenticatedLdpHandler', (): void => {
|
describe('An integrated AuthenticatedLdpHandler', (): void => {
|
||||||
describe('with simple handlers', (): void => {
|
describe('with simple handlers', (): void => {
|
||||||
const requestParser = new BasicRequestParser({
|
const handler = new BasicConfig().getHttpHandler();
|
||||||
targetExtractor: new BasicTargetExtractor(),
|
|
||||||
preferenceParser: new AcceptPreferenceParser(),
|
|
||||||
bodyParser: new RawBodyParser(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const credentialsExtractor = new UnsecureWebIdExtractor();
|
|
||||||
const permissionsExtractor = new MethodPermissionsExtractor();
|
|
||||||
const authorizer = new AllowEverythingAuthorizer();
|
|
||||||
|
|
||||||
const store = new InMemoryResourceStore(new RuntimeConfig({ base: 'http://test.com/' }));
|
|
||||||
const operationHandler = new CompositeAsyncHandler<Operation, ResponseDescription>([
|
|
||||||
new GetOperationHandler(store),
|
|
||||||
new PostOperationHandler(store),
|
|
||||||
new DeleteOperationHandler(store),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const responseWriter = new BasicResponseWriter();
|
|
||||||
|
|
||||||
const handler = new AuthenticatedLdpHandler({
|
|
||||||
requestParser,
|
|
||||||
credentialsExtractor,
|
|
||||||
permissionsExtractor,
|
|
||||||
authorizer,
|
|
||||||
operationHandler,
|
|
||||||
responseWriter,
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can add, read and delete data based on incoming requests.', async(): Promise<void> => {
|
it('can add, read and delete data based on incoming requests.', async(): Promise<void> => {
|
||||||
// POST
|
// POST
|
||||||
@ -80,9 +27,17 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
|
|||||||
|
|
||||||
// GET
|
// GET
|
||||||
requestUrl = new URL(id);
|
requestUrl = new URL(id);
|
||||||
response = await call(handler, requestUrl, 'GET', { accept: 'text/turtle' }, []);
|
response = await call(
|
||||||
|
handler,
|
||||||
|
requestUrl,
|
||||||
|
'GET',
|
||||||
|
{ accept: 'text/turtle' },
|
||||||
|
[],
|
||||||
|
);
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response._getData()).toContain('<http://test.com/s> <http://test.com/p> <http://test.com/o>.');
|
expect(response._getData()).toContain(
|
||||||
|
'<http://test.com/s> <http://test.com/p> <http://test.com/o>.',
|
||||||
|
);
|
||||||
expect(response._getHeaders().location).toBe(id);
|
expect(response._getHeaders().location).toBe(id);
|
||||||
|
|
||||||
// DELETE
|
// DELETE
|
||||||
@ -92,57 +47,20 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
|
|||||||
expect(response._getHeaders().location).toBe(url.format(requestUrl));
|
expect(response._getHeaders().location).toBe(url.format(requestUrl));
|
||||||
|
|
||||||
// GET
|
// GET
|
||||||
response = await call(handler, requestUrl, 'GET', { accept: 'text/turtle' }, []);
|
response = await call(
|
||||||
|
handler,
|
||||||
|
requestUrl,
|
||||||
|
'GET',
|
||||||
|
{ accept: 'text/turtle' },
|
||||||
|
[],
|
||||||
|
);
|
||||||
expect(response.statusCode).toBe(404);
|
expect(response.statusCode).toBe(404);
|
||||||
expect(response._getData()).toContain('NotFoundHttpError');
|
expect(response._getData()).toContain('NotFoundHttpError');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with simple PATCH handlers', (): void => {
|
describe('with simple PATCH handlers', (): void => {
|
||||||
const bodyParser: BodyParser = new CompositeAsyncHandler<HttpRequest, Representation | undefined>([
|
const handler = new BasicHandlersConfig().getHttpHandler();
|
||||||
new SparqlUpdateBodyParser(),
|
|
||||||
new RawBodyParser(),
|
|
||||||
]);
|
|
||||||
const requestParser = new BasicRequestParser({
|
|
||||||
targetExtractor: new BasicTargetExtractor(),
|
|
||||||
preferenceParser: new AcceptPreferenceParser(),
|
|
||||||
bodyParser,
|
|
||||||
});
|
|
||||||
|
|
||||||
const credentialsExtractor = new UnsecureWebIdExtractor();
|
|
||||||
const permissionsExtractor = new CompositeAsyncHandler([
|
|
||||||
new MethodPermissionsExtractor(),
|
|
||||||
new SparqlPatchPermissionsExtractor(),
|
|
||||||
]);
|
|
||||||
const authorizer = new AllowEverythingAuthorizer();
|
|
||||||
|
|
||||||
const store = new InMemoryResourceStore(new RuntimeConfig({ base: 'http://test.com/' }));
|
|
||||||
const converter = new CompositeAsyncHandler([
|
|
||||||
new QuadToTurtleConverter(),
|
|
||||||
new TurtleToQuadConverter(),
|
|
||||||
]);
|
|
||||||
const convertingStore = new RepresentationConvertingStore(store, converter);
|
|
||||||
const locker = new SingleThreadedResourceLocker();
|
|
||||||
const patcher = new SparqlUpdatePatchHandler(convertingStore, locker);
|
|
||||||
const patchingStore = new PatchingStore(convertingStore, patcher);
|
|
||||||
|
|
||||||
const operationHandler = new CompositeAsyncHandler<Operation, ResponseDescription>([
|
|
||||||
new GetOperationHandler(patchingStore),
|
|
||||||
new PostOperationHandler(patchingStore),
|
|
||||||
new DeleteOperationHandler(patchingStore),
|
|
||||||
new PatchOperationHandler(patchingStore),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const responseWriter = new BasicResponseWriter();
|
|
||||||
|
|
||||||
const handler = new AuthenticatedLdpHandler({
|
|
||||||
requestParser,
|
|
||||||
credentialsExtractor,
|
|
||||||
permissionsExtractor,
|
|
||||||
authorizer,
|
|
||||||
operationHandler,
|
|
||||||
responseWriter,
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can handle simple SPARQL updates.', async(): Promise<void> => {
|
it('can handle simple SPARQL updates.', async(): Promise<void> => {
|
||||||
// POST
|
// POST
|
||||||
@ -169,7 +87,8 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
|
|||||||
{ 'content-type': 'application/sparql-update', 'transfer-encoding': 'chunked' },
|
{ 'content-type': 'application/sparql-update', 'transfer-encoding': 'chunked' },
|
||||||
[ 'DELETE { <http://test.com/s1> <http://test.com/p1> <http://test.com/o1> }',
|
[ 'DELETE { <http://test.com/s1> <http://test.com/p1> <http://test.com/o1> }',
|
||||||
'INSERT {<http://test.com/s3> <http://test.com/p3> <http://test.com/o3>}',
|
'INSERT {<http://test.com/s3> <http://test.com/p3> <http://test.com/o3>}',
|
||||||
'WHERE {}' ],
|
'WHERE {}',
|
||||||
|
],
|
||||||
);
|
);
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response._getData()).toHaveLength(0);
|
expect(response._getData()).toHaveLength(0);
|
||||||
@ -177,18 +96,89 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
|
|||||||
|
|
||||||
// GET
|
// GET
|
||||||
requestUrl = new URL(id);
|
requestUrl = new URL(id);
|
||||||
response = await call(handler, requestUrl, 'GET', { accept: 'text/turtle' }, []);
|
response = await call(
|
||||||
|
handler,
|
||||||
|
requestUrl,
|
||||||
|
'GET',
|
||||||
|
{ accept: 'text/turtle' },
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response._getBuffer().toString()).toContain(
|
||||||
|
'<http://test.com/s2> <http://test.com/p2> <http://test.com/o2>.',
|
||||||
|
);
|
||||||
|
expect(response._getHeaders().location).toBe(id);
|
||||||
|
const parser = new Parser();
|
||||||
|
const triples = parser.parse(response._getBuffer().toString());
|
||||||
|
expect(triples).toBeRdfIsomorphic([
|
||||||
|
quad(
|
||||||
|
namedNode('http://test.com/s2'),
|
||||||
|
namedNode('http://test.com/p2'),
|
||||||
|
namedNode('http://test.com/o2'),
|
||||||
|
),
|
||||||
|
quad(
|
||||||
|
namedNode('http://test.com/s3'),
|
||||||
|
namedNode('http://test.com/p3'),
|
||||||
|
namedNode('http://test.com/o3'),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('with simple PUT handlers', (): void => {
|
||||||
|
const handler = new BasicHandlersConfig().getHttpHandler();
|
||||||
|
|
||||||
|
it('should overwrite the content on PUT request.', async(): Promise<void> => {
|
||||||
|
// POST
|
||||||
|
let requestUrl = new URL('http://test.com/');
|
||||||
|
let response: MockResponse<any> = await call(
|
||||||
|
handler,
|
||||||
|
requestUrl,
|
||||||
|
'POST',
|
||||||
|
{ 'content-type': 'text/turtle', 'transfer-encoding': 'chunked' },
|
||||||
|
[
|
||||||
|
'<http://test.com/s1> <http://test.com/p1> <http://test.com/o1>.',
|
||||||
|
'<http://test.com/s2> <http://test.com/p2> <http://test.com/o2>.',
|
||||||
|
],
|
||||||
|
);
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response._getData()).toHaveLength(0);
|
||||||
|
const id = response._getHeaders().location;
|
||||||
|
expect(id).toContain(url.format(requestUrl));
|
||||||
|
|
||||||
|
// PUT
|
||||||
|
requestUrl = new URL(id);
|
||||||
|
response = await call(
|
||||||
|
handler,
|
||||||
|
requestUrl,
|
||||||
|
'PUT',
|
||||||
|
{ 'content-type': 'text/turtle', 'transfer-encoding': 'chunked' },
|
||||||
|
[ '<http://test.com/s3> <http://test.com/p3> <http://test.com/o3>.' ],
|
||||||
|
);
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response._getData()).toHaveLength(0);
|
||||||
|
expect(response._getHeaders().location).toBe(id);
|
||||||
|
|
||||||
|
// GET
|
||||||
|
requestUrl = new URL(id);
|
||||||
|
response = await call(
|
||||||
|
handler,
|
||||||
|
requestUrl,
|
||||||
|
'GET',
|
||||||
|
{ accept: 'text/turtle' },
|
||||||
|
[],
|
||||||
|
);
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response._getData()).toContain('<http://test.com/s2> <http://test.com/p2> <http://test.com/o2>.');
|
|
||||||
expect(response._getHeaders().location).toBe(id);
|
expect(response._getHeaders().location).toBe(id);
|
||||||
const parser = new Parser();
|
const parser = new Parser();
|
||||||
const triples = parser.parse(response._getData());
|
const triples = parser.parse(response._getData());
|
||||||
expect(triples).toBeRdfIsomorphic(
|
expect(triples).toBeRdfIsomorphic([
|
||||||
[
|
quad(
|
||||||
quad(namedNode('http://test.com/s2'), namedNode('http://test.com/p2'), namedNode('http://test.com/o2')),
|
namedNode('http://test.com/s3'),
|
||||||
quad(namedNode('http://test.com/s3'), namedNode('http://test.com/p3'), namedNode('http://test.com/o3')),
|
namedNode('http://test.com/p3'),
|
||||||
],
|
namedNode('http://test.com/o3'),
|
||||||
);
|
),
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,127 +1,16 @@
|
|||||||
import { MockResponse } from 'node-mocks-http';
|
import { MockResponse } from 'node-mocks-http';
|
||||||
import streamifyArray from 'streamify-array';
|
import { BasicHandlersWithAclConfig } from '../configs/BasicHandlersWithAclConfig';
|
||||||
import { UnsecureWebIdExtractor } from '../../src/authentication/UnsecureWebIdExtractor';
|
import { AclTestHelper } from '../util/TestHelpers';
|
||||||
import { UrlBasedAclManager } from '../../src/authorization/UrlBasedAclManager';
|
|
||||||
import { WebAclAuthorizer } from '../../src/authorization/WebAclAuthorizer';
|
|
||||||
import { RuntimeConfig } from '../../src/init/RuntimeConfig';
|
|
||||||
import { AuthenticatedLdpHandler } from '../../src/ldp/AuthenticatedLdpHandler';
|
|
||||||
import { AcceptPreferenceParser } from '../../src/ldp/http/AcceptPreferenceParser';
|
|
||||||
import { BasicRequestParser } from '../../src/ldp/http/BasicRequestParser';
|
|
||||||
import { BasicResponseWriter } from '../../src/ldp/http/BasicResponseWriter';
|
|
||||||
import { BasicTargetExtractor } from '../../src/ldp/http/BasicTargetExtractor';
|
|
||||||
import { BodyParser } from '../../src/ldp/http/BodyParser';
|
|
||||||
import { RawBodyParser } from '../../src/ldp/http/RawBodyParser';
|
|
||||||
import { DeleteOperationHandler } from '../../src/ldp/operations/DeleteOperationHandler';
|
|
||||||
import { GetOperationHandler } from '../../src/ldp/operations/GetOperationHandler';
|
|
||||||
import { Operation } from '../../src/ldp/operations/Operation';
|
|
||||||
import { PostOperationHandler } from '../../src/ldp/operations/PostOperationHandler';
|
|
||||||
import { PutOperationHandler } from '../../src/ldp/operations/PutOperationHandler';
|
|
||||||
import { ResponseDescription } from '../../src/ldp/operations/ResponseDescription';
|
|
||||||
import { MethodPermissionsExtractor } from '../../src/ldp/permissions/MethodPermissionsExtractor';
|
|
||||||
import { PermissionSet } from '../../src/ldp/permissions/PermissionSet';
|
|
||||||
import { QuadToTurtleConverter } from '../../src/storage/conversion/QuadToTurtleConverter';
|
|
||||||
import { TurtleToQuadConverter } from '../../src/storage/conversion/TurtleToQuadConverter';
|
|
||||||
import { InMemoryResourceStore } from '../../src/storage/InMemoryResourceStore';
|
|
||||||
import { RepresentationConvertingStore } from '../../src/storage/RepresentationConvertingStore';
|
|
||||||
import { ResourceStore } from '../../src/storage/ResourceStore';
|
|
||||||
import { UrlContainerManager } from '../../src/storage/UrlContainerManager';
|
|
||||||
import { CompositeAsyncHandler } from '../../src/util/CompositeAsyncHandler';
|
|
||||||
import { call } from '../util/Util';
|
import { call } from '../util/Util';
|
||||||
|
|
||||||
const setAcl = async(store: ResourceStore, id: string, permissions: PermissionSet, control: boolean,
|
|
||||||
access: boolean, def: boolean, agent?: string, agentClass?: 'agent' | 'authenticated'): Promise<void> => {
|
|
||||||
const acl: string[] = [
|
|
||||||
'@prefix acl: <http://www.w3.org/ns/auth/acl#>.\n',
|
|
||||||
'@prefix foaf: <http://xmlns.com/foaf/0.1/>.\n',
|
|
||||||
'<http://test.com/#auth> a acl:Authorization',
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const perm of [ 'Read', 'Append', 'Write', 'Delete' ]) {
|
|
||||||
if (permissions[perm.toLowerCase() as keyof PermissionSet]) {
|
|
||||||
acl.push(`;\n acl:mode acl:${perm}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (control) {
|
|
||||||
acl.push(';\n acl:mode acl:Control');
|
|
||||||
}
|
|
||||||
if (access) {
|
|
||||||
acl.push(`;\n acl:accessTo <${id}>`);
|
|
||||||
}
|
|
||||||
if (def) {
|
|
||||||
acl.push(`;\n acl:default <${id}>`);
|
|
||||||
}
|
|
||||||
if (agent) {
|
|
||||||
acl.push(`;\n acl:agent <${agent}>`);
|
|
||||||
}
|
|
||||||
if (agentClass) {
|
|
||||||
acl.push(`;\n acl:agentClass ${agentClass === 'agent' ? 'foaf:Agent' : 'foaf:AuthenticatedAgent'}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
acl.push('.');
|
|
||||||
|
|
||||||
const representation = {
|
|
||||||
binary: true,
|
|
||||||
data: streamifyArray(acl),
|
|
||||||
metadata: {
|
|
||||||
raw: [],
|
|
||||||
profiles: [],
|
|
||||||
contentType: 'text/turtle',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return store.setRepresentation({ path: `${id}.acl` }, representation);
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('A server with authorization', (): void => {
|
describe('A server with authorization', (): void => {
|
||||||
const bodyParser: BodyParser = new RawBodyParser();
|
const config = new BasicHandlersWithAclConfig();
|
||||||
const requestParser = new BasicRequestParser({
|
const handler = config.getHttpHandler();
|
||||||
targetExtractor: new BasicTargetExtractor(),
|
const { store } = config;
|
||||||
preferenceParser: new AcceptPreferenceParser(),
|
const aclHelper = new AclTestHelper(store, 'http://test.com/');
|
||||||
bodyParser,
|
|
||||||
});
|
|
||||||
|
|
||||||
const store = new InMemoryResourceStore(new RuntimeConfig({ base: 'http://test.com/' }));
|
|
||||||
const converter = new CompositeAsyncHandler([
|
|
||||||
new QuadToTurtleConverter(),
|
|
||||||
new TurtleToQuadConverter(),
|
|
||||||
]);
|
|
||||||
const convertingStore = new RepresentationConvertingStore(store, converter);
|
|
||||||
|
|
||||||
const credentialsExtractor = new UnsecureWebIdExtractor();
|
|
||||||
const permissionsExtractor = new MethodPermissionsExtractor();
|
|
||||||
const authorizer = new WebAclAuthorizer(
|
|
||||||
new UrlBasedAclManager(),
|
|
||||||
new UrlContainerManager(new RuntimeConfig({ base: 'http://test.com/' })),
|
|
||||||
convertingStore,
|
|
||||||
);
|
|
||||||
|
|
||||||
const operationHandler = new CompositeAsyncHandler<Operation, ResponseDescription>([
|
|
||||||
new GetOperationHandler(convertingStore),
|
|
||||||
new PostOperationHandler(convertingStore),
|
|
||||||
new DeleteOperationHandler(convertingStore),
|
|
||||||
new PutOperationHandler(convertingStore),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const responseWriter = new BasicResponseWriter();
|
|
||||||
|
|
||||||
const handler = new AuthenticatedLdpHandler({
|
|
||||||
requestParser,
|
|
||||||
credentialsExtractor,
|
|
||||||
permissionsExtractor,
|
|
||||||
authorizer,
|
|
||||||
operationHandler,
|
|
||||||
responseWriter,
|
|
||||||
});
|
|
||||||
|
|
||||||
it('can create new entries.', async(): Promise<void> => {
|
it('can create new entries.', async(): Promise<void> => {
|
||||||
await setAcl(convertingStore,
|
await aclHelper.setSimpleAcl({ read: true, write: true, append: true }, 'agent');
|
||||||
'http://test.com/',
|
|
||||||
{ read: true, write: true, append: true },
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
undefined,
|
|
||||||
'agent');
|
|
||||||
|
|
||||||
// POST
|
// POST
|
||||||
let requestUrl = new URL('http://test.com/');
|
let requestUrl = new URL('http://test.com/');
|
||||||
@ -147,14 +36,7 @@ describe('A server with authorization', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('can not create new entries if not allowed.', async(): Promise<void> => {
|
it('can not create new entries if not allowed.', async(): Promise<void> => {
|
||||||
await setAcl(convertingStore,
|
await aclHelper.setSimpleAcl({ read: true, write: true, append: true }, 'authenticated');
|
||||||
'http://test.com/',
|
|
||||||
{ read: true, write: true, append: true },
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
undefined,
|
|
||||||
'authenticated');
|
|
||||||
|
|
||||||
// POST
|
// POST
|
||||||
let requestUrl = new URL('http://test.com/');
|
let requestUrl = new URL('http://test.com/');
|
||||||
|
186
test/integration/FileResourceStore.test.ts
Normal file
186
test/integration/FileResourceStore.test.ts
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
import * as rimraf from 'rimraf';
|
||||||
|
import { RuntimeConfig } from '../../src/init/RuntimeConfig';
|
||||||
|
import { HttpHandler } from '../../src/server/HttpHandler';
|
||||||
|
import { FileResourceStoreConfig } from '../configs/FileResourceStoreConfig';
|
||||||
|
import { getRuntimeConfig } from '../configs/Util';
|
||||||
|
import { FileTestHelper } from '../util/TestHelpers';
|
||||||
|
|
||||||
|
describe('A server using a FileResourceStore', (): void => {
|
||||||
|
describe('without acl', (): void => {
|
||||||
|
let config: FileResourceStoreConfig;
|
||||||
|
let handler: HttpHandler;
|
||||||
|
let fileHelper: FileTestHelper;
|
||||||
|
let runtimeConfig: RuntimeConfig;
|
||||||
|
|
||||||
|
beforeAll(async(): Promise<void> => {
|
||||||
|
runtimeConfig = getRuntimeConfig('FileResourceStore');
|
||||||
|
config = new FileResourceStoreConfig(runtimeConfig);
|
||||||
|
handler = config.getHttpHandler();
|
||||||
|
fileHelper = new FileTestHelper(handler, new URL(runtimeConfig.base));
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async(): Promise<void> => {
|
||||||
|
rimraf.sync(runtimeConfig.rootFilepath, { glob: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can add a file to the store, read it and delete it.', async():
|
||||||
|
Promise<void> => {
|
||||||
|
// POST
|
||||||
|
let response = await fileHelper.createFile('../assets/testfile0.txt', 'testfile0.txt');
|
||||||
|
const id = response._getHeaders().location;
|
||||||
|
|
||||||
|
// GET
|
||||||
|
response = await fileHelper.getFile(id);
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response._getHeaders().location).toBe(id);
|
||||||
|
expect(response._getBuffer().toString()).toContain('TESTFILE0');
|
||||||
|
|
||||||
|
// DELETE
|
||||||
|
await fileHelper.deleteFile(id);
|
||||||
|
await fileHelper.shouldNotExist(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can add and overwrite a file.', async(): Promise<void> => {
|
||||||
|
let response = await fileHelper.createFile('../assets/testfile0.txt', 'file.txt');
|
||||||
|
const id = response._getHeaders().location;
|
||||||
|
|
||||||
|
// GET
|
||||||
|
response = await fileHelper.getFile(id);
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response._getHeaders().location).toBe(id);
|
||||||
|
expect(response._getBuffer().toString()).toContain('TESTFILE0');
|
||||||
|
|
||||||
|
// PUT
|
||||||
|
response = await fileHelper.overwriteFile('../assets/testfile1.txt', id);
|
||||||
|
|
||||||
|
// GET
|
||||||
|
response = await fileHelper.getFile(id);
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response._getHeaders().location).toBe(id);
|
||||||
|
expect(response._getBuffer().toString()).toContain('TESTFILE1');
|
||||||
|
|
||||||
|
// DELETE
|
||||||
|
await fileHelper.deleteFile(id);
|
||||||
|
await fileHelper.shouldNotExist(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can create a folder and delete it.', async(): Promise<void> => {
|
||||||
|
// POST
|
||||||
|
let response = await fileHelper.createFolder('secondfolder/');
|
||||||
|
const id = response._getHeaders().location;
|
||||||
|
|
||||||
|
// GET
|
||||||
|
response = await fileHelper.getFolder(id);
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response._getHeaders().location).toBe(id);
|
||||||
|
|
||||||
|
// DELETE
|
||||||
|
await fileHelper.deleteFolder(id);
|
||||||
|
await fileHelper.shouldNotExist(id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can make a folder and put a file in it.', async(): Promise<void> => {
|
||||||
|
// Create folder
|
||||||
|
await fileHelper.createFolder('testfolder0/');
|
||||||
|
|
||||||
|
// Create file
|
||||||
|
let response = await fileHelper.createFile('../assets/testfile0.txt', 'testfolder0/testfile0.txt');
|
||||||
|
const id = response._getHeaders().location;
|
||||||
|
|
||||||
|
// GET File
|
||||||
|
response = await fileHelper.getFile(id);
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response._getHeaders().location).toBe(id);
|
||||||
|
|
||||||
|
// DELETE
|
||||||
|
await fileHelper.deleteFile(id);
|
||||||
|
await fileHelper.shouldNotExist(id);
|
||||||
|
await fileHelper.deleteFolder('http://test.com/testfolder0/');
|
||||||
|
await fileHelper.shouldNotExist('http://test.com/testfolder0/');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cannot remove a folder when the folder contains a file.', async(): Promise<void> => {
|
||||||
|
// Create folder
|
||||||
|
let response = await fileHelper.createFolder('testfolder1/');
|
||||||
|
const folderId = response._getHeaders().location;
|
||||||
|
|
||||||
|
// Create file
|
||||||
|
await fileHelper.createFile('../assets/testfile0.txt', 'testfolder1/testfile0.txt');
|
||||||
|
|
||||||
|
// Try DELETE folder
|
||||||
|
response = await fileHelper.simpleCall(new URL(folderId), 'DELETE', {});
|
||||||
|
expect(response.statusCode).toBe(409);
|
||||||
|
expect(response._getData()).toContain('ConflictHttpError: Container is not empty.');
|
||||||
|
|
||||||
|
// DELETE
|
||||||
|
await fileHelper.deleteFile('http://test.com/testfolder1/testfile0.txt');
|
||||||
|
await fileHelper.shouldNotExist('http://test.com/testfolder1/testfile0.txt');
|
||||||
|
await fileHelper.deleteFolder(folderId);
|
||||||
|
await fileHelper.shouldNotExist(folderId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cannot remove a folder when the folder contains a subfolder.', async(): Promise<void> => {
|
||||||
|
// Create folder
|
||||||
|
let response = await fileHelper.createFolder('testfolder2/');
|
||||||
|
const folderId = response._getHeaders().location;
|
||||||
|
|
||||||
|
// Create subfolder
|
||||||
|
response = await fileHelper.createFolder('testfolder2/subfolder0');
|
||||||
|
const subFolderId = response._getHeaders().location;
|
||||||
|
|
||||||
|
// Try DELETE folder
|
||||||
|
response = await fileHelper.simpleCall(new URL(folderId), 'DELETE', {});
|
||||||
|
expect(response.statusCode).toBe(409);
|
||||||
|
expect(response._getData()).toContain('ConflictHttpError: Container is not empty.');
|
||||||
|
|
||||||
|
// DELETE
|
||||||
|
await fileHelper.deleteFolder(subFolderId);
|
||||||
|
await fileHelper.shouldNotExist(subFolderId);
|
||||||
|
await fileHelper.deleteFolder(folderId);
|
||||||
|
await fileHelper.shouldNotExist(folderId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can read the contents of a folder.', async(): Promise<void> => {
|
||||||
|
// Create folder
|
||||||
|
let response = await fileHelper.createFolder('testfolder3/');
|
||||||
|
const folderId = response._getHeaders().location;
|
||||||
|
|
||||||
|
// Create subfolder
|
||||||
|
response = await fileHelper.createFolder('testfolder3/subfolder0');
|
||||||
|
const subFolderId = response._getHeaders().location;
|
||||||
|
|
||||||
|
// Create file
|
||||||
|
response = await fileHelper.createFile('../assets/testfile0.txt', 'testfolder3/testfile0.txt');
|
||||||
|
const fileId = response._getHeaders().location;
|
||||||
|
|
||||||
|
response = await fileHelper.getFolder(folderId);
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response._getHeaders().location).toBe(folderId);
|
||||||
|
expect(response._getBuffer().toString()).toContain('<http://www.w3.org/ns/ldp#contains> <http://test.com/testfolder3/subfolder0>.');
|
||||||
|
expect(response._getBuffer().toString()).toContain('<http://www.w3.org/ns/ldp#contains> <http://test.com/testfolder3/testfile0.txt>.');
|
||||||
|
|
||||||
|
// DELETE
|
||||||
|
await fileHelper.deleteFile(fileId);
|
||||||
|
await fileHelper.shouldNotExist(fileId);
|
||||||
|
await fileHelper.deleteFolder(subFolderId);
|
||||||
|
await fileHelper.shouldNotExist(subFolderId);
|
||||||
|
await fileHelper.deleteFolder(folderId);
|
||||||
|
await fileHelper.shouldNotExist(folderId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can upload and delete a image.', async(): Promise<void> => {
|
||||||
|
let response = await fileHelper.createFile('../assets/testimage.png', 'image.png');
|
||||||
|
const fileId = response._getHeaders().location;
|
||||||
|
|
||||||
|
// GET
|
||||||
|
response = await fileHelper.getFile(fileId);
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response._getHeaders().location).toBe(fileId);
|
||||||
|
expect(response._getHeaders()['content-type']).toBe('image/png');
|
||||||
|
|
||||||
|
// DELETE
|
||||||
|
await fileHelper.deleteFile(fileId);
|
||||||
|
await fileHelper.shouldNotExist(fileId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
0
test/testData/.gitkeep
Normal file
0
test/testData/.gitkeep
Normal file
@ -25,12 +25,18 @@ const { join: joinPath } = posix;
|
|||||||
const base = 'http://test.com/';
|
const base = 'http://test.com/';
|
||||||
const rootFilepath = '/Users/default/home/public/';
|
const rootFilepath = '/Users/default/home/public/';
|
||||||
|
|
||||||
fsPromises.rmdir = jest.fn();
|
jest.mock('fs', (): any => ({
|
||||||
fsPromises.lstat = jest.fn();
|
createReadStream: jest.fn(),
|
||||||
fsPromises.readdir = jest.fn();
|
createWriteStream: jest.fn(),
|
||||||
fsPromises.mkdir = jest.fn();
|
promises: {
|
||||||
fsPromises.unlink = jest.fn();
|
rmdir: jest.fn(),
|
||||||
fsPromises.access = jest.fn();
|
lstat: jest.fn(),
|
||||||
|
readdir: jest.fn(),
|
||||||
|
mkdir: jest.fn(),
|
||||||
|
unlink: jest.fn(),
|
||||||
|
access: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
describe('A FileResourceStore', (): void => {
|
describe('A FileResourceStore', (): void => {
|
||||||
let store: FileResourceStore;
|
let store: FileResourceStore;
|
||||||
@ -45,8 +51,6 @@ describe('A FileResourceStore', (): void => {
|
|||||||
namedNode('http://test.com/o'),
|
namedNode('http://test.com/o'),
|
||||||
);
|
);
|
||||||
|
|
||||||
fs.createReadStream = jest.fn();
|
|
||||||
|
|
||||||
beforeEach(async(): Promise<void> => {
|
beforeEach(async(): Promise<void> => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
@ -68,8 +72,6 @@ describe('A FileResourceStore', (): void => {
|
|||||||
mtime: new Date(),
|
mtime: new Date(),
|
||||||
} as jest.Mocked<Stats>;
|
} as jest.Mocked<Stats>;
|
||||||
|
|
||||||
// Mock the fs functions for the createDataFile function.
|
|
||||||
fs.createWriteStream = jest.fn();
|
|
||||||
writeStream = {
|
writeStream = {
|
||||||
on: jest.fn((name: string, func: () => void): any => {
|
on: jest.fn((name: string, func: () => void): any => {
|
||||||
if (name === 'finish') {
|
if (name === 'finish') {
|
||||||
|
211
test/util/TestHelpers.ts
Normal file
211
test/util/TestHelpers.ts
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
import { EventEmitter } from 'events';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import { IncomingHttpHeaders } from 'http';
|
||||||
|
import { join } from 'path';
|
||||||
|
import * as url from 'url';
|
||||||
|
import { createResponse, MockResponse } from 'node-mocks-http';
|
||||||
|
import streamifyArray from 'streamify-array';
|
||||||
|
import { ResourceStore } from '../../index';
|
||||||
|
import { PermissionSet } from '../../src/ldp/permissions/PermissionSet';
|
||||||
|
import { HttpHandler } from '../../src/server/HttpHandler';
|
||||||
|
import { HttpRequest } from '../../src/server/HttpRequest';
|
||||||
|
import { call } from './Util';
|
||||||
|
|
||||||
|
export class AclTestHelper {
|
||||||
|
public readonly store: ResourceStore;
|
||||||
|
public id: string;
|
||||||
|
|
||||||
|
public constructor(store: ResourceStore, id: string) {
|
||||||
|
this.store = store;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async setSimpleAcl(
|
||||||
|
permissions: PermissionSet,
|
||||||
|
agentClass: 'agent' | 'authenticated',
|
||||||
|
): Promise<void> {
|
||||||
|
const acl: string[] = [
|
||||||
|
'@prefix acl: <http://www.w3.org/ns/auth/acl#>.\n',
|
||||||
|
'@prefix foaf: <http://xmlns.com/foaf/0.1/>.\n',
|
||||||
|
'<http://test.com/#auth> a acl:Authorization',
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const perm of [ 'Read', 'Append', 'Write', 'Delete' ]) {
|
||||||
|
if (permissions[perm.toLowerCase() as keyof PermissionSet]) {
|
||||||
|
acl.push(`;\n acl:mode acl:${perm}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acl.push(';\n acl:mode acl:Control');
|
||||||
|
acl.push(`;\n acl:accessTo <${this.id}>`);
|
||||||
|
acl.push(`;\n acl:default <${this.id}>`);
|
||||||
|
acl.push(
|
||||||
|
`;\n acl:agentClass ${
|
||||||
|
agentClass === 'agent' ? 'foaf:Agent' : 'foaf:AuthenticatedAgent'
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
acl.push('.');
|
||||||
|
|
||||||
|
const representation = {
|
||||||
|
binary: true,
|
||||||
|
data: streamifyArray(acl),
|
||||||
|
metadata: {
|
||||||
|
raw: [],
|
||||||
|
profiles: [],
|
||||||
|
contentType: 'text/turtle',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.store.setRepresentation(
|
||||||
|
{ path: `${this.id}.acl` },
|
||||||
|
representation,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FileTestHelper {
|
||||||
|
public readonly handler: HttpHandler;
|
||||||
|
public readonly baseUrl: URL;
|
||||||
|
|
||||||
|
public constructor(handler: HttpHandler, baseUrl: URL) {
|
||||||
|
this.handler = handler;
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async simpleCall(
|
||||||
|
requestUrl: URL,
|
||||||
|
method: string,
|
||||||
|
headers: IncomingHttpHeaders,
|
||||||
|
): Promise<MockResponse<any>> {
|
||||||
|
return call(this.handler, requestUrl, method, headers, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async callWithFile(
|
||||||
|
requestUrl: URL,
|
||||||
|
method: string,
|
||||||
|
headers: IncomingHttpHeaders,
|
||||||
|
data: Buffer,
|
||||||
|
): Promise<MockResponse<any>> {
|
||||||
|
const request = streamifyArray([ data ]) as HttpRequest;
|
||||||
|
|
||||||
|
request.url = requestUrl.pathname;
|
||||||
|
request.method = method;
|
||||||
|
request.headers = headers;
|
||||||
|
request.headers.host = requestUrl.host;
|
||||||
|
const response: MockResponse<any> = createResponse({
|
||||||
|
eventEmitter: EventEmitter,
|
||||||
|
});
|
||||||
|
|
||||||
|
const endPromise = new Promise((resolve): void => {
|
||||||
|
response.on('end', (): void => {
|
||||||
|
expect(response._isEndCalled()).toBeTruthy();
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.handler.handleSafe({ request, response });
|
||||||
|
await endPromise;
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createFile(fileLocation: string, slug: string, mayFail = false): Promise<MockResponse<any>> {
|
||||||
|
const fileData = await fs.readFile(
|
||||||
|
join(__dirname, fileLocation),
|
||||||
|
);
|
||||||
|
|
||||||
|
const response: MockResponse<any> = await this.callWithFile(
|
||||||
|
this.baseUrl,
|
||||||
|
'POST',
|
||||||
|
{ 'content-type': 'application/octet-stream',
|
||||||
|
slug,
|
||||||
|
'transfer-encoding': 'chunked' },
|
||||||
|
fileData,
|
||||||
|
);
|
||||||
|
if (!mayFail) {
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response._getData()).toHaveLength(0);
|
||||||
|
expect(response._getHeaders().location).toContain(url.format(this.baseUrl));
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async overwriteFile(fileLocation: string, requestUrl: string): Promise<MockResponse<any>> {
|
||||||
|
const fileData = await fs.readFile(
|
||||||
|
join(__dirname, fileLocation),
|
||||||
|
);
|
||||||
|
|
||||||
|
const putUrl = new URL(requestUrl);
|
||||||
|
|
||||||
|
const response: MockResponse<any> = await this.callWithFile(
|
||||||
|
putUrl,
|
||||||
|
'PUT',
|
||||||
|
{ 'content-type': 'application/octet-stream', 'transfer-encoding': 'chunked' },
|
||||||
|
fileData,
|
||||||
|
);
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response._getData()).toHaveLength(0);
|
||||||
|
expect(response._getHeaders().location).toContain(url.format(putUrl));
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getFile(requestUrl: string): Promise<MockResponse<any>> {
|
||||||
|
const getUrl = new URL(requestUrl);
|
||||||
|
|
||||||
|
return this.simpleCall(getUrl, 'GET', { accept: '*/*' });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteFile(requestUrl: string, mayFail = false): Promise<MockResponse<any>> {
|
||||||
|
const deleteUrl = new URL(requestUrl);
|
||||||
|
|
||||||
|
const response = await this.simpleCall(deleteUrl, 'DELETE', {});
|
||||||
|
if (!mayFail) {
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response._getData()).toHaveLength(0);
|
||||||
|
expect(response._getHeaders().location).toBe(url.format(requestUrl));
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createFolder(slug: string): Promise<MockResponse<any>> {
|
||||||
|
const response: MockResponse<any> = await this.simpleCall(
|
||||||
|
this.baseUrl,
|
||||||
|
'POST',
|
||||||
|
{
|
||||||
|
slug,
|
||||||
|
link: '<http://www.w3.org/ns/ldp#Container>; rel="type"',
|
||||||
|
'content-type': 'text/plain',
|
||||||
|
'transfer-encoding': 'chunked',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response._getData()).toHaveLength(0);
|
||||||
|
expect(response._getHeaders().location).toContain(url.format(this.baseUrl));
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getFolder(requestUrl: string): Promise<MockResponse<any>> {
|
||||||
|
const getUrl = new URL(requestUrl);
|
||||||
|
|
||||||
|
return await this.simpleCall(getUrl, 'GET', { accept: 'text/turtle' });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteFolder(requestUrl: string): Promise<MockResponse<any>> {
|
||||||
|
const deleteUrl = new URL(requestUrl);
|
||||||
|
|
||||||
|
const response = await this.simpleCall(deleteUrl, 'DELETE', {});
|
||||||
|
expect(response.statusCode).toBe(200);
|
||||||
|
expect(response._getData()).toHaveLength(0);
|
||||||
|
expect(response._getHeaders().location).toBe(url.format(requestUrl));
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async shouldNotExist(requestUrl: string): Promise<MockResponse<any>> {
|
||||||
|
const getUrl = new URL(requestUrl);
|
||||||
|
|
||||||
|
const response = await this.simpleCall(getUrl, 'GET', { accept: '*/*' });
|
||||||
|
expect(response.statusCode).toBe(404);
|
||||||
|
expect(response._getData()).toContain('NotFoundHttpError');
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
@ -5,14 +5,21 @@ import streamifyArray from 'streamify-array';
|
|||||||
import { HttpHandler } from '../../src/server/HttpHandler';
|
import { HttpHandler } from '../../src/server/HttpHandler';
|
||||||
import { HttpRequest } from '../../src/server/HttpRequest';
|
import { HttpRequest } from '../../src/server/HttpRequest';
|
||||||
|
|
||||||
export const call = async(handler: HttpHandler, requestUrl: URL, method: string,
|
export const call = async(
|
||||||
headers: IncomingHttpHeaders, data: string[]): Promise<MockResponse<any>> => {
|
handler: HttpHandler,
|
||||||
|
requestUrl: URL,
|
||||||
|
method: string,
|
||||||
|
headers: IncomingHttpHeaders,
|
||||||
|
data: string[],
|
||||||
|
): Promise<MockResponse<any>> => {
|
||||||
const request = streamifyArray(data) as HttpRequest;
|
const request = streamifyArray(data) as HttpRequest;
|
||||||
request.url = requestUrl.pathname;
|
request.url = requestUrl.pathname;
|
||||||
request.method = method;
|
request.method = method;
|
||||||
request.headers = headers;
|
request.headers = headers;
|
||||||
request.headers.host = requestUrl.host;
|
request.headers.host = requestUrl.host;
|
||||||
const response: MockResponse<any> = createResponse({ eventEmitter: EventEmitter });
|
const response: MockResponse<any> = createResponse({
|
||||||
|
eventEmitter: EventEmitter,
|
||||||
|
});
|
||||||
|
|
||||||
const endPromise = new Promise((resolve): void => {
|
const endPromise = new Promise((resolve): void => {
|
||||||
response.on('end', (): void => {
|
response.on('end', (): void => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user