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:
Joachim Van Herwegen 2020-09-14 16:06:27 +02:00 committed by GitHub
parent 383da24601
commit b1991cb08a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1122 additions and 256 deletions

View File

@ -46,6 +46,7 @@ module.exports = {
}
}],
'unused-imports/no-unused-imports-ts': 'error',
'import/no-extraneous-dependencies': 'error'
'import/no-extraneous-dependencies': 'error',
'unicorn/import-index': 'off'
},
};

View File

@ -79,6 +79,7 @@ export * from './src/storage/patch/SparqlUpdatePatchHandler';
export * from './src/storage/AtomicResourceStore';
export * from './src/storage/Conditions';
export * from './src/storage/ContainerManager';
export * from './src/storage/FileResourceStore';
export * from './src/storage/InMemoryResourceStore';
export * from './src/storage/Lock';
export * from './src/storage/LockingResourceStore';
@ -103,4 +104,6 @@ export * from './src/util/errors/UnsupportedMediaTypeHttpError';
export * from './src/util/AcceptParser';
export * from './src/util/AsyncHandler';
export * from './src/util/CompositeAsyncHandler';
export * from './src/util/InteractionController';
export * from './src/util/MetadataController';
export * from './src/util/Util';

View File

@ -16,7 +16,8 @@ module.exports = {
"setupFilesAfterEnv": ["jest-rdf"],
"collectCoverage": true,
"coveragePathIgnorePatterns": [
"/node_modules/"
"/node_modules/",
"/src/init/CliRunner.ts"
],
"coverageThreshold": {
"./src": {

43
package-lock.json generated
View File

@ -1324,6 +1324,16 @@
"@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": {
"version": "4.1.3",
"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",
"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": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz",
@ -1471,6 +1487,16 @@
"@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": {
"version": "1.13.5",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.5.tgz",
@ -4349,6 +4375,17 @@
"flatted": "^2.0.0",
"rimraf": "2.6.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": {
@ -8780,9 +8817,9 @@
"dev": true
},
"rimraf": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"dev": true,
"requires": {
"glob": "^7.1.3"

View File

@ -91,6 +91,7 @@
},
"devDependencies": {
"@types/jest": "^26.0.0",
"@types/rimraf": "^3.0.0",
"@types/supertest": "^2.0.10",
"@typescript-eslint/eslint-plugin": "^2.33.0",
"@typescript-eslint/parser": "^2.33.0",
@ -108,6 +109,7 @@
"manual-git-changelog": "^1.0.1",
"node-mocks-http": "^1.8.1",
"nodemon": "^2.0.4",
"rimraf": "^3.0.2",
"stream-to-string": "^1.1.0",
"supertest": "^4.0.2",
"ts-jest": "^26.0.0",

View File

@ -0,0 +1 @@
TEST

View File

@ -0,0 +1 @@
TESTFILE0

View File

@ -0,0 +1 @@
TESTFILE1

View File

@ -0,0 +1 @@
TESTFILE2

BIN
test/assets/testimage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 B

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View File

@ -0,0 +1,5 @@
import { HttpHandler } from '../../src/server/HttpHandler';
export interface ServerConfig {
getHttpHandler(): HttpHandler;
}

143
test/configs/Util.ts Normal file
View 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);
};

View 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);
});
});
});

View File

@ -2,66 +2,13 @@ import * as url from 'url';
import { namedNode, quad } from '@rdfjs/data-model';
import { Parser } from 'n3';
import { MockResponse } from 'node-mocks-http';
import { UnsecureWebIdExtractor } from '../../src/authentication/UnsecureWebIdExtractor';
import { AllowEverythingAuthorizer } from '../../src/authorization/AllowEverythingAuthorizer';
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 { BasicConfig } from '../configs/BasicConfig';
import { BasicHandlersConfig } from '../configs/BasicHandlersConfig';
import { call } from '../util/Util';
describe('An integrated AuthenticatedLdpHandler', (): void => {
describe('with simple handlers', (): void => {
const requestParser = new BasicRequestParser({
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,
});
const handler = new BasicConfig().getHttpHandler();
it('can add, read and delete data based on incoming requests.', async(): Promise<void> => {
// POST
@ -80,9 +27,17 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
// GET
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._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);
// DELETE
@ -92,57 +47,20 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
expect(response._getHeaders().location).toBe(url.format(requestUrl));
// 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._getData()).toContain('NotFoundHttpError');
});
});
describe('with simple PATCH handlers', (): void => {
const bodyParser: BodyParser = new CompositeAsyncHandler<HttpRequest, Representation | undefined>([
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,
});
const handler = new BasicHandlersConfig().getHttpHandler();
it('can handle simple SPARQL updates.', async(): Promise<void> => {
// POST
@ -169,7 +87,8 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
{ 'content-type': 'application/sparql-update', 'transfer-encoding': 'chunked' },
[ '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>}',
'WHERE {}' ],
'WHERE {}',
],
);
expect(response.statusCode).toBe(200);
expect(response._getData()).toHaveLength(0);
@ -177,18 +96,89 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
// GET
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._getData()).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._getData());
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')),
],
);
expect(triples).toBeRdfIsomorphic([
quad(
namedNode('http://test.com/s3'),
namedNode('http://test.com/p3'),
namedNode('http://test.com/o3'),
),
]);
});
});
});

View File

@ -1,127 +1,16 @@
import { MockResponse } from 'node-mocks-http';
import streamifyArray from 'streamify-array';
import { UnsecureWebIdExtractor } from '../../src/authentication/UnsecureWebIdExtractor';
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 { BasicHandlersWithAclConfig } from '../configs/BasicHandlersWithAclConfig';
import { AclTestHelper } from '../util/TestHelpers';
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 => {
const bodyParser: BodyParser = new RawBodyParser();
const requestParser = new BasicRequestParser({
targetExtractor: new BasicTargetExtractor(),
preferenceParser: new AcceptPreferenceParser(),
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,
});
const config = new BasicHandlersWithAclConfig();
const handler = config.getHttpHandler();
const { store } = config;
const aclHelper = new AclTestHelper(store, 'http://test.com/');
it('can create new entries.', async(): Promise<void> => {
await setAcl(convertingStore,
'http://test.com/',
{ read: true, write: true, append: true },
true,
true,
true,
undefined,
'agent');
await aclHelper.setSimpleAcl({ read: true, write: true, append: true }, 'agent');
// POST
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> => {
await setAcl(convertingStore,
'http://test.com/',
{ read: true, write: true, append: true },
true,
true,
true,
undefined,
'authenticated');
await aclHelper.setSimpleAcl({ read: true, write: true, append: true }, 'authenticated');
// POST
let requestUrl = new URL('http://test.com/');

View 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
View File

View File

@ -25,12 +25,18 @@ const { join: joinPath } = posix;
const base = 'http://test.com/';
const rootFilepath = '/Users/default/home/public/';
fsPromises.rmdir = jest.fn();
fsPromises.lstat = jest.fn();
fsPromises.readdir = jest.fn();
fsPromises.mkdir = jest.fn();
fsPromises.unlink = jest.fn();
fsPromises.access = jest.fn();
jest.mock('fs', (): any => ({
createReadStream: jest.fn(),
createWriteStream: jest.fn(),
promises: {
rmdir: jest.fn(),
lstat: jest.fn(),
readdir: jest.fn(),
mkdir: jest.fn(),
unlink: jest.fn(),
access: jest.fn(),
},
}));
describe('A FileResourceStore', (): void => {
let store: FileResourceStore;
@ -45,8 +51,6 @@ describe('A FileResourceStore', (): void => {
namedNode('http://test.com/o'),
);
fs.createReadStream = jest.fn();
beforeEach(async(): Promise<void> => {
jest.clearAllMocks();
@ -68,8 +72,6 @@ describe('A FileResourceStore', (): void => {
mtime: new Date(),
} as jest.Mocked<Stats>;
// Mock the fs functions for the createDataFile function.
fs.createWriteStream = jest.fn();
writeStream = {
on: jest.fn((name: string, func: () => void): any => {
if (name === 'finish') {

211
test/util/TestHelpers.ts Normal file
View 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;
}
}

View File

@ -5,14 +5,21 @@ import streamifyArray from 'streamify-array';
import { HttpHandler } from '../../src/server/HttpHandler';
import { HttpRequest } from '../../src/server/HttpRequest';
export const call = async(handler: HttpHandler, requestUrl: URL, method: string,
headers: IncomingHttpHeaders, data: string[]): Promise<MockResponse<any>> => {
export const call = async(
handler: HttpHandler,
requestUrl: URL,
method: string,
headers: IncomingHttpHeaders,
data: string[],
): 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 response: MockResponse<any> = createResponse({
eventEmitter: EventEmitter,
});
const endPromise = new Promise((resolve): void => {
response.on('end', (): void => {