mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
refactor: Remove RuntimeConfig in favor of config variables, Closes #106
This commit is contained in:
parent
b1991cb08a
commit
1dd140ab61
@ -10,6 +10,7 @@
|
|||||||
"files-scs:config/presets/ldp/request-parser.json",
|
"files-scs:config/presets/ldp/request-parser.json",
|
||||||
"files-scs:config/presets/setup.json",
|
"files-scs:config/presets/setup.json",
|
||||||
"files-scs:config/presets/storage.json",
|
"files-scs:config/presets/storage.json",
|
||||||
"files-scs:config/presets/storage_wrapper.json"
|
"files-scs:config/presets/storage_wrapper.json",
|
||||||
|
"files-scs:config/presets/cli-params.json"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
17
config/presets/cli-params.json
Normal file
17
config/presets/cli-params.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld",
|
||||||
|
"@graph": [
|
||||||
|
{
|
||||||
|
"@id": "urn:solid-server:default:variable:port",
|
||||||
|
"@type": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@id": "urn:solid-server:default:variable:base",
|
||||||
|
"@type": "Variable"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@id": "urn:solid-server:default:variable:rootFilePath",
|
||||||
|
"@type": "Variable"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -13,10 +13,11 @@
|
|||||||
"Setup:_aclManager": {
|
"Setup:_aclManager": {
|
||||||
"@id": "urn:solid-server:default:AclManager"
|
"@id": "urn:solid-server:default:AclManager"
|
||||||
},
|
},
|
||||||
"Setup:_runtimeConfig": {
|
"Setup:_base": {
|
||||||
"@id": "urn:solid-server:default:RuntimeConfig",
|
"@id": "urn:solid-server:default:variable:base"
|
||||||
"@type": "RuntimeConfig",
|
},
|
||||||
"RuntimeConfigData:_port": 3000
|
"Setup:_port": {
|
||||||
|
"@id": "urn:solid-server:default:variable:port"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
{
|
{
|
||||||
"@id": "urn:solid-server:default:ResourceStore",
|
"@id": "urn:solid-server:default:ResourceStore",
|
||||||
"@type": "InMemoryResourceStore",
|
"@type": "InMemoryResourceStore",
|
||||||
"InMemoryResourceStore:_runtimeConfig": {
|
"InMemoryResourceStore:_base": {
|
||||||
"@id": "urn:solid-server:default:RuntimeConfig"
|
"@id": "urn:solid-server:default:variable:base"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -72,8 +72,8 @@
|
|||||||
{
|
{
|
||||||
"@id": "urn:solid-server:default:UrlContainerManager",
|
"@id": "urn:solid-server:default:UrlContainerManager",
|
||||||
"@type": "UrlContainerManager",
|
"@type": "UrlContainerManager",
|
||||||
"UrlContainerManager:_runtimeConfig": {
|
"UrlContainerManager:_base": {
|
||||||
"@id": "urn:solid-server:default:RuntimeConfig"
|
"@id": "urn:solid-server:default:variable:base"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
1
index.ts
1
index.ts
@ -12,7 +12,6 @@ export * from './src/authorization/WebAclAuthorizer';
|
|||||||
|
|
||||||
// Init
|
// Init
|
||||||
export * from './src/init/CliRunner';
|
export * from './src/init/CliRunner';
|
||||||
export * from './src/init/RuntimeConfig';
|
|
||||||
export * from './src/init/Setup';
|
export * from './src/init/Setup';
|
||||||
|
|
||||||
// LDP/HTTP
|
// LDP/HTTP
|
||||||
|
6
package-lock.json
generated
6
package-lock.json
generated
@ -2519,9 +2519,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"componentsjs": {
|
"componentsjs": {
|
||||||
"version": "3.4.2",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/componentsjs/-/componentsjs-3.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/componentsjs/-/componentsjs-3.6.0.tgz",
|
||||||
"integrity": "sha512-ICaSvvxY/CvWwFt0lLsoRL/DHY09akoI6x9WakQQ9g4GYHVaZumWSAdOrzM/htjGpBumpVh1C/4hk0/ghh9jXA==",
|
"integrity": "sha512-G3lMrIbE7iiZpERoPXnxM0aDopq9q1s1C5aIUrnHW3rcRDa3kcCytc4ASt5aFRjNiwBubivcMfJsvF2ihXg7jQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/lodash": "^4.14.56",
|
"@types/lodash": "^4.14.56",
|
||||||
"@types/minimist": "^1.2.0",
|
"@types/minimist": "^1.2.0",
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
"prepare": "npm run build",
|
"prepare": "npm run build",
|
||||||
"start": "node ./bin/server.js -p 3000",
|
"start": "node ./bin/server.js -p 3000",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"validate": "componentsjs-compile-config urn:solid-server:default -c config/config-default.json > /dev/null",
|
"validate": "componentsjs-compile-config urn:solid-server:default -c config/config-default.json -f > /dev/null",
|
||||||
"version": "manual-git-changelog onversion",
|
"version": "manual-git-changelog onversion",
|
||||||
"watch": "nodemon --watch \"src/**/*.js\" --watch \"bin/**/*.js\" --exec npm start"
|
"watch": "nodemon --watch \"src/**/*.js\" --watch \"bin/**/*.js\" --exec npm start"
|
||||||
},
|
},
|
||||||
@ -76,7 +76,7 @@
|
|||||||
"@types/yargs": "^15.0.5",
|
"@types/yargs": "^15.0.5",
|
||||||
"arrayify-stream": "^1.0.0",
|
"arrayify-stream": "^1.0.0",
|
||||||
"async-lock": "^1.2.4",
|
"async-lock": "^1.2.4",
|
||||||
"componentsjs": "^3.4.2",
|
"componentsjs": "^3.6.0",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"mime-types": "^2.1.27",
|
"mime-types": "^2.1.27",
|
||||||
|
@ -2,7 +2,6 @@ import * as Path from 'path';
|
|||||||
import { ReadStream, WriteStream } from 'tty';
|
import { ReadStream, WriteStream } from 'tty';
|
||||||
import { Loader, LoaderProperties } from 'componentsjs';
|
import { Loader, LoaderProperties } from 'componentsjs';
|
||||||
import yargs from 'yargs';
|
import yargs from 'yargs';
|
||||||
import { RuntimeConfig } from './RuntimeConfig';
|
|
||||||
import { Setup } from './Setup';
|
import { Setup } from './Setup';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,12 +22,12 @@ export const runCustom = function(
|
|||||||
const { argv } = yargs
|
const { argv } = yargs
|
||||||
.usage('node ./bin/server.js [args]')
|
.usage('node ./bin/server.js [args]')
|
||||||
.options({
|
.options({
|
||||||
port: { type: 'number', alias: 'p' },
|
port: { type: 'number', alias: 'p', default: 3000 },
|
||||||
config: { type: 'string', alias: 'c' },
|
config: { type: 'string', alias: 'c' },
|
||||||
})
|
})
|
||||||
.help();
|
.help();
|
||||||
|
|
||||||
new Promise<RuntimeConfig>(async(resolve): Promise<void> => {
|
(async(): Promise<string> => {
|
||||||
// Load provided or default config file
|
// Load provided or default config file
|
||||||
const configPath = argv.config ?
|
const configPath = argv.config ?
|
||||||
Path.join(process.cwd(), argv.config) :
|
Path.join(process.cwd(), argv.config) :
|
||||||
@ -38,10 +37,16 @@ export const runCustom = function(
|
|||||||
const loader = new Loader(properties);
|
const loader = new Loader(properties);
|
||||||
await loader.registerAvailableModuleResources();
|
await loader.registerAvailableModuleResources();
|
||||||
const setup: Setup = await loader
|
const setup: Setup = await loader
|
||||||
.instantiateFromUrl('urn:solid-server:default', configPath);
|
.instantiateFromUrl('urn:solid-server:default', configPath, undefined, {
|
||||||
resolve(await setup.setup({ port: argv.port }));
|
variables: {
|
||||||
}).then((runtimeConfig: RuntimeConfig): void => {
|
'urn:solid-server:default:variable:port': argv.port,
|
||||||
stdout.write(`Running at ${runtimeConfig.base}\n`);
|
'urn:solid-server:default:variable:base': `http://localhost:${argv.port}/`,
|
||||||
|
'urn:solid-server:default:variable:rootFilePath': process.cwd(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return await setup.setup();
|
||||||
|
})().then((base: string): void => {
|
||||||
|
stdout.write(`Running at ${base}\n`);
|
||||||
}).catch((error): void => {
|
}).catch((error): void => {
|
||||||
stderr.write(`${error}\n`);
|
stderr.write(`${error}\n`);
|
||||||
});
|
});
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
import { ensureTrailingSlash } from '../util/Util';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class holds all configuration options that can be defined by the user via the command line.
|
|
||||||
*
|
|
||||||
* Concretely, this contains data that is only relevant *after* dependency injection.
|
|
||||||
*/
|
|
||||||
export class RuntimeConfig implements RuntimeConfigData {
|
|
||||||
private pport!: number;
|
|
||||||
private pbase!: string;
|
|
||||||
private prootFilepath!: string;
|
|
||||||
|
|
||||||
public constructor(data: RuntimeConfigData = {}) {
|
|
||||||
this.reset(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public reset(data: RuntimeConfigData): void {
|
|
||||||
this.pport = data.port ?? 3000;
|
|
||||||
this.pbase = ensureTrailingSlash(data.base ?? `http://localhost:${this.port}/`);
|
|
||||||
this.prootFilepath = data.rootFilepath ?? process.cwd();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The returned URL is ensured to have a trailing slash.
|
|
||||||
*/
|
|
||||||
public get base(): string {
|
|
||||||
return this.pbase;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get port(): number {
|
|
||||||
return this.pport;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get rootFilepath(): string {
|
|
||||||
return this.prootFilepath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RuntimeConfigData {
|
|
||||||
port?: number;
|
|
||||||
base?: string;
|
|
||||||
rootFilepath?: string;
|
|
||||||
}
|
|
@ -3,7 +3,6 @@ import { AclManager } from '../authorization/AclManager';
|
|||||||
import { ExpressHttpServer } from '../server/ExpressHttpServer';
|
import { ExpressHttpServer } from '../server/ExpressHttpServer';
|
||||||
import { ResourceStore } from '../storage/ResourceStore';
|
import { ResourceStore } from '../storage/ResourceStore';
|
||||||
import { TEXT_TURTLE } from '../util/ContentTypes';
|
import { TEXT_TURTLE } from '../util/ContentTypes';
|
||||||
import { RuntimeConfig, RuntimeConfigData } from './RuntimeConfig';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invokes all logic to setup a server.
|
* Invokes all logic to setup a server.
|
||||||
@ -12,27 +11,27 @@ export class Setup {
|
|||||||
private readonly httpServer: ExpressHttpServer;
|
private readonly httpServer: ExpressHttpServer;
|
||||||
private readonly store: ResourceStore;
|
private readonly store: ResourceStore;
|
||||||
private readonly aclManager: AclManager;
|
private readonly aclManager: AclManager;
|
||||||
private readonly runtimeConfig: RuntimeConfig;
|
private readonly base: string;
|
||||||
|
private readonly port: number;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
httpServer: ExpressHttpServer,
|
httpServer: ExpressHttpServer,
|
||||||
store: ResourceStore,
|
store: ResourceStore,
|
||||||
aclManager: AclManager,
|
aclManager: AclManager,
|
||||||
runtimeConfig: RuntimeConfig,
|
base: string,
|
||||||
|
port: number,
|
||||||
) {
|
) {
|
||||||
this.httpServer = httpServer;
|
this.httpServer = httpServer;
|
||||||
this.store = store;
|
this.store = store;
|
||||||
this.aclManager = aclManager;
|
this.aclManager = aclManager;
|
||||||
this.runtimeConfig = runtimeConfig;
|
this.base = base;
|
||||||
|
this.port = port;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up a server at the given port and base URL.
|
* Set up a server.
|
||||||
* @param data - Runtime config data.
|
|
||||||
*/
|
*/
|
||||||
public async setup(data: RuntimeConfigData = {}): Promise<RuntimeConfig> {
|
public async setup(): Promise<string> {
|
||||||
this.runtimeConfig.reset(data);
|
|
||||||
|
|
||||||
// Set up acl so everything can still be done by default
|
// Set up acl so everything can still be done by default
|
||||||
// Note that this will need to be adapted to go through all the correct channels later on
|
// Note that this will need to be adapted to go through all the correct channels later on
|
||||||
const aclSetup = async(): Promise<void> => {
|
const aclSetup = async(): Promise<void> => {
|
||||||
@ -47,10 +46,10 @@ export class Setup {
|
|||||||
acl:mode acl:Append;
|
acl:mode acl:Append;
|
||||||
acl:mode acl:Delete;
|
acl:mode acl:Delete;
|
||||||
acl:mode acl:Control;
|
acl:mode acl:Control;
|
||||||
acl:accessTo <${this.runtimeConfig.base}>;
|
acl:accessTo <${this.base}>;
|
||||||
acl:default <${this.runtimeConfig.base}>.`;
|
acl:default <${this.base}>.`;
|
||||||
await this.store.setRepresentation(
|
await this.store.setRepresentation(
|
||||||
await this.aclManager.getAcl({ path: this.runtimeConfig.base }),
|
await this.aclManager.getAcl({ path: this.base }),
|
||||||
{
|
{
|
||||||
binary: true,
|
binary: true,
|
||||||
data: streamifyArray([ acl ]),
|
data: streamifyArray([ acl ]),
|
||||||
@ -64,8 +63,8 @@ export class Setup {
|
|||||||
};
|
};
|
||||||
await aclSetup();
|
await aclSetup();
|
||||||
|
|
||||||
this.httpServer.listen(this.runtimeConfig.port);
|
this.httpServer.listen(this.port);
|
||||||
|
|
||||||
return this.runtimeConfig;
|
return this.base;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { posix } from 'path';
|
import { posix } from 'path';
|
||||||
import { types } from 'mime-types';
|
import { types } from 'mime-types';
|
||||||
import { RuntimeConfig } from '../init/RuntimeConfig';
|
|
||||||
import { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
import { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
||||||
import { APPLICATION_OCTET_STREAM, TEXT_TURTLE } from '../util/ContentTypes';
|
import { APPLICATION_OCTET_STREAM, TEXT_TURTLE } from '../util/ContentTypes';
|
||||||
import { ConflictHttpError } from '../util/errors/ConflictHttpError';
|
import { ConflictHttpError } from '../util/errors/ConflictHttpError';
|
||||||
@ -24,22 +23,22 @@ export interface ResourcePath {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class ExtensionBasedMapper implements FileIdentifierMapper {
|
export class ExtensionBasedMapper implements FileIdentifierMapper {
|
||||||
private readonly runtimeConfig: RuntimeConfig;
|
private readonly base: string;
|
||||||
|
private readonly prootFilepath: string;
|
||||||
private readonly types: Record<string, any>;
|
private readonly types: Record<string, any>;
|
||||||
|
|
||||||
public constructor(runtimeConfig: RuntimeConfig, overrideTypes = { acl: TEXT_TURTLE, metadata: TEXT_TURTLE }) {
|
public constructor(base: string, rootFilepath: string, overrideTypes = { acl: TEXT_TURTLE, metadata: TEXT_TURTLE }) {
|
||||||
this.runtimeConfig = runtimeConfig;
|
this.base = base;
|
||||||
|
this.prootFilepath = rootFilepath;
|
||||||
this.types = { ...types, ...overrideTypes };
|
this.types = { ...types, ...overrideTypes };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using getters because the values of runtimeConfig get filled in at runtime (so they are still empty at
|
|
||||||
// construction time until issue #106 gets resolved.)
|
|
||||||
public get baseRequestURI(): string {
|
public get baseRequestURI(): string {
|
||||||
return trimTrailingSlashes(this.runtimeConfig.base);
|
return trimTrailingSlashes(this.base);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get rootFilepath(): string {
|
public get rootFilepath(): string {
|
||||||
return trimTrailingSlashes(this.runtimeConfig.rootFilepath);
|
return trimTrailingSlashes(this.prootFilepath);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { PassThrough } from 'stream';
|
import { PassThrough } from 'stream';
|
||||||
import arrayifyStream from 'arrayify-stream';
|
import arrayifyStream from 'arrayify-stream';
|
||||||
import streamifyArray from 'streamify-array';
|
import streamifyArray from 'streamify-array';
|
||||||
import { RuntimeConfig } from '../init/RuntimeConfig';
|
|
||||||
import { Representation } from '../ldp/representation/Representation';
|
import { Representation } from '../ldp/representation/Representation';
|
||||||
import { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
import { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
||||||
import { TEXT_TURTLE } from '../util/ContentTypes';
|
import { TEXT_TURTLE } from '../util/ContentTypes';
|
||||||
@ -15,15 +14,15 @@ import { ResourceStore } from './ResourceStore';
|
|||||||
*/
|
*/
|
||||||
export class InMemoryResourceStore implements ResourceStore {
|
export class InMemoryResourceStore implements ResourceStore {
|
||||||
private readonly store: { [id: string]: Representation };
|
private readonly store: { [id: string]: Representation };
|
||||||
private readonly runtimeConfig: RuntimeConfig;
|
private readonly base: string;
|
||||||
private index = 0;
|
private index = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param runtimeConfig - Config containing base that will be stripped of all incoming URIs
|
* @param base - Base that will be stripped of all incoming URIs
|
||||||
* and added to all outgoing ones to find the relative path.
|
* and added to all outgoing ones to find the relative path.
|
||||||
*/
|
*/
|
||||||
public constructor(runtimeConfig: RuntimeConfig) {
|
public constructor(base: string) {
|
||||||
this.runtimeConfig = runtimeConfig;
|
this.base = ensureTrailingSlash(base);
|
||||||
|
|
||||||
this.store = {
|
this.store = {
|
||||||
// Default root entry (what you get when the identifier is equal to the base)
|
// Default root entry (what you get when the identifier is equal to the base)
|
||||||
@ -105,8 +104,8 @@ export class InMemoryResourceStore implements ResourceStore {
|
|||||||
* @returns A string representing the relative path.
|
* @returns A string representing the relative path.
|
||||||
*/
|
*/
|
||||||
private parseIdentifier(identifier: ResourceIdentifier): string {
|
private parseIdentifier(identifier: ResourceIdentifier): string {
|
||||||
const path = identifier.path.slice(this.runtimeConfig.base.length);
|
const path = identifier.path.slice(this.base.length);
|
||||||
if (!identifier.path.startsWith(this.runtimeConfig.base)) {
|
if (!identifier.path.startsWith(this.base)) {
|
||||||
throw new NotFoundHttpError();
|
throw new NotFoundHttpError();
|
||||||
}
|
}
|
||||||
return path;
|
return path;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { RuntimeConfig } from '../init/RuntimeConfig';
|
|
||||||
import { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
import { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
||||||
import { ensureTrailingSlash } from '../util/Util';
|
import { ensureTrailingSlash } from '../util/Util';
|
||||||
import { ContainerManager } from './ContainerManager';
|
import { ContainerManager } from './ContainerManager';
|
||||||
@ -7,15 +6,15 @@ import { ContainerManager } from './ContainerManager';
|
|||||||
* Determines containers based on URL decomposition.
|
* Determines containers based on URL decomposition.
|
||||||
*/
|
*/
|
||||||
export class UrlContainerManager implements ContainerManager {
|
export class UrlContainerManager implements ContainerManager {
|
||||||
private readonly runtimeConfig: RuntimeConfig;
|
private readonly base: string;
|
||||||
|
|
||||||
public constructor(runtimeConfig: RuntimeConfig) {
|
public constructor(base: string) {
|
||||||
this.runtimeConfig = runtimeConfig;
|
this.base = base;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getContainer(id: ResourceIdentifier): Promise<ResourceIdentifier> {
|
public async getContainer(id: ResourceIdentifier): Promise<ResourceIdentifier> {
|
||||||
const path = this.canonicalUrl(id.path);
|
const path = this.canonicalUrl(id.path);
|
||||||
if (this.runtimeConfig.base === path) {
|
if (this.base === path) {
|
||||||
throw new Error('Root does not have a container.');
|
throw new Error('Root does not have a container.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ import {
|
|||||||
ResourceStore,
|
ResourceStore,
|
||||||
UnsecureWebIdExtractor,
|
UnsecureWebIdExtractor,
|
||||||
QuadToRdfConverter,
|
QuadToRdfConverter,
|
||||||
RuntimeConfig,
|
|
||||||
} from '../../index';
|
} from '../../index';
|
||||||
import { ServerConfig } from './ServerConfig';
|
import { ServerConfig } from './ServerConfig';
|
||||||
import {
|
import {
|
||||||
@ -27,13 +26,13 @@ import {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export class AuthenticatedFileResourceStoreConfig implements ServerConfig {
|
export class AuthenticatedFileResourceStoreConfig implements ServerConfig {
|
||||||
private readonly runtimeConfig: RuntimeConfig;
|
public base: string;
|
||||||
public store: ResourceStore;
|
public store: ResourceStore;
|
||||||
|
|
||||||
public constructor(runtimeConfig: RuntimeConfig) {
|
public constructor(base: string, rootFilepath: string) {
|
||||||
this.runtimeConfig = runtimeConfig;
|
this.base = base;
|
||||||
this.store = getConvertingStore(
|
this.store = getConvertingStore(
|
||||||
getFileResourceStore(runtimeConfig),
|
getFileResourceStore(base, rootFilepath),
|
||||||
[ new QuadToRdfConverter(),
|
[ new QuadToRdfConverter(),
|
||||||
new RdfToQuadConverter() ],
|
new RdfToQuadConverter() ],
|
||||||
);
|
);
|
||||||
@ -50,7 +49,7 @@ export class AuthenticatedFileResourceStoreConfig implements ServerConfig {
|
|||||||
const operationHandler = getOperationHandler(this.store);
|
const operationHandler = getOperationHandler(this.store);
|
||||||
|
|
||||||
const responseWriter = new BasicResponseWriter();
|
const responseWriter = new BasicResponseWriter();
|
||||||
const authorizer = getWebAclAuthorizer(this.store, this.runtimeConfig.base);
|
const authorizer = getWebAclAuthorizer(this.store, this.base);
|
||||||
|
|
||||||
const handler = new AuthenticatedLdpHandler({
|
const handler = new AuthenticatedLdpHandler({
|
||||||
requestParser,
|
requestParser,
|
||||||
|
@ -9,7 +9,6 @@ import {
|
|||||||
RawBodyParser,
|
RawBodyParser,
|
||||||
RdfToQuadConverter,
|
RdfToQuadConverter,
|
||||||
ResourceStore,
|
ResourceStore,
|
||||||
RuntimeConfig,
|
|
||||||
UnsecureWebIdExtractor,
|
UnsecureWebIdExtractor,
|
||||||
} from '../../index';
|
} from '../../index';
|
||||||
import { ServerConfig } from './ServerConfig';
|
import { ServerConfig } from './ServerConfig';
|
||||||
@ -25,9 +24,9 @@ import { getFileResourceStore, getOperationHandler, getConvertingStore, getBasic
|
|||||||
export class FileResourceStoreConfig implements ServerConfig {
|
export class FileResourceStoreConfig implements ServerConfig {
|
||||||
public store: ResourceStore;
|
public store: ResourceStore;
|
||||||
|
|
||||||
public constructor(runtimeConfig: RuntimeConfig) {
|
public constructor(base: string, rootFilepath: string) {
|
||||||
this.store = getConvertingStore(
|
this.store = getConvertingStore(
|
||||||
getFileResourceStore(runtimeConfig),
|
getFileResourceStore(base, rootFilepath),
|
||||||
[ new QuadToRdfConverter(), new RdfToQuadConverter() ],
|
[ new QuadToRdfConverter(), new RdfToQuadConverter() ],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,6 @@ import {
|
|||||||
RepresentationConvertingStore,
|
RepresentationConvertingStore,
|
||||||
ResourceStore,
|
ResourceStore,
|
||||||
ResponseDescription,
|
ResponseDescription,
|
||||||
RuntimeConfig,
|
|
||||||
SingleThreadedResourceLocker,
|
SingleThreadedResourceLocker,
|
||||||
SparqlUpdatePatchHandler,
|
SparqlUpdatePatchHandler,
|
||||||
UrlBasedAclManager,
|
UrlBasedAclManager,
|
||||||
@ -32,26 +31,24 @@ import {
|
|||||||
} from '../../index';
|
} from '../../index';
|
||||||
import { ExtensionBasedMapper } from '../../src/storage/ExtensionBasedMapper';
|
import { ExtensionBasedMapper } from '../../src/storage/ExtensionBasedMapper';
|
||||||
|
|
||||||
const BASE = 'http://test.com';
|
export const BASE = 'http://test.com';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a RuntimeConfig with its rootFilePath set based on the given subfolder.
|
* Creates a RuntimeConfig with its rootFilePath set based on the given subfolder.
|
||||||
* @param subfolder - Folder to use in the global testData folder.
|
* @param subfolder - Folder to use in the global testData folder.
|
||||||
*/
|
*/
|
||||||
export const getRuntimeConfig = (subfolder: string): RuntimeConfig => new RuntimeConfig({
|
export const getRootFilePath = (subfolder: string): string => join(__dirname, '../testData', subfolder);
|
||||||
base: BASE,
|
|
||||||
rootFilepath: join(__dirname, '../testData', subfolder),
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gives a file resource store based on (default) runtime config.
|
* Gives a file resource store based on (default) runtime config.
|
||||||
* @param runtimeConfig - Optional runtime config.
|
* @param base - Base URL.
|
||||||
|
* @param rootFilepath - The root file path.
|
||||||
*
|
*
|
||||||
* @returns The file resource store.
|
* @returns The file resource store.
|
||||||
*/
|
*/
|
||||||
export const getFileResourceStore = (runtimeConfig: RuntimeConfig): FileResourceStore =>
|
export const getFileResourceStore = (base: string, rootFilepath: string): FileResourceStore =>
|
||||||
new FileResourceStore(
|
new FileResourceStore(
|
||||||
new ExtensionBasedMapper(runtimeConfig),
|
new ExtensionBasedMapper(base, rootFilepath),
|
||||||
new InteractionController(),
|
new InteractionController(),
|
||||||
new MetadataController(),
|
new MetadataController(),
|
||||||
);
|
);
|
||||||
@ -63,7 +60,7 @@ export const getFileResourceStore = (runtimeConfig: RuntimeConfig): FileResource
|
|||||||
* @returns The in memory resource store.
|
* @returns The in memory resource store.
|
||||||
*/
|
*/
|
||||||
export const getInMemoryResourceStore = (base = BASE): InMemoryResourceStore =>
|
export const getInMemoryResourceStore = (base = BASE): InMemoryResourceStore =>
|
||||||
new InMemoryResourceStore(new RuntimeConfig({ base }));
|
new InMemoryResourceStore(base);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gives a converting store given some converters.
|
* Gives a converting store given some converters.
|
||||||
@ -138,6 +135,6 @@ export const getBasicRequestParser = (bodyParsers: BodyParser[] = []): BasicRequ
|
|||||||
*/
|
*/
|
||||||
export const getWebAclAuthorizer =
|
export const getWebAclAuthorizer =
|
||||||
(store: ResourceStore, base = BASE, aclManager = new UrlBasedAclManager()): WebAclAuthorizer => {
|
(store: ResourceStore, base = BASE, aclManager = new UrlBasedAclManager()): WebAclAuthorizer => {
|
||||||
const containerManager = new UrlContainerManager(new RuntimeConfig({ base }));
|
const containerManager = new UrlContainerManager(base);
|
||||||
return new WebAclAuthorizer(aclManager, containerManager, store);
|
return new WebAclAuthorizer(aclManager, containerManager, store);
|
||||||
};
|
};
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { copyFileSync, mkdirSync } from 'fs';
|
import { copyFileSync, mkdirSync } from 'fs';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import * as rimraf from 'rimraf';
|
import * as rimraf from 'rimraf';
|
||||||
import { HttpHandler, ResourceStore, RuntimeConfig } from '../../index';
|
import { HttpHandler, ResourceStore } from '../../index';
|
||||||
|
import { ensureTrailingSlash } from '../../src/util/Util';
|
||||||
import { AuthenticatedFileResourceStoreConfig } from '../configs/AuthenticatedFileResourceStoreConfig';
|
import { AuthenticatedFileResourceStoreConfig } from '../configs/AuthenticatedFileResourceStoreConfig';
|
||||||
import { getRuntimeConfig } from '../configs/Util';
|
import { BASE, getRootFilePath } from '../configs/Util';
|
||||||
import { AclTestHelper, FileTestHelper } from '../util/TestHelpers';
|
import { AclTestHelper, FileTestHelper } from '../util/TestHelpers';
|
||||||
|
|
||||||
describe('A server using a AuthenticatedFileResourceStore', (): void => {
|
describe('A server using a AuthenticatedFileResourceStore', (): void => {
|
||||||
@ -12,24 +13,23 @@ describe('A server using a AuthenticatedFileResourceStore', (): void => {
|
|||||||
let store: ResourceStore;
|
let store: ResourceStore;
|
||||||
let aclHelper: AclTestHelper;
|
let aclHelper: AclTestHelper;
|
||||||
let fileHelper: FileTestHelper;
|
let fileHelper: FileTestHelper;
|
||||||
let runtimeConfig: RuntimeConfig;
|
let rootFilePath: string;
|
||||||
|
|
||||||
beforeAll(async(): Promise<void> => {
|
beforeAll(async(): Promise<void> => {
|
||||||
runtimeConfig = getRuntimeConfig('AuthenticatedFileResourceStore');
|
rootFilePath = getRootFilePath('AuthenticatedFileResourceStore');
|
||||||
config = new AuthenticatedFileResourceStoreConfig(runtimeConfig);
|
config = new AuthenticatedFileResourceStoreConfig(BASE, rootFilePath);
|
||||||
const { base, rootFilepath } = runtimeConfig;
|
|
||||||
handler = config.getHttpHandler();
|
handler = config.getHttpHandler();
|
||||||
({ store } = config);
|
({ store } = config);
|
||||||
aclHelper = new AclTestHelper(store, base);
|
aclHelper = new AclTestHelper(store, ensureTrailingSlash(BASE));
|
||||||
fileHelper = new FileTestHelper(handler, new URL('http://test.com/'));
|
fileHelper = new FileTestHelper(handler, new URL(ensureTrailingSlash(BASE)));
|
||||||
|
|
||||||
// Make sure the root directory exists
|
// Make sure the root directory exists
|
||||||
mkdirSync(rootFilepath, { recursive: true });
|
mkdirSync(rootFilePath, { recursive: true });
|
||||||
copyFileSync(join(__dirname, '../assets/permanent.txt'), `${rootFilepath}/permanent.txt`);
|
copyFileSync(join(__dirname, '../assets/permanent.txt'), `${rootFilePath}/permanent.txt`);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async(): Promise<void> => {
|
afterAll(async(): Promise<void> => {
|
||||||
rimraf.sync(runtimeConfig.rootFilepath, { glob: false });
|
rimraf.sync(rootFilePath, { glob: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('with acl', (): void => {
|
describe('with acl', (): void => {
|
||||||
|
@ -1,26 +1,25 @@
|
|||||||
import * as rimraf from 'rimraf';
|
import * as rimraf from 'rimraf';
|
||||||
import { RuntimeConfig } from '../../src/init/RuntimeConfig';
|
|
||||||
import { HttpHandler } from '../../src/server/HttpHandler';
|
import { HttpHandler } from '../../src/server/HttpHandler';
|
||||||
import { FileResourceStoreConfig } from '../configs/FileResourceStoreConfig';
|
import { FileResourceStoreConfig } from '../configs/FileResourceStoreConfig';
|
||||||
import { getRuntimeConfig } from '../configs/Util';
|
import { BASE, getRootFilePath } from '../configs/Util';
|
||||||
import { FileTestHelper } from '../util/TestHelpers';
|
import { FileTestHelper } from '../util/TestHelpers';
|
||||||
|
|
||||||
describe('A server using a FileResourceStore', (): void => {
|
describe('A server using a FileResourceStore', (): void => {
|
||||||
describe('without acl', (): void => {
|
describe('without acl', (): void => {
|
||||||
|
let rootFilePath: string;
|
||||||
let config: FileResourceStoreConfig;
|
let config: FileResourceStoreConfig;
|
||||||
let handler: HttpHandler;
|
let handler: HttpHandler;
|
||||||
let fileHelper: FileTestHelper;
|
let fileHelper: FileTestHelper;
|
||||||
let runtimeConfig: RuntimeConfig;
|
|
||||||
|
|
||||||
beforeAll(async(): Promise<void> => {
|
beforeAll(async(): Promise<void> => {
|
||||||
runtimeConfig = getRuntimeConfig('FileResourceStore');
|
rootFilePath = getRootFilePath('FileResourceStore');
|
||||||
config = new FileResourceStoreConfig(runtimeConfig);
|
config = new FileResourceStoreConfig(BASE, rootFilePath);
|
||||||
handler = config.getHttpHandler();
|
handler = config.getHttpHandler();
|
||||||
fileHelper = new FileTestHelper(handler, new URL(runtimeConfig.base));
|
fileHelper = new FileTestHelper(handler, new URL(BASE));
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async(): Promise<void> => {
|
afterAll(async(): Promise<void> => {
|
||||||
rimraf.sync(runtimeConfig.rootFilepath, { glob: false });
|
rimraf.sync(rootFilePath, { glob: false });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can add a file to the store, read it and delete it.', async():
|
it('can add a file to the store, read it and delete it.', async():
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
import { RuntimeConfig } from '../../../src/init/RuntimeConfig';
|
|
||||||
|
|
||||||
describe('RuntimeConfig', (): void => {
|
|
||||||
it('handles undefined args.', async(): Promise<void> => {
|
|
||||||
const config = new RuntimeConfig();
|
|
||||||
expect(config.port).toEqual(3000);
|
|
||||||
expect(config.base).toEqual('http://localhost:3000/');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles empty args.', async(): Promise<void> => {
|
|
||||||
const config = new RuntimeConfig({});
|
|
||||||
expect(config.port).toEqual(3000);
|
|
||||||
expect(config.base).toEqual('http://localhost:3000/');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles args with port.', async(): Promise<void> => {
|
|
||||||
const config = new RuntimeConfig({ port: 1234 });
|
|
||||||
expect(config.port).toEqual(1234);
|
|
||||||
expect(config.base).toEqual('http://localhost:1234/');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles args with base.', async(): Promise<void> => {
|
|
||||||
const config = new RuntimeConfig({ base: 'http://example.org/' });
|
|
||||||
expect(config.port).toEqual(3000);
|
|
||||||
expect(config.base).toEqual('http://example.org/');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles args with port and base.', async(): Promise<void> => {
|
|
||||||
const config = new RuntimeConfig({ port: 1234, base: 'http://example.org/' });
|
|
||||||
expect(config.port).toEqual(1234);
|
|
||||||
expect(config.base).toEqual('http://example.org/');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles resetting data.', async(): Promise<void> => {
|
|
||||||
const config = new RuntimeConfig({});
|
|
||||||
expect(config.port).toEqual(3000);
|
|
||||||
expect(config.base).toEqual('http://localhost:3000/');
|
|
||||||
|
|
||||||
config.reset({ port: 1234, base: 'http://example.org/' });
|
|
||||||
expect(config.port).toEqual(1234);
|
|
||||||
expect(config.base).toEqual('http://example.org/');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('ensures trailing slash in base.', async(): Promise<void> => {
|
|
||||||
const config = new RuntimeConfig({ base: 'http://example.org' });
|
|
||||||
expect(config.base).toEqual('http://example.org/');
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,4 +1,3 @@
|
|||||||
import { RuntimeConfig } from '../../../src/init/RuntimeConfig';
|
|
||||||
import { Setup } from '../../../src/init/Setup';
|
import { Setup } from '../../../src/init/Setup';
|
||||||
|
|
||||||
describe('Setup', (): void => {
|
describe('Setup', (): void => {
|
||||||
@ -16,7 +15,7 @@ describe('Setup', (): void => {
|
|||||||
httpServer = {
|
httpServer = {
|
||||||
listen: jest.fn(),
|
listen: jest.fn(),
|
||||||
};
|
};
|
||||||
setup = new Setup(httpServer, store, aclManager, new RuntimeConfig());
|
setup = new Setup(httpServer, store, aclManager, 'http://localhost:3000/', 3000);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('starts an HTTP server.', async(): Promise<void> => {
|
it('starts an HTTP server.', async(): Promise<void> => {
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { RuntimeConfig } from '../../../src/init/RuntimeConfig';
|
|
||||||
import { ExtensionBasedMapper } from '../../../src/storage/ExtensionBasedMapper';
|
import { ExtensionBasedMapper } from '../../../src/storage/ExtensionBasedMapper';
|
||||||
|
|
||||||
describe('An ExtensionBasedMapper', (): void => {
|
describe('An ExtensionBasedMapper', (): void => {
|
||||||
const base = 'http://test.com/';
|
const base = 'http://test.com/';
|
||||||
const rootFilepath = 'uploads/';
|
const rootFilepath = 'uploads/';
|
||||||
const resourceMapper = new ExtensionBasedMapper(new RuntimeConfig({ base, rootFilepath }));
|
const resourceMapper = new ExtensionBasedMapper(base, rootFilepath);
|
||||||
|
|
||||||
it('returns the correct url of a file.', async(): Promise<void> => {
|
it('returns the correct url of a file.', async(): Promise<void> => {
|
||||||
let result = resourceMapper.mapFilePathToUrl(`${rootFilepath}test.txt`);
|
let result = resourceMapper.mapFilePathToUrl(`${rootFilepath}test.txt`);
|
||||||
|
@ -5,7 +5,6 @@ import { literal, namedNode, quad as quadRDF, triple } from '@rdfjs/data-model';
|
|||||||
import arrayifyStream from 'arrayify-stream';
|
import arrayifyStream from 'arrayify-stream';
|
||||||
import { DataFactory } from 'n3';
|
import { DataFactory } from 'n3';
|
||||||
import streamifyArray from 'streamify-array';
|
import streamifyArray from 'streamify-array';
|
||||||
import { RuntimeConfig } from '../../../src/init/RuntimeConfig';
|
|
||||||
import { Representation } from '../../../src/ldp/representation/Representation';
|
import { Representation } from '../../../src/ldp/representation/Representation';
|
||||||
import { RepresentationMetadata } from '../../../src/ldp/representation/RepresentationMetadata';
|
import { RepresentationMetadata } from '../../../src/ldp/representation/RepresentationMetadata';
|
||||||
import { ExtensionBasedMapper } from '../../../src/storage/ExtensionBasedMapper';
|
import { ExtensionBasedMapper } from '../../../src/storage/ExtensionBasedMapper';
|
||||||
@ -55,7 +54,7 @@ describe('A FileResourceStore', (): void => {
|
|||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
|
|
||||||
store = new FileResourceStore(
|
store = new FileResourceStore(
|
||||||
new ExtensionBasedMapper(new RuntimeConfig({ base, rootFilepath })),
|
new ExtensionBasedMapper(base, rootFilepath),
|
||||||
new InteractionController(),
|
new InteractionController(),
|
||||||
new MetadataController(),
|
new MetadataController(),
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Readable } from 'stream';
|
import { Readable } from 'stream';
|
||||||
import streamifyArray from 'streamify-array';
|
import streamifyArray from 'streamify-array';
|
||||||
import { RuntimeConfig } from '../../../src/init/RuntimeConfig';
|
|
||||||
import { Representation } from '../../../src/ldp/representation/Representation';
|
import { Representation } from '../../../src/ldp/representation/Representation';
|
||||||
import { RepresentationMetadata } from '../../../src/ldp/representation/RepresentationMetadata';
|
import { RepresentationMetadata } from '../../../src/ldp/representation/RepresentationMetadata';
|
||||||
import { InMemoryResourceStore } from '../../../src/storage/InMemoryResourceStore';
|
import { InMemoryResourceStore } from '../../../src/storage/InMemoryResourceStore';
|
||||||
@ -15,7 +14,7 @@ describe('A InMemoryResourceStore', (): void => {
|
|||||||
const dataString = '<http://test.com/s> <http://test.com/p> <http://test.com/o>.';
|
const dataString = '<http://test.com/s> <http://test.com/p> <http://test.com/o>.';
|
||||||
|
|
||||||
beforeEach(async(): Promise<void> => {
|
beforeEach(async(): Promise<void> => {
|
||||||
store = new InMemoryResourceStore(new RuntimeConfig({ base }));
|
store = new InMemoryResourceStore(base);
|
||||||
|
|
||||||
representation = {
|
representation = {
|
||||||
binary: true,
|
binary: true,
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import { RuntimeConfig } from '../../../src/init/RuntimeConfig';
|
|
||||||
import { UrlContainerManager } from '../../../src/storage/UrlContainerManager';
|
import { UrlContainerManager } from '../../../src/storage/UrlContainerManager';
|
||||||
|
|
||||||
describe('An UrlContainerManager', (): void => {
|
describe('An UrlContainerManager', (): void => {
|
||||||
it('returns the parent URl for a single call.', async(): Promise<void> => {
|
it('returns the parent URl for a single call.', async(): Promise<void> => {
|
||||||
const manager = new UrlContainerManager(new RuntimeConfig({ base: 'http://test.com/foo/' }));
|
const manager = new UrlContainerManager('http://test.com/foo/');
|
||||||
await expect(manager.getContainer({ path: 'http://test.com/foo/bar' }))
|
await expect(manager.getContainer({ path: 'http://test.com/foo/bar' }))
|
||||||
.resolves.toEqual({ path: 'http://test.com/foo/' });
|
.resolves.toEqual({ path: 'http://test.com/foo/' });
|
||||||
await expect(manager.getContainer({ path: 'http://test.com/foo/bar/' }))
|
await expect(manager.getContainer({ path: 'http://test.com/foo/bar/' }))
|
||||||
@ -11,13 +10,13 @@ describe('An UrlContainerManager', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('errors when getting the container of root.', async(): Promise<void> => {
|
it('errors when getting the container of root.', async(): Promise<void> => {
|
||||||
let manager = new UrlContainerManager(new RuntimeConfig({ base: 'http://test.com/foo/' }));
|
let manager = new UrlContainerManager('http://test.com/foo/');
|
||||||
await expect(manager.getContainer({ path: 'http://test.com/foo/' }))
|
await expect(manager.getContainer({ path: 'http://test.com/foo/' }))
|
||||||
.rejects.toThrow('Root does not have a container.');
|
.rejects.toThrow('Root does not have a container.');
|
||||||
await expect(manager.getContainer({ path: 'http://test.com/foo' }))
|
await expect(manager.getContainer({ path: 'http://test.com/foo' }))
|
||||||
.rejects.toThrow('Root does not have a container.');
|
.rejects.toThrow('Root does not have a container.');
|
||||||
|
|
||||||
manager = new UrlContainerManager(new RuntimeConfig({ base: 'http://test.com/foo' }));
|
manager = new UrlContainerManager('http://test.com/foo/');
|
||||||
await expect(manager.getContainer({ path: 'http://test.com/foo/' }))
|
await expect(manager.getContainer({ path: 'http://test.com/foo/' }))
|
||||||
.rejects.toThrow('Root does not have a container.');
|
.rejects.toThrow('Root does not have a container.');
|
||||||
await expect(manager.getContainer({ path: 'http://test.com/foo' }))
|
await expect(manager.getContainer({ path: 'http://test.com/foo' }))
|
||||||
@ -25,7 +24,7 @@ describe('An UrlContainerManager', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('errors when the root of an URl is reached that does not match the input root.', async(): Promise<void> => {
|
it('errors when the root of an URl is reached that does not match the input root.', async(): Promise<void> => {
|
||||||
const manager = new UrlContainerManager(new RuntimeConfig({ base: 'http://test.com/foo/' }));
|
const manager = new UrlContainerManager('http://test.com/foo/');
|
||||||
await expect(manager.getContainer({ path: 'http://test.com/' }))
|
await expect(manager.getContainer({ path: 'http://test.com/' }))
|
||||||
.rejects.toThrow('URL root reached.');
|
.rejects.toThrow('URL root reached.');
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user