Repository: solid/community-server
Pull request #30 (2020-07-16 10:10:37)

#29 Setup steps in readme


Patch:
diff --git a/.npmignore b/.npmignore
deleted file mode 100644
index 3a0edc65f..000000000
--- a/.npmignore
+++ /dev/null
@@ -1,1 +0,0 @@
-# Needed to overwrite .gitignore, actual files are determined by `files` field in package.json
\ No newline at end of file
diff --git a/README.md b/README.md
index 5fab3a204..540e932ed 100644
--- a/README.md
+++ b/README.md
@@ -5,3 +5,10 @@ Implementation of the Solid server.
 The architecture is based on the description [here](https://github.com/RubenVerborgh/solid-server-architecture).
 We follow the Inrupt [coding standards](https://github.com/inrupt/public-documentation/blob/master/coding-conventions/javascript-coding-standards.md) where feasible.
 An initial dummy implementation with some examples can be found [here](https://github.com/RubenVerborgh/solid-server-ts).
+
+## Running locally
+
+```
+npm ci
+npm start
+```
diff --git a/index.ts b/index.ts
index 871778ceb..cb5457909 100644
--- a/index.ts
+++ b/index.ts
@@ -56,12 +56,10 @@ export * from './src/server/HttpResponse';
 export * from './src/storage/AtomicResourceStore';
 export * from './src/storage/Conditions';
 export * from './src/storage/Lock';
-export * from './src/storage/LockingResourceStore';
 export * from './src/storage/RepresentationConverter';
 export * from './src/storage/ResourceLocker';
 export * from './src/storage/ResourceMapper';
 export * from './src/storage/ResourceStore';
-export * from './src/storage/SingleThreadedResourceLocker';
 export * from './src/storage/SimpleResourceStore';
 
 // Util/Errors
diff --git a/package-lock.json b/package-lock.json
index 13a234b3e..0026b9748 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -723,11 +723,6 @@
         "@types/node": "*"
       }
     },
-    "@types/async-lock": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/@types/async-lock/-/async-lock-1.1.2.tgz",
-      "integrity": "sha512-j9n4bb6RhgFIydBe0+kpjnBPYumDaDyU8zvbWykyVMkku+c2CSu31MZkLeaBfqIwU+XCxlDpYDfyMQRkM0AkeQ=="
-    },
     "@types/babel__core": {
       "version": "7.1.7",
       "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.7.tgz",
@@ -1345,11 +1340,6 @@
       "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
       "dev": true
     },
-    "async-lock": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.2.4.tgz",
-      "integrity": "sha512-UBQJC2pbeyGutIfYmErGc9RaJYnpZ1FHaxuKwb0ahvGiiCkPUf3p67Io+YLPmmv3RHY+mF6JEtNW8FlHsraAaA=="
-    },
     "asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -4721,9 +4711,9 @@
       }
     },
     "lodash": {
-      "version": "4.17.19",
-      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
-      "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
+      "version": "4.17.15",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+      "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
       "dev": true
     },
     "lodash.get": {
diff --git a/package.json b/package.json
index 8efabca90..af7ff1021 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,6 @@
     "build": "tsc",
     "coveralls": "jest --coverage && cat ./coverage/lcov.info | coveralls",
     "lint": "eslint . --ext .ts",
-    "prepare": "npm run build",
     "start": "node ./bin/server.js -p 3000",
     "test": "jest",
     "watch": "nodemon --watch \"src/**/*.js\" --watch \"bin/**/*.js\" --exec npm start"
@@ -29,14 +28,12 @@
   ],
   "dependencies": {
     "@rdfjs/data-model": "^1.1.2",
-    "@types/async-lock": "^1.1.2",
     "@types/cors": "^2.8.6",
     "@types/express": "^4.17.6",
     "@types/n3": "^1.4.0",
     "@types/node": "^14.0.1",
     "@types/rdf-js": "^3.0.0",
     "@types/yargs": "^15.0.5",
-    "async-lock": "^1.2.4",
     "cors": "^2.8.5",
     "express": "^4.17.1",
     "n3": "^1.4.0",
diff --git a/src/ldp/AuthenticatedLdpHandler.ts b/src/ldp/AuthenticatedLdpHandler.ts
index 52aced3d8..57bcf5619 100644
--- a/src/ldp/AuthenticatedLdpHandler.ts
+++ b/src/ldp/AuthenticatedLdpHandler.ts
@@ -98,7 +98,7 @@ export class AuthenticatedLdpHandler extends HttpHandler {
 
     const writeData = { response: input.response, description, error: err };
 
-    await this.responseWriter.handleSafe(writeData);
+    return this.responseWriter.handleSafe(writeData);
   }
 
   /**
diff --git a/src/ldp/http/SimpleBodyParser.ts b/src/ldp/http/SimpleBodyParser.ts
index f0eddce2e..1ad22eef5 100644
--- a/src/ldp/http/SimpleBodyParser.ts
+++ b/src/ldp/http/SimpleBodyParser.ts
@@ -1,10 +1,8 @@
 import { BodyParser } from './BodyParser';
 import { HttpRequest } from '../../server/HttpRequest';
-import { PassThrough } from 'stream';
 import { QuadRepresentation } from '../representation/QuadRepresentation';
 import { RepresentationMetadata } from '../representation/RepresentationMetadata';
 import { StreamParser } from 'n3';
-import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
 import { UnsupportedMediaTypeHttpError } from '../../util/errors/UnsupportedMediaTypeHttpError';
 
 /**
@@ -43,16 +41,9 @@ export class SimpleBodyParser extends BodyParser {
       contentType: mediaType,
     };
 
-    // Catch parsing errors and emit correct error
-    // Node 10 requires both writableObjectMode and readableObjectMode
-    const errorStream = new PassThrough({ writableObjectMode: true, readableObjectMode: true });
-    const data = input.pipe(new StreamParser());
-    data.pipe(errorStream);
-    data.on('error', (error): boolean => errorStream.emit('error', new UnsupportedHttpError(error.message)));
-
     return {
       dataType: 'quad',
-      data: errorStream,
+      data: input.pipe(new StreamParser()),
       metadata,
     };
   }
diff --git a/src/ldp/http/SimpleResponseWriter.ts b/src/ldp/http/SimpleResponseWriter.ts
index ba2187cba..3b504cd84 100644
--- a/src/ldp/http/SimpleResponseWriter.ts
+++ b/src/ldp/http/SimpleResponseWriter.ts
@@ -12,11 +12,6 @@ export class SimpleResponseWriter extends ResponseWriter {
     if (!input.description && !input.error) {
       throw new UnsupportedHttpError('Either a description or an error is required for output.');
     }
-    if (input.description && input.description.body) {
-      if (input.description.body.dataType !== 'binary' && input.description.body.dataType !== 'string') {
-        throw new UnsupportedHttpError('Only string or binary results are supported.');
-      }
-    }
   }
 
   public async handle(input: { response: HttpResponse; description?: ResponseDescription; error?: Error }): Promise<void> {
diff --git a/src/server/ExpressHttpServer.ts b/src/server/ExpressHttpServer.ts
index 996c58e8c..488f1f8b8 100644
--- a/src/server/ExpressHttpServer.ts
+++ b/src/server/ExpressHttpServer.ts
@@ -21,13 +21,7 @@ export class ExpressHttpServer {
     }));
 
     app.use(async(request, response): Promise<void> => {
-      try {
-        await this.handler.handleSafe({ request, response });
-      } catch (error) {
-        const errMsg = `${error.name}: ${error.message}\n${error.stack}`;
-        process.stderr.write(errMsg);
-        response.status(500).send(errMsg);
-      }
+      await this.handler.handleSafe({ request, response });
     });
     return app.listen(port);
   }
diff --git a/src/storage/LockingResourceStore.ts b/src/storage/LockingResourceStore.ts
deleted file mode 100644
index 8361d6f66..000000000
--- a/src/storage/LockingResourceStore.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { AtomicResourceStore } from './AtomicResourceStore';
-import { Conditions } from './Conditions';
-import { Patch } from '../ldp/http/Patch';
-import { Representation } from '../ldp/representation/Representation';
-import { RepresentationPreferences } from '../ldp/representation/RepresentationPreferences';
-import { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
-import { ResourceLocker } from './ResourceLocker';
-import { ResourceStore } from './ResourceStore';
-
-/**
- * Store that for every call acquires a lock before executing it on the requested resource,
- * and releases it afterwards.
- */
-export class LockingResourceStore implements AtomicResourceStore {
-  private readonly source: ResourceStore;
-  private readonly locks: ResourceLocker;
-
-  public constructor(source: ResourceStore, locks: ResourceLocker) {
-    this.source = source;
-    this.locks = locks;
-  }
-
-  public async addResource(container: ResourceIdentifier, representation: Representation, conditions?: Conditions): Promise<ResourceIdentifier> {
-    return this.lockedRun(container, async(): Promise<ResourceIdentifier> => this.source.addResource(container, representation, conditions));
-  }
-
-  public async deleteResource(identifier: ResourceIdentifier, conditions?: Conditions): Promise<void> {
-    return this.lockedRun(identifier, async(): Promise<void> => this.source.deleteResource(identifier, conditions));
-  }
-
-  public async getRepresentation(identifier: ResourceIdentifier, preferences: RepresentationPreferences, conditions?: Conditions): Promise<Representation> {
-    return this.lockedRun(identifier, async(): Promise<Representation> => this.source.getRepresentation(identifier, preferences, conditions));
-  }
-
-  public async modifyResource(identifier: ResourceIdentifier, patch: Patch, conditions?: Conditions): Promise<void> {
-    return this.lockedRun(identifier, async(): Promise<void> => this.source.modifyResource(identifier, patch, conditions));
-  }
-
-  public async setRepresentation(identifier: ResourceIdentifier, representation: Representation, conditions?: Conditions): Promise<void> {
-    return this.lockedRun(identifier, async(): Promise<void> => this.source.setRepresentation(identifier, representation, conditions));
-  }
-
-  private async lockedRun<T>(identifier: ResourceIdentifier, func: () => Promise<T>): Promise<T> {
-    const lock = await this.locks.acquire(identifier);
-    try {
-      return await func();
-    } finally {
-      await lock.release();
-    }
-  }
-}
diff --git a/src/storage/SingleThreadedResourceLocker.ts b/src/storage/SingleThreadedResourceLocker.ts
deleted file mode 100644
index bebaa9d9d..000000000
--- a/src/storage/SingleThreadedResourceLocker.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import AsyncLock from 'async-lock';
-import { Lock } from './Lock';
-import { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
-import { ResourceLocker } from './ResourceLocker';
-
-/**
- * A resource locker making use of the `async-lock` library.
- */
-export class SingleThreadedResourceLocker implements ResourceLocker {
-  private readonly locks: AsyncLock;
-
-  public constructor() {
-    this.locks = new AsyncLock();
-  }
-
-  /**
-   * Acquires a new lock for the requested identifier.
-   * Will resolve when the lock is available.
-   * @param identifier - Identifier of resource that needs to be locked.
-   *
-   * @returns The {@link Lock} when it's available. Its release function needs to be called when finished.
-   */
-  public async acquire(identifier: ResourceIdentifier): Promise<Lock> {
-    return new Promise(async(resolve): Promise<Lock> =>
-      this.locks.acquire(identifier.path, (done): void => {
-        resolve({ release: async(): Promise<void> => done() });
-      }));
-  }
-}


Test patch:
diff --git a/test/integration/RequestParser.test.ts b/test/integration/RequestParser.test.ts
index 469d2835b..dcc156cdf 100644
--- a/test/integration/RequestParser.test.ts
+++ b/test/integration/RequestParser.test.ts
@@ -1,11 +1,11 @@
 import { AcceptPreferenceParser } from '../../src/ldp/http/AcceptPreferenceParser';
 import arrayifyStream from 'arrayify-stream';
 import { HttpRequest } from '../../src/server/HttpRequest';
-import { Readable } from 'stream';
 import { SimpleBodyParser } from '../../src/ldp/http/SimpleBodyParser';
 import { SimpleRequestParser } from '../../src/ldp/http/SimpleRequestParser';
 import { SimpleTargetExtractor } from '../../src/ldp/http/SimpleTargetExtractor';
 import streamifyArray from 'streamify-array';
+import { StreamParser } from 'n3';
 import { namedNode, triple } from '@rdfjs/data-model';
 
 describe('A SimpleRequestParser with simple input parsers', (): void => {
@@ -34,7 +34,7 @@ describe('A SimpleRequestParser with simple input parsers', (): void => {
         language: [{ value: 'en-gb', weight: 1 }, { value: 'en', weight: 0.5 }],
       },
       body: {
-        data: expect.any(Readable),
+        data: expect.any(StreamParser),
         dataType: 'quad',
         metadata: {
           contentType: 'text/turtle',
diff --git a/test/unit/ldp/AuthenticatedLdpHandler.test.ts b/test/unit/ldp/AuthenticatedLdpHandler.test.ts
index f1e31b02a..efa1d61a5 100644
--- a/test/unit/ldp/AuthenticatedLdpHandler.test.ts
+++ b/test/unit/ldp/AuthenticatedLdpHandler.test.ts
@@ -42,7 +42,7 @@ describe('An AuthenticatedLdpHandler', (): void => {
   it('can handle input.', async(): Promise<void> => {
     const handler = new AuthenticatedLdpHandler(args);
 
-    await expect(handler.handle({ request: 'request' as any, response: 'response' as any })).resolves.toEqual(undefined);
+    await expect(handler.handle({ request: 'request' as any, response: 'response' as any })).resolves.toEqual('response');
     expect(responseFn).toHaveBeenCalledTimes(1);
     expect(responseFn).toHaveBeenLastCalledWith({ response: 'response', description: 'operation' as any });
   });
@@ -51,7 +51,7 @@ describe('An AuthenticatedLdpHandler', (): void => {
     args.requestParser = new StaticAsyncHandler(false, null);
     const handler = new AuthenticatedLdpHandler(args);
 
-    await expect(handler.handle({ request: 'request' as any, response: null })).resolves.toEqual(undefined);
+    await expect(handler.handle({ request: 'request' as any, response: null })).resolves.toEqual('response');
     expect(responseFn).toHaveBeenCalledTimes(1);
     expect(responseFn.mock.calls[0][0].error).toBeInstanceOf(Error);
   });
diff --git a/test/unit/ldp/http/SimpleBodyParser.test.ts b/test/unit/ldp/http/SimpleBodyParser.test.ts
index e5a0d4300..6e141e1fe 100644
--- a/test/unit/ldp/http/SimpleBodyParser.test.ts
+++ b/test/unit/ldp/http/SimpleBodyParser.test.ts
@@ -1,9 +1,8 @@
 import arrayifyStream from 'arrayify-stream';
 import { HttpRequest } from '../../../../src/server/HttpRequest';
-import { Readable } from 'stream';
 import { SimpleBodyParser } from '../../../../src/ldp/http/SimpleBodyParser';
 import streamifyArray from 'streamify-array';
-import { UnsupportedHttpError } from '../../../../src/util/errors/UnsupportedHttpError';
+import { StreamParser } from 'n3';
 import { UnsupportedMediaTypeHttpError } from '../../../../src/util/errors/UnsupportedMediaTypeHttpError';
 import { namedNode, triple } from '@rdfjs/data-model';
 import 'jest-rdf';
@@ -43,7 +42,7 @@ describe('A SimpleBodyparser', (): void => {
     input.headers = { 'content-type': 'text/turtle' };
     const result = await bodyParser.handle(input);
     expect(result).toEqual({
-      data: expect.any(Readable),
+      data: expect.any(StreamParser),
       dataType: 'quad',
       metadata: {
         contentType: 'text/turtle',
@@ -57,11 +56,4 @@ describe('A SimpleBodyparser', (): void => {
       namedNode('http://test.com/o'),
     ) ]);
   });
-
-  it('throws an UnsupportedHttpError on invalid triple data when reading the stream.', async(): Promise<void> => {
-    const input = streamifyArray([ '<http://test.com/s> <http://test.com/p> <http://test.com/o>' ]) as HttpRequest;
-    input.headers = { 'content-type': 'text/turtle' };
-    const result = await bodyParser.handle(input);
-    await expect(arrayifyStream(result.data)).rejects.toThrow(UnsupportedHttpError);
-  });
 });
diff --git a/test/unit/ldp/http/SimpleResponseWriter.test.ts b/test/unit/ldp/http/SimpleResponseWriter.test.ts
index 13bc02d9e..c2679fdeb 100644
--- a/test/unit/ldp/http/SimpleResponseWriter.test.ts
+++ b/test/unit/ldp/http/SimpleResponseWriter.test.ts
@@ -20,12 +20,6 @@ describe('A SimpleResponseWriter', (): void => {
     await expect(writer.canHandle({ response })).rejects.toThrow(UnsupportedHttpError);
   });
 
-  it('requires the description body to be a string or binary stream if present.', async(): Promise<void> => {
-    await expect(writer.canHandle({ response, description: { body: { dataType: 'quad' }} as ResponseDescription })).rejects.toThrow(UnsupportedHttpError);
-    await expect(writer.canHandle({ response, description: { body: { dataType: 'string' }} as ResponseDescription })).resolves.toBeUndefined();
-    await expect(writer.canHandle({ response, description: { body: { dataType: 'binary' }} as ResponseDescription })).resolves.toBeUndefined();
-  });
-
   it('responds with status code 200 and a location header if there is a description.', async(): Promise<void> => {
     await writer.handle({ response, description: { identifier: { path: 'path' }}});
     expect(response._isEndCalled()).toBeTruthy();
diff --git a/test/unit/server/ExpressHttpServer.test.ts b/test/unit/server/ExpressHttpServer.test.ts
index 183c0d113..a48c9619b 100644
--- a/test/unit/server/ExpressHttpServer.test.ts
+++ b/test/unit/server/ExpressHttpServer.test.ts
@@ -24,9 +24,8 @@ describe('ExpressHttpServer', (): void => {
   let server: Server;
   let canHandleJest: jest.Mock<Promise<void>, []>;
   let handleJest: jest.Mock<Promise<void>, [any]>;
-  let handler: SimpleHttpHandler;
   beforeEach(async(): Promise<void> => {
-    handler = new SimpleHttpHandler();
+    const handler = new SimpleHttpHandler();
     canHandleJest = jest.fn(async(): Promise<void> => undefined);
     handleJest = jest.fn(async(input): Promise<void> => handle(input));
 
@@ -75,12 +74,4 @@ describe('ExpressHttpServer', (): void => {
       response: expect.objectContaining({}),
     });
   });
-
-  it('catches errors thrown by its handler.', async(): Promise<void> => {
-    handler.handle = async(): Promise<void> => {
-      throw new Error('dummyError');
-    };
-    const res = await request(server).get('/').expect(500);
-    expect(res.text).toContain('dummyError');
-  });
 });
diff --git a/test/unit/storage/LockingResourceStore.test.ts b/test/unit/storage/LockingResourceStore.test.ts
deleted file mode 100644
index 906f6daaa..000000000
--- a/test/unit/storage/LockingResourceStore.test.ts
+++ /dev/null
@@ -1,97 +0,0 @@
-import { Lock } from '../../../src/storage/Lock';
-import { LockingResourceStore } from '../../../src/storage/LockingResourceStore';
-import { ResourceLocker } from '../../../src/storage/ResourceLocker';
-import { ResourceStore } from '../../../src/storage/ResourceStore';
-
-describe('A LockingResourceStore', (): void => {
-  let store: LockingResourceStore;
-  let locker: ResourceLocker;
-  let lock: Lock;
-  let release: () => Promise<void>;
-  let source: ResourceStore;
-  let order: string[];
-
-  beforeEach(async(): Promise<void> => {
-    order = [];
-    const delayedResolve = (resolve: () => void, name: string): void => {
-      // `setImmediate` is introduced to make sure the promise doesn't execute immediately
-      setImmediate((): void => {
-        order.push(name);
-        resolve();
-      });
-    };
-
-    source = {
-      getRepresentation: jest.fn(async(): Promise<any> => new Promise((resolve): any => delayedResolve(resolve, 'getRepresentation'))),
-      addResource: jest.fn(async(): Promise<any> => new Promise((resolve): any => delayedResolve(resolve, 'addResource'))),
-      setRepresentation: jest.fn(async(): Promise<any> => new Promise((resolve): any => delayedResolve(resolve, 'setRepresentation'))),
-      deleteResource: jest.fn(async(): Promise<any> => new Promise((resolve): any => delayedResolve(resolve, 'deleteResource'))),
-      modifyResource: jest.fn(async(): Promise<any> => new Promise((resolve): any => delayedResolve(resolve, 'modifyResource'))),
-    };
-    release = jest.fn(async(): Promise<any> => order.push('release'));
-    locker = {
-      acquire: jest.fn(async(): Promise<any> => {
-        order.push('acquire');
-        lock = { release };
-        return lock;
-      }),
-    };
-    store = new LockingResourceStore(source, locker);
-  });
-
-  it('acquires a lock on the resource when getting it.', async(): Promise<void> => {
-    await store.getRepresentation({ path: 'path' }, null);
-    expect(locker.acquire).toHaveBeenCalledTimes(1);
-    expect(locker.acquire).toHaveBeenLastCalledWith({ path: 'path' });
-    expect(source.getRepresentation).toHaveBeenCalledTimes(1);
-    expect(lock.release).toHaveBeenCalledTimes(1);
-    expect(order).toEqual([ 'acquire', 'getRepresentation', 'release' ]);
-  });
-
-  it('acquires a lock on the container when adding a representation.', async(): Promise<void> => {
-    await store.addResource({ path: 'path' }, null);
-    expect(locker.acquire).toHaveBeenCalledTimes(1);
-    expect(locker.acquire).toHaveBeenLastCalledWith({ path: 'path' });
-    expect(source.addResource).toHaveBeenCalledTimes(1);
-    expect(lock.release).toHaveBeenCalledTimes(1);
-    expect(order).toEqual([ 'acquire', 'addResource', 'release' ]);
-  });
-
-  it('acquires a lock on the resource when setting its representation.', async(): Promise<void> => {
-    await store.setRepresentation({ path: 'path' }, null);
-    expect(locker.acquire).toHaveBeenCalledTimes(1);
-    expect(locker.acquire).toHaveBeenLastCalledWith({ path: 'path' });
-    expect(source.setRepresentation).toHaveBeenCalledTimes(1);
-    expect(lock.release).toHaveBeenCalledTimes(1);
-    expect(order).toEqual([ 'acquire', 'setRepresentation', 'release' ]);
-  });
-
-  it('acquires a lock on the resource when deleting it.', async(): Promise<void> => {
-    await store.deleteResource({ path: 'path' });
-    expect(locker.acquire).toHaveBeenCalledTimes(1);
-    expect(locker.acquire).toHaveBeenLastCalledWith({ path: 'path' });
-    expect(source.deleteResource).toHaveBeenCalledTimes(1);
-    expect(lock.release).toHaveBeenCalledTimes(1);
-    expect(order).toEqual([ 'acquire', 'deleteResource', 'release' ]);
-  });
-
-  it('acquires a lock on the resource when modifying its representation.', async(): Promise<void> => {
-    await store.modifyResource({ path: 'path' }, null);
-    expect(locker.acquire).toHaveBeenCalledTimes(1);
-    expect(locker.acquire).toHaveBeenLastCalledWith({ path: 'path' });
-    expect(source.modifyResource).toHaveBeenCalledTimes(1);
-    expect(lock.release).toHaveBeenCalledTimes(1);
-    expect(order).toEqual([ 'acquire', 'modifyResource', 'release' ]);
-  });
-
-  it('releases the lock if an error was thrown.', async(): Promise<void> => {
-    source.getRepresentation = async(): Promise<any> => {
-      throw new Error('dummy');
-    };
-    await expect(store.getRepresentation({ path: 'path' }, null)).rejects.toThrow('dummy');
-    expect(locker.acquire).toHaveBeenCalledTimes(1);
-    expect(locker.acquire).toHaveBeenLastCalledWith({ path: 'path' });
-    expect(lock.release).toHaveBeenCalledTimes(1);
-    expect(order).toEqual([ 'acquire', 'release' ]);
-  });
-});
diff --git a/test/unit/storage/SingleThreadedResourceLocker.test.ts b/test/unit/storage/SingleThreadedResourceLocker.test.ts
deleted file mode 100644
index b4b51907e..000000000
--- a/test/unit/storage/SingleThreadedResourceLocker.test.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import { SingleThreadedResourceLocker } from '../../../src/storage/SingleThreadedResourceLocker';
-
-describe('A SingleThreadedResourceLocker', (): void => {
-  let locker: SingleThreadedResourceLocker;
-  beforeEach(async(): Promise<void> => {
-    locker = new SingleThreadedResourceLocker();
-  });
-
-  it('can acquire a lock.', async(): Promise<void> => {
-    const lock = await locker.acquire({ path: 'path' });
-    expect(lock).toEqual(expect.objectContaining({ release: expect.any(Function) }));
-  });
-
-  it('can release an acquired lock.', async(): Promise<void> => {
-    const lock = await locker.acquire({ path: 'path' });
-    await expect(lock.release()).resolves.toBeUndefined();
-  });
-
-  it('can acquire a lock after it was released.', async(): Promise<void> => {
-    let lock = await locker.acquire({ path: 'path' });
-    await lock.release();
-    lock = await locker.acquire({ path: 'path' });
-    expect(lock).toEqual(expect.objectContaining({ release: expect.any(Function) }));
-  });
-
-  it('blocks lock acquisition until they are released.', async(): Promise<void> => {
-    const results: number[] = [];
-    const lock1 = locker.acquire({ path: 'path' });
-    const lock2 = locker.acquire({ path: 'path' });
-    const lock3 = locker.acquire({ path: 'path' });
-
-    // Note the different order of calls
-    const prom2 = lock2.then(async(lock): Promise<void> => {
-      results.push(2);
-      return lock.release();
-    });
-    const prom3 = lock3.then(async(lock): Promise<void> => {
-      results.push(3);
-      return lock.release();
-    });
-    const prom1 = lock1.then(async(lock): Promise<void> => {
-      results.push(1);
-      return lock.release();
-    });
-    await Promise.all([ prom2, prom3, prom1 ]);
-    expect(results).toEqual([ 1, 2, 3 ]);
-  });
-
-  it('can acquire different keys simultaneously.', async(): Promise<void> => {
-    const results: number[] = [];
-    const lock1 = locker.acquire({ path: 'path1' });
-    const lock2 = locker.acquire({ path: 'path2' });
-    const lock3 = locker.acquire({ path: 'path3' });
-    await lock2.then(async(lock): Promise<void> => {
-      results.push(2);
-      return lock.release();
-    });
-    await lock3.then(async(lock): Promise<void> => {
-      results.push(3);
-      return lock.release();
-    });
-    await lock1.then(async(lock): Promise<void> => {
-      results.push(1);
-      return lock.release();
-    });
-    expect(results).toEqual([ 2, 3, 1 ]);
-  });
-});