Skip to content

Commit 5126356

Browse files
authoredAug 26, 2020
feat: Move runtime config into dedicated component, Closes #67
* Move runtime config into dedicated component, Closes #67 * Migrate FileResourceStore to RuntimeConfig
1 parent 4f8ebff commit 5126356

14 files changed

+194
-84
lines changed
 

‎bin/server.ts

+11-10
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
QuadToTurtleConverter,
1212
Representation,
1313
RepresentationConvertingStore,
14+
RuntimeConfig,
1415
Setup,
1516
SimpleAclAuthorizer,
1617
SimpleBodyParser,
@@ -36,15 +37,13 @@ import {
3637
const { argv } = yargs
3738
.usage('node ./bin/server.js [args]')
3839
.options({
39-
port: { type: 'number', alias: 'p', default: 3000 },
40+
port: { type: 'number', alias: 'p' },
4041
})
4142
.help();
4243

43-
const { port } = argv;
44-
45-
const base = `http://localhost:${port}/`;
46-
4744
// This is instead of the dependency injection that still needs to be added
45+
const runtimeConfig = new RuntimeConfig();
46+
4847
const bodyParser = new CompositeAsyncHandler<HttpRequest, Representation | undefined>([
4948
new SimpleSparqlUpdateBodyParser(),
5049
new SimpleBodyParser(),
@@ -62,7 +61,7 @@ const permissionsExtractor = new CompositeAsyncHandler([
6261
]);
6362

6463
// Will have to see how to best handle this
65-
const store = new SimpleResourceStore(base);
64+
const store = new SimpleResourceStore(runtimeConfig);
6665
const converter = new CompositeAsyncHandler([
6766
new TurtleToQuadConverter(),
6867
new QuadToTurtleConverter(),
@@ -73,7 +72,7 @@ const patcher = new SimpleSparqlUpdatePatchHandler(convertingStore, locker);
7372
const patchingStore = new PatchingStore(convertingStore, patcher);
7473

7574
const aclManager = new SimpleExtensionAclManager();
76-
const containerManager = new UrlContainerManager(base);
75+
const containerManager = new UrlContainerManager(runtimeConfig);
7776
const authorizer = new SimpleAclAuthorizer(aclManager, containerManager, patchingStore);
7877

7978
const operationHandler = new CompositeAsyncHandler([
@@ -97,9 +96,11 @@ const httpHandler = new AuthenticatedLdpHandler({
9796

9897
const httpServer = new ExpressHttpServer(httpHandler);
9998

100-
const setup = new Setup(httpServer, store, aclManager);
101-
setup.setup(port, base).then((): void => {
102-
process.stdout.write(`Running at ${base}\n`);
99+
const setup = new Setup(httpServer, store, aclManager, runtimeConfig);
100+
101+
runtimeConfig.reset({ port: argv.port });
102+
setup.setup().then((): void => {
103+
process.stdout.write(`Running at ${runtimeConfig.base}\n`);
103104
}).catch((error): void => {
104105
process.stderr.write(`${error}\n`);
105106
process.exit(1);

‎index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export * from './src/authorization/SimpleAuthorizer';
1111
export * from './src/authorization/SimpleExtensionAclManager';
1212

1313
// Init
14+
export * from './src/init/RuntimeConfig';
1415
export * from './src/init/Setup';
1516

1617
// LDP/HTTP

‎src/init/RuntimeConfig.ts

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* This class holds all configuration options that can be defined by the user via the command line.
3+
*
4+
* Concretely, this contains data that is only relevant *after* dependency injection.
5+
*/
6+
export class RuntimeConfig implements RuntimeConfigData {
7+
private pport!: number;
8+
private pbase!: string;
9+
private prootFilepath!: string;
10+
11+
public constructor(data: RuntimeConfigData = {}) {
12+
this.reset(data);
13+
}
14+
15+
public reset(data: RuntimeConfigData): void {
16+
this.pport = data.port ?? 3000;
17+
this.pbase = data.base ?? `http://localhost:${this.port}/`;
18+
this.prootFilepath = data.rootFilepath ?? process.cwd();
19+
}
20+
21+
public get base(): string {
22+
return this.pbase;
23+
}
24+
25+
public get port(): number {
26+
return this.pport;
27+
}
28+
29+
public get rootFilepath(): string {
30+
return this.prootFilepath;
31+
}
32+
}
33+
34+
export interface RuntimeConfigData {
35+
port?: number;
36+
base?: string;
37+
rootFilepath?: string;
38+
}

‎src/init/Setup.ts

+14-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { AclManager } from '../authorization/AclManager';
22
import { DATA_TYPE_BINARY } from '../util/ContentTypes';
33
import { ExpressHttpServer } from '../server/ExpressHttpServer';
44
import { ResourceStore } from '../storage/ResourceStore';
5+
import { RuntimeConfig } from './RuntimeConfig';
56
import streamifyArray from 'streamify-array';
67

78
/**
@@ -11,19 +12,26 @@ export class Setup {
1112
private readonly httpServer: ExpressHttpServer;
1213
private readonly store: ResourceStore;
1314
private readonly aclManager: AclManager;
15+
private readonly runtimeConfig: RuntimeConfig;
1416

15-
public constructor(httpServer: ExpressHttpServer, store: ResourceStore, aclManager: AclManager) {
17+
public constructor(
18+
httpServer: ExpressHttpServer,
19+
store: ResourceStore,
20+
aclManager: AclManager,
21+
runtimeConfig: RuntimeConfig,
22+
) {
1623
this.httpServer = httpServer;
1724
this.store = store;
1825
this.aclManager = aclManager;
26+
this.runtimeConfig = runtimeConfig;
1927
}
2028

2129
/**
2230
* Set up a server at the given port and base URL.
2331
* @param port - A port number.
2432
* @param base - A base URL.
2533
*/
26-
public async setup(port: number, base: string): Promise<void> {
34+
public async setup(): Promise<void> {
2735
// Set up acl so everything can still be done by default
2836
// Note that this will need to be adapted to go through all the correct channels later on
2937
const aclSetup = async(): Promise<void> => {
@@ -38,10 +46,10 @@ export class Setup {
3846
acl:mode acl:Append;
3947
acl:mode acl:Delete;
4048
acl:mode acl:Control;
41-
acl:accessTo <${base}>;
42-
acl:default <${base}>.`;
49+
acl:accessTo <${this.runtimeConfig.base}>;
50+
acl:default <${this.runtimeConfig.base}>.`;
4351
await this.store.setRepresentation(
44-
await this.aclManager.getAcl({ path: base }),
52+
await this.aclManager.getAcl({ path: this.runtimeConfig.base }),
4553
{
4654
dataType: DATA_TYPE_BINARY,
4755
data: streamifyArray([ acl ]),
@@ -56,6 +64,6 @@ export class Setup {
5664

5765
await aclSetup();
5866

59-
this.httpServer.listen(port);
67+
this.httpServer.listen(this.runtimeConfig.port);
6068
}
6169
}

‎src/storage/FileResourceStore.ts

+13-8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Representation } from '../ldp/representation/Representation';
1212
import { RepresentationMetadata } from '../ldp/representation/RepresentationMetadata';
1313
import { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
1414
import { ResourceStore } from './ResourceStore';
15+
import { RuntimeConfig } from '../init/RuntimeConfig';
1516
import streamifyArray from 'streamify-array';
1617
import { UnsupportedMediaTypeHttpError } from '../util/errors/UnsupportedMediaTypeHttpError';
1718
import { CONTENT_TYPE_QUADS, DATA_TYPE_BINARY, DATA_TYPE_QUAD } from '../util/ContentTypes';
@@ -25,26 +26,30 @@ const { extname, join: joinPath, normalize: normalizePath } = posix;
2526
* All requests will throw an {@link NotFoundHttpError} if unknown identifiers get passed.
2627
*/
2728
export class FileResourceStore implements ResourceStore {
28-
private readonly baseRequestURI: string;
29-
private readonly rootFilepath: string;
29+
private readonly runtimeConfig: RuntimeConfig;
3030
private readonly interactionController: InteractionController;
3131
private readonly metadataController: MetadataController;
3232

3333
/**
34-
* @param baseRequestURI - Will be stripped of all incoming URIs and added to all outgoing ones to find the relative
35-
* path.
36-
* @param rootFilepath - Root filepath in which the resources and containers will be saved as files and directories.
34+
* @param runtimeConfig - The runtime config.
3735
* @param interactionController - Instance of InteractionController to use.
3836
* @param metadataController - Instance of MetadataController to use.
3937
*/
40-
public constructor(baseRequestURI: string, rootFilepath: string, interactionController: InteractionController,
38+
public constructor(runtimeConfig: RuntimeConfig, interactionController: InteractionController,
4139
metadataController: MetadataController) {
42-
this.baseRequestURI = trimTrailingSlashes(baseRequestURI);
43-
this.rootFilepath = trimTrailingSlashes(rootFilepath);
40+
this.runtimeConfig = runtimeConfig;
4441
this.interactionController = interactionController;
4542
this.metadataController = metadataController;
4643
}
4744

45+
public get baseRequestURI(): string {
46+
return trimTrailingSlashes(this.runtimeConfig.base);
47+
}
48+
49+
public get rootFilepath(): string {
50+
return trimTrailingSlashes(this.runtimeConfig.rootFilepath);
51+
}
52+
4853
/**
4954
* Store the incoming data as a file under a file path corresponding to `container.path`,
5055
* where slashes correspond to subdirectories.

‎src/storage/SimpleResourceStore.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { NotFoundHttpError } from '../util/errors/NotFoundHttpError';
55
import { Representation } from '../ldp/representation/Representation';
66
import { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
77
import { ResourceStore } from './ResourceStore';
8+
import { RuntimeConfig } from '../init/RuntimeConfig';
89
import streamifyArray from 'streamify-array';
910

1011
/**
@@ -13,14 +14,15 @@ import streamifyArray from 'streamify-array';
1314
*/
1415
export class SimpleResourceStore implements ResourceStore {
1516
private readonly store: { [id: string]: Representation };
16-
private readonly base: string;
17+
private readonly runtimeConfig: RuntimeConfig;
1718
private index = 0;
1819

1920
/**
20-
* @param base - Will be stripped of all incoming URIs and added to all outgoing ones to find the relative path.
21+
* @param runtimeConfig - Config containing base that will be stripped of all incoming URIs
22+
* and added to all outgoing ones to find the relative path.
2123
*/
22-
public constructor(base: string) {
23-
this.base = base;
24+
public constructor(runtimeConfig: RuntimeConfig) {
25+
this.runtimeConfig = runtimeConfig;
2426

2527
this.store = {
2628
// Default root entry (what you get when the identifier is equal to the base)
@@ -102,8 +104,8 @@ export class SimpleResourceStore implements ResourceStore {
102104
* @returns A string representing the relative path.
103105
*/
104106
private parseIdentifier(identifier: ResourceIdentifier): string {
105-
const path = identifier.path.slice(this.base.length);
106-
if (!identifier.path.startsWith(this.base)) {
107+
const path = identifier.path.slice(this.runtimeConfig.base.length);
108+
if (!identifier.path.startsWith(this.runtimeConfig.base)) {
107109
throw new NotFoundHttpError();
108110
}
109111
return path;

‎src/storage/UrlContainerManager.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
import { ContainerManager } from './ContainerManager';
22
import { ensureTrailingSlash } from '../util/Util';
33
import { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
4+
import { RuntimeConfig } from '../init/RuntimeConfig';
45

56
/**
67
* Determines containers based on URL decomposition.
78
*/
89
export class UrlContainerManager implements ContainerManager {
9-
private readonly root: string;
10+
private readonly runtimeConfig: RuntimeConfig;
1011

11-
public constructor(root: string) {
12-
this.root = this.canonicalUrl(root);
12+
public constructor(runtimeConfig: RuntimeConfig) {
13+
this.runtimeConfig = runtimeConfig;
1314
}
1415

1516
public async getContainer(id: ResourceIdentifier): Promise<ResourceIdentifier> {
1617
const path = this.canonicalUrl(id.path);
17-
if (this.root === path) {
18+
if (this.canonicalUrl(this.runtimeConfig.base) === path) {
1819
throw new Error('Root does not have a container.');
1920
}
2021

‎test/integration/AuthenticatedLdpHandler.test.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { QuadToTurtleConverter } from '../../src/storage/conversion/QuadToTurtle
1313
import { Representation } from '../../src/ldp/representation/Representation';
1414
import { RepresentationConvertingStore } from '../../src/storage/RepresentationConvertingStore';
1515
import { ResponseDescription } from '../../src/ldp/operations/ResponseDescription';
16+
import { RuntimeConfig } from '../../src/init/RuntimeConfig';
1617
import { SimpleAuthorizer } from '../../src/authorization/SimpleAuthorizer';
1718
import { SimpleBodyParser } from '../../src/ldp/http/SimpleBodyParser';
1819
import { SimpleCredentialsExtractor } from '../../src/authentication/SimpleCredentialsExtractor';
@@ -44,7 +45,7 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
4445
const permissionsExtractor = new BasePermissionsExtractor();
4546
const authorizer = new SimpleAuthorizer();
4647

47-
const store = new SimpleResourceStore('http://test.com/');
48+
const store = new SimpleResourceStore(new RuntimeConfig({ base: 'http://test.com/' }));
4849
const operationHandler = new CompositeAsyncHandler<Operation, ResponseDescription>([
4950
new SimpleGetOperationHandler(store),
5051
new SimplePostOperationHandler(store),
@@ -115,7 +116,7 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
115116
]);
116117
const authorizer = new SimpleAuthorizer();
117118

118-
const store = new SimpleResourceStore('http://test.com/');
119+
const store = new SimpleResourceStore(new RuntimeConfig({ base: 'http://test.com/' }));
119120
const converter = new CompositeAsyncHandler([
120121
new QuadToTurtleConverter(),
121122
new TurtleToQuadConverter(),

‎test/integration/Authorization.test.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { QuadToTurtleConverter } from '../../src/storage/conversion/QuadToTurtle
1212
import { RepresentationConvertingStore } from '../../src/storage/RepresentationConvertingStore';
1313
import { ResourceStore } from '../../src/storage/ResourceStore';
1414
import { ResponseDescription } from '../../src/ldp/operations/ResponseDescription';
15+
import { RuntimeConfig } from '../../src/init/RuntimeConfig';
1516
import { SimpleAclAuthorizer } from '../../src/authorization/SimpleAclAuthorizer';
1617
import { SimpleBodyParser } from '../../src/ldp/http/SimpleBodyParser';
1718
import { SimpleCredentialsExtractor } from '../../src/authentication/SimpleCredentialsExtractor';
@@ -80,7 +81,7 @@ describe('A server with authorization', (): void => {
8081
bodyParser,
8182
});
8283

83-
const store = new SimpleResourceStore('http://test.com/');
84+
const store = new SimpleResourceStore(new RuntimeConfig({ base: 'http://test.com/' }));
8485
const converter = new CompositeAsyncHandler([
8586
new QuadToTurtleConverter(),
8687
new TurtleToQuadConverter(),
@@ -91,7 +92,7 @@ describe('A server with authorization', (): void => {
9192
const permissionsExtractor = new BasePermissionsExtractor();
9293
const authorizer = new SimpleAclAuthorizer(
9394
new SimpleExtensionAclManager(),
94-
new UrlContainerManager('http://test.com/'),
95+
new UrlContainerManager(new RuntimeConfig({ base: 'http://test.com/' })),
9596
convertingStore,
9697
);
9798

‎test/unit/init/RuntimeConfig.test.ts

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { RuntimeConfig } from '../../../src/init/RuntimeConfig';
2+
3+
describe('RuntimeConfig', (): void => {
4+
it('handles undefined args.', async(): Promise<void> => {
5+
const config = new RuntimeConfig();
6+
expect(config.port).toEqual(3000);
7+
expect(config.base).toEqual('http://localhost:3000/');
8+
});
9+
10+
it('handles empty args.', async(): Promise<void> => {
11+
const config = new RuntimeConfig({});
12+
expect(config.port).toEqual(3000);
13+
expect(config.base).toEqual('http://localhost:3000/');
14+
});
15+
16+
it('handles args with port.', async(): Promise<void> => {
17+
const config = new RuntimeConfig({ port: 1234 });
18+
expect(config.port).toEqual(1234);
19+
expect(config.base).toEqual('http://localhost:1234/');
20+
});
21+
22+
it('handles args with base.', async(): Promise<void> => {
23+
const config = new RuntimeConfig({ base: 'http://example.org/' });
24+
expect(config.port).toEqual(3000);
25+
expect(config.base).toEqual('http://example.org/');
26+
});
27+
28+
it('handles args with port and base.', async(): Promise<void> => {
29+
const config = new RuntimeConfig({ port: 1234, base: 'http://example.org/' });
30+
expect(config.port).toEqual(1234);
31+
expect(config.base).toEqual('http://example.org/');
32+
});
33+
34+
it('handles resetting data.', async(): Promise<void> => {
35+
const config = new RuntimeConfig({});
36+
expect(config.port).toEqual(3000);
37+
expect(config.base).toEqual('http://localhost:3000/');
38+
39+
config.reset({ port: 1234, base: 'http://example.org/' });
40+
expect(config.port).toEqual(1234);
41+
expect(config.base).toEqual('http://example.org/');
42+
});
43+
});

‎test/unit/init/Setup.test.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { RuntimeConfig } from '../../../src/init/RuntimeConfig';
12
import { Setup } from '../../../src/init/Setup';
23

34
describe('Setup', (): void => {
@@ -15,16 +16,16 @@ describe('Setup', (): void => {
1516
httpServer = {
1617
listen: jest.fn(),
1718
};
18-
setup = new Setup(httpServer, store, aclManager);
19+
setup = new Setup(httpServer, store, aclManager, new RuntimeConfig());
1920
});
2021

2122
it('starts an HTTP server.', async(): Promise<void> => {
22-
await setup.setup(3000, 'http://localhost:3000/');
23+
await setup.setup();
2324
expect(httpServer.listen).toHaveBeenCalledWith(3000);
2425
});
2526

2627
it('invokes ACL initialization.', async(): Promise<void> => {
27-
await setup.setup(3000, 'http://localhost:3000/');
28+
await setup.setup();
2829
expect(aclManager.getAcl).toHaveBeenCalledWith({ path: 'http://localhost:3000/' });
2930
expect(store.setRepresentation).toHaveBeenCalledTimes(1);
3031
});

‎test/unit/storage/FileResourceStore.test.ts

+44-38
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { NotFoundHttpError } from '../../../src/util/errors/NotFoundHttpError';
1010
import { posix } from 'path';
1111
import { Readable } from 'stream';
1212
import { RepresentationMetadata } from '../../../src/ldp/representation/RepresentationMetadata';
13+
import { RuntimeConfig } from '../../../src/init/RuntimeConfig';
1314
import streamifyArray from 'streamify-array';
1415
import { UnsupportedMediaTypeHttpError } from '../../../src/util/errors/UnsupportedMediaTypeHttpError';
1516
import { CONTENT_TYPE_QUADS, DATA_TYPE_BINARY, DATA_TYPE_QUAD } from '../../../src/util/ContentTypes';
@@ -21,7 +22,7 @@ import { literal, namedNode, quad as quadRDF, triple } from '@rdfjs/data-model';
2122
const { join: joinPath } = posix;
2223

2324
const base = 'http://test.com/';
24-
const root = '/Users/default/home/public/';
25+
const rootFilepath = '/Users/default/home/public/';
2526

2627
fsPromises.rmdir = jest.fn();
2728
fsPromises.lstat = jest.fn();
@@ -48,7 +49,11 @@ describe('A FileResourceStore', (): void => {
4849
beforeEach(async(): Promise<void> => {
4950
jest.clearAllMocks();
5051

51-
store = new FileResourceStore(base, root, new InteractionController(), new MetadataController());
52+
store = new FileResourceStore(
53+
new RuntimeConfig({ base, rootFilepath }),
54+
new InteractionController(),
55+
new MetadataController(),
56+
);
5257

5358
representation = {
5459
data: streamifyArray([ rawData ]),
@@ -131,7 +136,7 @@ describe('A FileResourceStore', (): void => {
131136
// Write container (POST)
132137
representation.metadata = { linkRel: { type: new Set([ LINK_TYPE_LDP_BC ]) }, slug: 'myContainer/', raw: []};
133138
const identifier = await store.addResource({ path: base }, representation);
134-
expect(fsPromises.mkdir as jest.Mock).toBeCalledWith(joinPath(root, 'myContainer/'), { recursive: true });
139+
expect(fsPromises.mkdir as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'myContainer/'), { recursive: true });
135140
expect(identifier.path).toBe(`${base}myContainer/`);
136141

137142
// Read container
@@ -155,7 +160,7 @@ describe('A FileResourceStore', (): void => {
155160
// Tests
156161
representation.metadata = { linkRel: { type: new Set([ LINK_TYPE_LDP_BC ]) }, slug: 'myContainer/', raw: []};
157162
await expect(store.addResource({ path: `${base}foo` }, representation)).rejects.toThrow(MethodNotAllowedHttpError);
158-
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(root, 'foo'));
163+
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'foo'));
159164
});
160165

161166
it('errors 405 for POST invalid path ending without slash.', async(): Promise<void> => {
@@ -172,17 +177,17 @@ describe('A FileResourceStore', (): void => {
172177
representation.metadata = { linkRel: { type: new Set([ LINK_TYPE_LDP_BC ]) }, slug: 'myContainer/', raw: []};
173178
await expect(store.addResource({ path: `${base}doesnotexist` }, representation))
174179
.rejects.toThrow(MethodNotAllowedHttpError);
175-
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(root, 'doesnotexist'));
180+
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'doesnotexist'));
176181

177182
representation.metadata = { linkRel: { type: new Set([ LINK_TYPE_LDPR ]) }, slug: 'file.txt', raw: []};
178183
await expect(store.addResource({ path: `${base}doesnotexist` }, representation))
179184
.rejects.toThrow(MethodNotAllowedHttpError);
180-
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(root, 'doesnotexist'));
185+
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'doesnotexist'));
181186

182187
representation.metadata = { linkRel: { type: new Set() }, slug: 'file.txt', raw: []};
183188
await expect(store.addResource({ path: `${base}existingresource` }, representation))
184189
.rejects.toThrow(MethodNotAllowedHttpError);
185-
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(root, 'existingresource'));
190+
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'existingresource'));
186191
});
187192

188193
it('can set data.', async(): Promise<void> => {
@@ -204,7 +209,7 @@ describe('A FileResourceStore', (): void => {
204209

205210
// Tests
206211
await store.setRepresentation({ path: `${base}file.txt` }, representation);
207-
expect(fs.createWriteStream as jest.Mock).toBeCalledWith(joinPath(root, 'file.txt'));
212+
expect(fs.createWriteStream as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'file.txt'));
208213
const result = await store.getRepresentation({ path: `${base}file.txt` });
209214
expect(result).toEqual({
210215
dataType: DATA_TYPE_BINARY,
@@ -217,9 +222,9 @@ describe('A FileResourceStore', (): void => {
217222
},
218223
});
219224
await expect(arrayifyStream(result.data)).resolves.toEqual([ rawData ]);
220-
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(root, 'file.txt'));
221-
expect(fs.createReadStream as jest.Mock).toBeCalledWith(joinPath(root, 'file.txt'));
222-
expect(fs.createReadStream as jest.Mock).toBeCalledWith(joinPath(root, 'file.txt.metadata'));
225+
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'file.txt'));
226+
expect(fs.createReadStream as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'file.txt'));
227+
expect(fs.createReadStream as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'file.txt.metadata'));
223228
});
224229

225230
it('can delete data.', async(): Promise<void> => {
@@ -239,7 +244,7 @@ describe('A FileResourceStore', (): void => {
239244

240245
// Tests
241246
await store.deleteResource({ path: `${base}file.txt` });
242-
expect(fsPromises.unlink as jest.Mock).toBeCalledWith(joinPath(root, 'file.txt'));
247+
expect(fsPromises.unlink as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'file.txt'));
243248
await expect(store.getRepresentation({ path: `${base}file.txt` })).rejects.toThrow(NotFoundHttpError);
244249
});
245250

@@ -253,8 +258,9 @@ describe('A FileResourceStore', (): void => {
253258
representation.metadata = { linkRel: { type: new Set([ LINK_TYPE_LDPR ]) }, slug: 'file.txt', raw: []};
254259
const identifier = await store.addResource({ path: `${base}doesnotexistyet/` }, representation);
255260
expect(identifier.path).toBe(`${base}doesnotexistyet/file.txt`);
256-
expect(fsPromises.mkdir as jest.Mock).toBeCalledWith(joinPath(root, 'doesnotexistyet/'), { recursive: true });
257-
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(root, 'doesnotexistyet/'));
261+
expect(fsPromises.mkdir as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'doesnotexistyet/'),
262+
{ recursive: true });
263+
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'doesnotexistyet/'));
258264
});
259265

260266
it('creates metadata file when metadata triples are passed.', async(): Promise<void> => {
@@ -277,8 +283,8 @@ describe('A FileResourceStore', (): void => {
277283
representation.metadata = { linkRel: { type: new Set([ LINK_TYPE_LDPR ]) }, raw: [ quad ]};
278284
representation.data = readableMock;
279285
await store.addResource({ path: `${base}foo/` }, representation);
280-
expect(fsPromises.mkdir as jest.Mock).toBeCalledWith(joinPath(root, 'foo/'), { recursive: true });
281-
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(root, 'foo/'));
286+
expect(fsPromises.mkdir as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'foo/'), { recursive: true });
287+
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'foo/'));
282288

283289
representation.metadata = { linkRel: { type: new Set([ LINK_TYPE_LDPR ]) }, raw: [ quad ]};
284290
await store.setRepresentation({ path: `${base}foo/file.txt` }, representation);
@@ -298,8 +304,8 @@ describe('A FileResourceStore', (): void => {
298304

299305
// Tests
300306
await expect(store.deleteResource({ path: `${base}notempty/` })).rejects.toThrow(ConflictHttpError);
301-
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(root, 'notempty/'));
302-
expect(fsPromises.readdir as jest.Mock).toBeCalledWith(joinPath(root, 'notempty/'));
307+
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'notempty/'));
308+
expect(fsPromises.readdir as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'notempty/'));
303309
});
304310

305311
it('deletes metadata file when deleting container.', async(): Promise<void> => {
@@ -312,10 +318,10 @@ describe('A FileResourceStore', (): void => {
312318

313319
// Tests
314320
await store.deleteResource({ path: `${base}foo/` });
315-
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(root, 'foo/'));
316-
expect(fsPromises.readdir as jest.Mock).toBeCalledWith(joinPath(root, 'foo/'));
317-
expect(fsPromises.unlink as jest.Mock).toBeCalledWith(joinPath(root, 'foo', '.metadata'));
318-
expect(fsPromises.rmdir as jest.Mock).toBeCalledWith(joinPath(root, 'foo/'));
321+
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'foo/'));
322+
expect(fsPromises.readdir as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'foo/'));
323+
expect(fsPromises.unlink as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'foo', '.metadata'));
324+
expect(fsPromises.rmdir as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'foo/'));
319325
});
320326

321327
it('errors 404 when accessing non resource (file/directory), e.g. special files.', async(): Promise<void> => {
@@ -368,10 +374,10 @@ describe('A FileResourceStore', (): void => {
368374
},
369375
});
370376
await expect(arrayifyStream(result.data)).resolves.toEqualRdfQuadArray(quads);
371-
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(root, 'foo/'));
372-
expect(fsPromises.readdir as jest.Mock).toBeCalledWith(joinPath(root, 'foo/'));
373-
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(root, 'foo', 'file.txt'));
374-
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(root, 'foo', '.nonresource'));
377+
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'foo/'));
378+
expect(fsPromises.readdir as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'foo/'));
379+
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'foo', 'file.txt'));
380+
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'foo', '.nonresource'));
375381
});
376382

377383
it('can overwrite representation with PUT.', async(): Promise<void> => {
@@ -387,8 +393,8 @@ describe('A FileResourceStore', (): void => {
387393
representation.metadata = { linkRel: { type: new Set([ LINK_TYPE_LDPR ]) }, raw: []};
388394
await store.setRepresentation({ path: `${base}alreadyexists.txt` }, representation);
389395
expect(fs.createWriteStream as jest.Mock).toBeCalledTimes(1);
390-
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(root, 'alreadyexists.txt'));
391-
expect(fsPromises.mkdir as jest.Mock).toBeCalledWith(root, { recursive: true });
396+
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'alreadyexists.txt'));
397+
expect(fsPromises.mkdir as jest.Mock).toBeCalledWith(rootFilepath, { recursive: true });
392398
});
393399

394400
it('errors when overwriting container with PUT.', async(): Promise<void> => {
@@ -399,7 +405,7 @@ describe('A FileResourceStore', (): void => {
399405
// Tests
400406
await expect(store.setRepresentation({ path: `${base}alreadyexists` }, representation)).rejects
401407
.toThrow(ConflictHttpError);
402-
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(root, 'alreadyexists'));
408+
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'alreadyexists'));
403409
representation.metadata = { linkRel: { type: new Set([ LINK_TYPE_LDP_BC ]) }, raw: []};
404410
await expect(store.setRepresentation({ path: `${base}alreadyexists/` }, representation)).rejects
405411
.toThrow(ConflictHttpError);
@@ -445,9 +451,9 @@ describe('A FileResourceStore', (): void => {
445451
// Tests
446452
representation.metadata = { linkRel: { type: new Set([ LINK_TYPE_LDPR ]) }, slug: 'file.txt', raw: [ quad ]};
447453
await expect(store.addResource({ path: base }, representation)).rejects.toThrow(Error);
448-
expect(fs.createWriteStream as jest.Mock).toBeCalledWith(joinPath(root, 'file.txt.metadata'));
449-
expect(fs.createWriteStream as jest.Mock).toBeCalledWith(joinPath(root, 'file.txt'));
450-
expect(fsPromises.unlink as jest.Mock).toBeCalledWith(joinPath(root, 'file.txt.metadata'));
454+
expect(fs.createWriteStream as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'file.txt.metadata'));
455+
expect(fs.createWriteStream as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'file.txt'));
456+
expect(fsPromises.unlink as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'file.txt.metadata'));
451457
});
452458

453459
it('undoes container creation when metadata file creation fails.', async(): Promise<void> => {
@@ -461,7 +467,7 @@ describe('A FileResourceStore', (): void => {
461467
// Tests
462468
representation.metadata = { linkRel: { type: new Set([ LINK_TYPE_LDP_BC ]) }, slug: 'foo/', raw: [ quad ]};
463469
await expect(store.addResource({ path: base }, representation)).rejects.toThrow(Error);
464-
expect(fsPromises.rmdir as jest.Mock).toBeCalledWith(joinPath(root, 'foo/'));
470+
expect(fsPromises.rmdir as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'foo/'));
465471
});
466472

467473
it('creates container when POSTing without linkRel and with slug ending with slash.', async(): Promise<void> => {
@@ -474,7 +480,7 @@ describe('A FileResourceStore', (): void => {
474480
const identifier = await store.addResource({ path: base }, representation);
475481
expect(identifier.path).toBe(`${base}myContainer/`);
476482
expect(fsPromises.mkdir as jest.Mock).toBeCalledTimes(1);
477-
expect(fsPromises.mkdir as jest.Mock).toBeCalledWith(joinPath(root, 'myContainer/'), { recursive: true });
483+
expect(fsPromises.mkdir as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'myContainer/'), { recursive: true });
478484
});
479485

480486
it('returns no contentType when unknown for representation.', async(): Promise<void> => {
@@ -514,9 +520,9 @@ describe('A FileResourceStore', (): void => {
514520
// Tests
515521
representation.metadata = { raw: []};
516522
await store.setRepresentation({ path: `${base}file.txt` }, representation);
517-
expect(fsPromises.mkdir as jest.Mock).toBeCalledWith(root, { recursive: true });
523+
expect(fsPromises.mkdir as jest.Mock).toBeCalledWith(rootFilepath, { recursive: true });
518524
expect(fs.createWriteStream as jest.Mock).toBeCalledTimes(1);
519-
expect(fs.createWriteStream as jest.Mock).toBeCalledWith(joinPath(root, 'file.txt'));
525+
expect(fs.createWriteStream as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'file.txt'));
520526
});
521527

522528
it('creates container when POST to existing container path ending without slash and slug without slash.',
@@ -530,7 +536,7 @@ describe('A FileResourceStore', (): void => {
530536
representation.metadata = { linkRel: { type: new Set([ LINK_TYPE_LDP_BC ]) }, slug: 'bar', raw: []};
531537
const identifier = await store.addResource({ path: `${base}foo` }, representation);
532538
expect(identifier.path).toBe(`${base}foo/bar/`);
533-
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(root, 'foo'));
534-
expect(fsPromises.mkdir as jest.Mock).toBeCalledWith(joinPath(root, 'foo', 'bar/'), { recursive: false });
539+
expect(fsPromises.lstat as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'foo'));
540+
expect(fsPromises.mkdir as jest.Mock).toBeCalledWith(joinPath(rootFilepath, 'foo', 'bar/'), { recursive: false });
535541
});
536542
});

‎test/unit/storage/SimpleResourceStore.test.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { DATA_TYPE_BINARY } from '../../../src/util/ContentTypes';
44
import { NotFoundHttpError } from '../../../src/util/errors/NotFoundHttpError';
55
import { Readable } from 'stream';
66
import { RepresentationMetadata } from '../../../src/ldp/representation/RepresentationMetadata';
7+
import { RuntimeConfig } from '../../../src/init/RuntimeConfig';
78
import { SimpleResourceStore } from '../../../src/storage/SimpleResourceStore';
89
import streamifyArray from 'streamify-array';
910

@@ -15,7 +16,7 @@ describe('A SimpleResourceStore', (): void => {
1516
const dataString = '<http://test.com/s> <http://test.com/p> <http://test.com/o>.';
1617

1718
beforeEach(async(): Promise<void> => {
18-
store = new SimpleResourceStore(base);
19+
store = new SimpleResourceStore(new RuntimeConfig({ base }));
1920

2021
representation = {
2122
data: streamifyArray([ dataString ]),

‎test/unit/storage/UrlContainerManager.test.ts

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,31 @@
1+
import { RuntimeConfig } from '../../../src/init/RuntimeConfig';
12
import { UrlContainerManager } from '../../../src/storage/UrlContainerManager';
23

34
describe('An UrlContainerManager', (): void => {
45
it('returns the parent URl for a single call.', async(): Promise<void> => {
5-
const manager = new UrlContainerManager('http://test.com/foo/');
6+
const manager = new UrlContainerManager(new RuntimeConfig({ base: 'http://test.com/foo/' }));
67
await expect(manager.getContainer({ path: 'http://test.com/foo/bar' }))
78
.resolves.toEqual({ path: 'http://test.com/foo/' });
89
await expect(manager.getContainer({ path: 'http://test.com/foo/bar/' }))
910
.resolves.toEqual({ path: 'http://test.com/foo/' });
1011
});
1112

1213
it('errors when getting the container of root.', async(): Promise<void> => {
13-
let manager = new UrlContainerManager('http://test.com/foo/');
14+
let manager = new UrlContainerManager(new RuntimeConfig({ base: 'http://test.com/foo/' }));
1415
await expect(manager.getContainer({ path: 'http://test.com/foo/' }))
1516
.rejects.toThrow('Root does not have a container.');
1617
await expect(manager.getContainer({ path: 'http://test.com/foo' }))
1718
.rejects.toThrow('Root does not have a container.');
1819

19-
manager = new UrlContainerManager('http://test.com/foo');
20+
manager = new UrlContainerManager(new RuntimeConfig({ base: 'http://test.com/foo' }));
2021
await expect(manager.getContainer({ path: 'http://test.com/foo/' }))
2122
.rejects.toThrow('Root does not have a container.');
2223
await expect(manager.getContainer({ path: 'http://test.com/foo' }))
2324
.rejects.toThrow('Root does not have a container.');
2425
});
2526

2627
it('errors when the root of an URl is reached that does not match the input root.', async(): Promise<void> => {
27-
const manager = new UrlContainerManager('http://test.com/foo/');
28+
const manager = new UrlContainerManager(new RuntimeConfig({ base: 'http://test.com/foo/' }));
2829
await expect(manager.getContainer({ path: 'http://test.com/' }))
2930
.rejects.toThrow('URL root reached.');
3031
});

0 commit comments

Comments
 (0)
Please sign in to comment.