feat: Add App class to start and stop the server

This commit is contained in:
Joachim Van Herwegen 2021-06-11 15:30:21 +02:00
parent 29ddf57341
commit e8a0f63e02
40 changed files with 335 additions and 306 deletions

View File

@ -1,8 +1,12 @@
# Init # App
Options related to the server initialization. Options related to the server startup.
This is the entry point to the main server setup.
## Handler ## Base
This is the entry point to the main server setup.
* *default*: The main application. This should only be changed/replaced
if you want to start from a different kind of class.
## Init
Contains a list of initializer that need to be run when starting the server. Contains a list of initializer that need to be run when starting the server.
For example, when acl authorization is used, For example, when acl authorization is used,
an initializer will be added that makes sure there is an acl file in the root. an initializer will be added that makes sure there is an acl file in the root.

View File

@ -0,0 +1,19 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"@graph": [
{
"comment": "This is the entry point to the application. It can be used to both start and stop the server.",
"@id": "urn:solid-server:default:App",
"@type": "App",
"initializer": { "@id": "urn:solid-server:default:Initializer" },
"finalizer": {
"comment": "This is going to contain the list of finalizers that need to be called. These should be added in the configs where such classes are configured.",
"@id": "urn:solid-server:default:Finalizer",
"@type": "ParallelFinalizer",
"finalizers": [
{ "@id": "urn:solid-server:default:ServerInitializer" }
]
}
}
]
}

View File

@ -1,8 +1,8 @@
{ {
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld", "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"import": [ "import": [
"files-scs:config/init/handler/initializers/logger.json", "files-scs:config/app/init/initializers/logger.json",
"files-scs:config/init/handler/initializers/server.json" "files-scs:config/app/init/initializers/server.json"
], ],
"@graph": [ "@graph": [
{ {

View File

@ -1,8 +1,8 @@
{ {
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld", "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"import": [ "import": [
"files-scs:config/init/handler/base/init.json", "files-scs:config/app/init/base/init.json",
"files-scs:config/init/handler/initializers/root-container.json" "files-scs:config/app/init/initializers/root-container.json"
], ],
"@graph": [ "@graph": [
{ {

View File

@ -1,6 +1,8 @@
{ {
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld", "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"import": [ "import": [
"files-scs:config/app/app/default.json",
"files-scs:config/app/init/default.json",
"files-scs:config/http/handler/default.json", "files-scs:config/http/handler/default.json",
"files-scs:config/http/middleware/websockets.json", "files-scs:config/http/middleware/websockets.json",
"files-scs:config/http/server-factory/websockets.json", "files-scs:config/http/server-factory/websockets.json",
@ -9,7 +11,6 @@
"files-scs:config/identity/handler/default.json", "files-scs:config/identity/handler/default.json",
"files-scs:config/identity/ownership/token.json", "files-scs:config/identity/ownership/token.json",
"files-scs:config/identity/pod/static.json", "files-scs:config/identity/pod/static.json",
"files-scs:config/init/handler/default.json",
"files-scs:config/ldp/authentication/dpop-bearer.json", "files-scs:config/ldp/authentication/dpop-bearer.json",
"files-scs:config/ldp/authorization/webacl.json", "files-scs:config/ldp/authorization/webacl.json",
"files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/handler/default.json",

View File

@ -1,6 +1,8 @@
{ {
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld", "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"import": [ "import": [
"files-scs:config/app/app/default.json",
"files-scs:config/app/init/default.json",
"files-scs:config/http/handler/default.json", "files-scs:config/http/handler/default.json",
"files-scs:config/http/middleware/websockets.json", "files-scs:config/http/middleware/websockets.json",
"files-scs:config/http/server-factory/websockets.json", "files-scs:config/http/server-factory/websockets.json",
@ -9,7 +11,6 @@
"files-scs:config/identity/handler/default.json", "files-scs:config/identity/handler/default.json",
"files-scs:config/identity/ownership/token.json", "files-scs:config/identity/ownership/token.json",
"files-scs:config/identity/pod/dynamic.json", "files-scs:config/identity/pod/dynamic.json",
"files-scs:config/init/handler/default.json",
"files-scs:config/ldp/authentication/dpop-bearer.json", "files-scs:config/ldp/authentication/dpop-bearer.json",
"files-scs:config/ldp/authorization/webacl.json", "files-scs:config/ldp/authorization/webacl.json",
"files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/handler/default.json",

View File

@ -1,6 +1,8 @@
{ {
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld", "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"import": [ "import": [
"files-scs:config/app/app/default.json",
"files-scs:config/app/init/default.json",
"files-scs:config/http/handler/default.json", "files-scs:config/http/handler/default.json",
"files-scs:config/http/middleware/websockets.json", "files-scs:config/http/middleware/websockets.json",
@ -8,7 +10,6 @@
"files-scs:config/identity/email/default.json", "files-scs:config/identity/email/default.json",
"files-scs:config/identity/handler/default.json", "files-scs:config/identity/handler/default.json",
"files-scs:config/identity/ownership/token.json", "files-scs:config/identity/ownership/token.json",
"files-scs:config/init/handler/default.json",
"files-scs:config/ldp/authentication/dpop-bearer.json", "files-scs:config/ldp/authentication/dpop-bearer.json",
"files-scs:config/ldp/authorization/webacl.json", "files-scs:config/ldp/authorization/webacl.json",
"files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/handler/default.json",

View File

@ -1,6 +1,8 @@
{ {
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld", "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"import": [ "import": [
"files-scs:config/app/app/default.json",
"files-scs:config/app/init/default.json",
"files-scs:config/http/handler/default.json", "files-scs:config/http/handler/default.json",
"files-scs:config/http/middleware/websockets.json", "files-scs:config/http/middleware/websockets.json",
"files-scs:config/http/server-factory/websockets.json", "files-scs:config/http/server-factory/websockets.json",
@ -9,7 +11,6 @@
"files-scs:config/identity/handler/default.json", "files-scs:config/identity/handler/default.json",
"files-scs:config/identity/ownership/token.json", "files-scs:config/identity/ownership/token.json",
"files-scs:config/identity/pod/static.json", "files-scs:config/identity/pod/static.json",
"files-scs:config/init/handler/default.json",
"files-scs:config/ldp/authentication/dpop-bearer.json", "files-scs:config/ldp/authentication/dpop-bearer.json",
"files-scs:config/ldp/authorization/webacl.json", "files-scs:config/ldp/authorization/webacl.json",
"files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/handler/default.json",

View File

@ -14,6 +14,11 @@
"@id": "urn:solid-server:default:ExpiringIdpStorage", "@id": "urn:solid-server:default:ExpiringIdpStorage",
"@type": "WrappedExpiringStorage", "@type": "WrappedExpiringStorage",
"source": { "@id": "urn:solid-server:default:IdpStorage" } "source": { "@id": "urn:solid-server:default:IdpStorage" }
},
{
"comment": "Makes sure the expiring storage cleanup timer is stopped when the application needs to stop.",
"@id": "urn:solid-server:default:Finalizer",
"ParallelFinalizer:_finalizers": [ { "@id": "urn:solid-server:default:ExpiringIdpStorage" } ]
} }
] ]
} }

View File

@ -1,6 +1,8 @@
{ {
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld", "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"import": [ "import": [
"files-scs:config/app/app/default.json",
"files-scs:config/app/init/default.json",
"files-scs:config/http/handler/default.json", "files-scs:config/http/handler/default.json",
"files-scs:config/http/middleware/websockets.json", "files-scs:config/http/middleware/websockets.json",
"files-scs:config/http/server-factory/websockets.json", "files-scs:config/http/server-factory/websockets.json",
@ -9,7 +11,6 @@
"files-scs:config/identity/handler/default.json", "files-scs:config/identity/handler/default.json",
"files-scs:config/identity/ownership/token.json", "files-scs:config/identity/ownership/token.json",
"files-scs:config/identity/pod/static.json", "files-scs:config/identity/pod/static.json",
"files-scs:config/init/handler/default.json",
"files-scs:config/ldp/authentication/dpop-bearer.json", "files-scs:config/ldp/authentication/dpop-bearer.json",
"files-scs:config/ldp/authorization/webacl.json", "files-scs:config/ldp/authorization/webacl.json",
"files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/handler/default.json",

View File

@ -1,6 +1,8 @@
{ {
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld", "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"import": [ "import": [
"files-scs:config/app/app/default.json",
"files-scs:config/app/init/default.json",
"files-scs:config/http/handler/default.json", "files-scs:config/http/handler/default.json",
"files-scs:config/http/middleware/websockets.json", "files-scs:config/http/middleware/websockets.json",
"files-scs:config/http/server-factory/websockets.json", "files-scs:config/http/server-factory/websockets.json",
@ -9,7 +11,6 @@
"files-scs:config/identity/handler/default.json", "files-scs:config/identity/handler/default.json",
"files-scs:config/identity/ownership/token.json", "files-scs:config/identity/ownership/token.json",
"files-scs:config/identity/pod/static.json", "files-scs:config/identity/pod/static.json",
"files-scs:config/init/handler/default.json",
"files-scs:config/ldp/authentication/dpop-bearer.json", "files-scs:config/ldp/authentication/dpop-bearer.json",
"files-scs:config/ldp/authorization/webacl.json", "files-scs:config/ldp/authorization/webacl.json",
"files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/handler/default.json",

View File

@ -1,6 +1,8 @@
{ {
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld", "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"import": [ "import": [
"files-scs:config/app/app/default.json",
"files-scs:config/app/init/default.json",
"files-scs:config/http/handler/default.json", "files-scs:config/http/handler/default.json",
"files-scs:config/http/middleware/websockets.json", "files-scs:config/http/middleware/websockets.json",
"files-scs:config/http/server-factory/websockets.json", "files-scs:config/http/server-factory/websockets.json",
@ -9,7 +11,6 @@
"files-scs:config/identity/handler/default.json", "files-scs:config/identity/handler/default.json",
"files-scs:config/identity/ownership/token.json", "files-scs:config/identity/ownership/token.json",
"files-scs:config/identity/pod/static.json", "files-scs:config/identity/pod/static.json",
"files-scs:config/init/handler/default.json",
"files-scs:config/ldp/authentication/dpop-bearer.json", "files-scs:config/ldp/authentication/dpop-bearer.json",
"files-scs:config/ldp/authorization/webacl.json", "files-scs:config/ldp/authorization/webacl.json",
"files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/handler/default.json",

View File

@ -18,6 +18,11 @@
"suffixes_write": "write" "suffixes_write": "write"
}, },
"expiration": 3000 "expiration": 3000
},
{
"comment": "Makes sure the redis connection is closed when the application needs to stop.",
"@id": "urn:solid-server:default:Finalizer",
"ParallelFinalizer:_finalizers": [ { "@id": "urn:solid-server:default:RedisResourceLocker" } ]
} }
] ]
} }

View File

@ -66,8 +66,13 @@ export * from './identity/storage/WrappedFetchAdapterFactory';
export * from './identity/IdentityProviderFactory'; export * from './identity/IdentityProviderFactory';
export * from './identity/IdentityProviderHttpHandler'; export * from './identity/IdentityProviderHttpHandler';
// Init/Final
export * from './init/final/Finalizable';
export * from './init/final/ParallelFinalizer';
// Init // Init
export * from './init/AclInitializer'; export * from './init/AclInitializer';
export * from './init/App';
export * from './init/AppRunner'; export * from './init/AppRunner';
export * from './init/ConfigPodInitializer'; export * from './init/ConfigPodInitializer';
export * from './init/Initializer'; export * from './init/Initializer';

29
src/init/App.ts Normal file
View File

@ -0,0 +1,29 @@
import type { Finalizable } from './final/Finalizable';
import type { Initializer } from './Initializer';
/**
* Entry point for the entire Solid server.
*/
export class App {
private readonly initializer: Initializer;
private readonly finalizer: Finalizable;
public constructor(initializer: Initializer, finalizer: Finalizable) {
this.initializer = initializer;
this.finalizer = finalizer;
}
/**
* Initializes and starts the application.
*/
public async start(): Promise<void> {
await this.initializer.handleSafe();
}
/**
* Stops the application and handles cleanup.
*/
public async stop(): Promise<void> {
await this.finalizer.finalize();
}
}

View File

@ -6,11 +6,25 @@ import { ComponentsManager } from 'componentsjs';
import yargs from 'yargs'; import yargs from 'yargs';
import { getLoggerFor } from '../logging/LogUtil'; import { getLoggerFor } from '../logging/LogUtil';
import { absoluteFilePath, ensureTrailingSlash, joinFilePath } from '../util/PathUtil'; import { absoluteFilePath, ensureTrailingSlash, joinFilePath } from '../util/PathUtil';
import type { Initializer } from './Initializer'; import type { App } from './App';
export class AppRunner { export class AppRunner {
private readonly logger = getLoggerFor(this); private readonly logger = getLoggerFor(this);
/**
* Generic function for getting the app object to start the server from JavaScript for a given config.
* @param loaderProperties - Components.js loader properties.
* @param configFile - Path to the server config file.
* @param variableParams - Variables to pass into the config file.
*/
public async getApp(
loaderProperties: IComponentsManagerBuilderOptions<App>,
configFile: string,
variableParams: ConfigVariables,
): Promise<App> {
const variables = this.createVariables(variableParams);
return this.createApp(loaderProperties, configFile, variables);
}
/** /**
* Generic run function for starting the server from JavaScript for a given config. * Generic run function for starting the server from JavaScript for a given config.
* @param loaderProperties - Components.js loader properties. * @param loaderProperties - Components.js loader properties.
@ -18,13 +32,12 @@ export class AppRunner {
* @param variableParams - Variables to pass into the config file. * @param variableParams - Variables to pass into the config file.
*/ */
public async run( public async run(
loaderProperties: IComponentsManagerBuilderOptions<Initializer>, loaderProperties: IComponentsManagerBuilderOptions<App>,
configFile: string, configFile: string,
variableParams: ConfigVariables, variableParams: ConfigVariables,
): Promise<void> { ): Promise<void> {
const variables = this.createVariables(variableParams); const app = await this.getApp(loaderProperties, configFile, variableParams);
const initializer = await this.createInitializer(loaderProperties, configFile, variables); await app.start();
await initializer.handleSafe();
} }
/** /**
@ -74,18 +87,17 @@ export class AppRunner {
.help(); .help();
// Gather settings for instantiating the server // Gather settings for instantiating the server
const loaderProperties: IComponentsManagerBuilderOptions<Initializer> = { const loaderProperties: IComponentsManagerBuilderOptions<App> = {
mainModulePath: this.resolveFilePath(params.mainModulePath), mainModulePath: this.resolveFilePath(params.mainModulePath),
dumpErrorState: true, dumpErrorState: true,
logLevel: params.loggingLevel as LogLevel, logLevel: params.loggingLevel as LogLevel,
}; };
const configFile = this.resolveFilePath(params.config, 'config/default.json'); const configFile = this.resolveFilePath(params.config, 'config/default.json');
const variables = this.createVariables(params);
// Create and execute the server initializer // Create and execute the server initializer
this.createInitializer(loaderProperties, configFile, variables) this.getApp(loaderProperties, configFile, params)
.then( .then(
async(initializer): Promise<void> => initializer.handleSafe(), async(app): Promise<void> => app.start(),
(error: Error): void => { (error: Error): void => {
// Instantiation of components has failed, so there is no logger to use // Instantiation of components has failed, so there is no logger to use
stderr.write(`Error: could not instantiate server from ${configFile}\n`); stderr.write(`Error: could not instantiate server from ${configFile}\n`);
@ -93,7 +105,7 @@ export class AppRunner {
process.exit(1); process.exit(1);
}, },
).catch((error): void => { ).catch((error): void => {
this.logger.error(`Could not initialize server: ${error}`, { error }); this.logger.error(`Could not start server: ${error}`, { error });
process.exit(1); process.exit(1);
}); });
} }
@ -131,14 +143,14 @@ export class AppRunner {
/** /**
* Creates the server initializer * Creates the server initializer
*/ */
protected async createInitializer( protected async createApp(
componentsProperties: IComponentsManagerBuilderOptions<Initializer>, componentsProperties: IComponentsManagerBuilderOptions<App>,
configFile: string, configFile: string,
variables: Record<string, any>, variables: Record<string, any>,
): Promise<Initializer> { ): Promise<App> {
const componentsManager = await ComponentsManager.build(componentsProperties); const componentsManager = await ComponentsManager.build(componentsProperties);
const initializer = 'urn:solid-server:default:Initializer'; const initializer = 'urn:solid-server:default:App';
await componentsManager.configRegistry.register(configFile); await componentsManager.configRegistry.register(configFile);
return await componentsManager.instantiate(initializer, { variables }); return await componentsManager.instantiate(initializer, { variables });
} }

View File

@ -0,0 +1,16 @@
import type { Finalizable } from './Finalizable';
/**
* Finalizes all the injected Finalizable classes in parallel.
*/
export class ParallelFinalizer implements Finalizable {
private readonly finalizers: Finalizable[];
public constructor(finalizers: Finalizable[] = []) {
this.finalizers = finalizers;
}
public async finalize(): Promise<void> {
await Promise.all(this.finalizers.map(async(finalizer): Promise<void> => finalizer.finalize()));
}
}

View File

@ -3,7 +3,6 @@ import { ComponentsManager } from 'componentsjs';
import * as rimraf from 'rimraf'; import * as rimraf from 'rimraf';
import { joinFilePath } from '../../src/util/PathUtil'; import { joinFilePath } from '../../src/util/PathUtil';
export const BASE = 'http://test.com';
let cachedModuleState: IModuleState; let cachedModuleState: IModuleState;
/** /**
@ -42,3 +41,13 @@ export function getTestFolder(name: string): string {
export function removeFolder(folder: string): void { export function removeFolder(folder: string): void {
rimraf.sync(folder, { glob: false }); rimraf.sync(folder, { glob: false });
} }
export function getDefaultVariables(port: number, baseUrl?: string): Record<string, any> {
return {
'urn:solid-server:default:variable:baseUrl': baseUrl ?? `http://localhost:${port}/`,
'urn:solid-server:default:variable:port': port,
'urn:solid-server:default:variable:loggingLevel': 'off',
'urn:solid-server:default:variable:showStackTrace': true,
'urn:solid-server:default:variable:idpTemplateFolder': joinFilePath(__dirname, '../../templates/idp'),
};
}

View File

@ -1,13 +1,10 @@
import { mkdirSync } from 'fs'; import { mkdirSync } from 'fs';
import type { Server } from 'http';
import { stringify } from 'querystring'; import { stringify } from 'querystring';
import fetch from 'cross-fetch'; import fetch from 'cross-fetch';
import type { Initializer } from '../../src/init/Initializer'; import type { App } from '../../src/init/App';
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
import type { WrappedExpiringStorage } from '../../src/storage/keyvalue/WrappedExpiringStorage';
import { joinFilePath } from '../../src/util/PathUtil'; import { joinFilePath } from '../../src/util/PathUtil';
import { getPort } from '../util/Util'; import { getPort } from '../util/Util';
import { getTestConfigPath, getTestFolder, instantiateFromConfig, removeFolder } from './Config'; import { getDefaultVariables, getTestConfigPath, getTestFolder, instantiateFromConfig, removeFolder } from './Config';
const port = getPort('DynamicPods'); const port = getPort('DynamicPods');
const baseUrl = `http://localhost:${port}/`; const baseUrl = `http://localhost:${port}/`;
@ -26,45 +23,34 @@ const configs: [string, any][] = [
// Using the actual templates instead of specific test ones to prevent a lot of duplication // Using the actual templates instead of specific test ones to prevent a lot of duplication
// Tests are very similar to subdomain/pod tests. Would be nice if they can be combined // Tests are very similar to subdomain/pod tests. Would be nice if they can be combined
describe.each(configs)('A dynamic pod server with template config %s', (template, { teardown }): void => { describe.each(configs)('A dynamic pod server with template config %s', (template, { teardown }): void => {
let server: Server; let app: App;
let initializer: Initializer;
let factory: HttpServerFactory;
let expiringStorage: WrappedExpiringStorage<any, any>;
const settings = { podName: 'alice', webId: 'http://test.com/#alice', email: 'alice@test.email', template, createPod: true }; const settings = { podName: 'alice', webId: 'http://test.com/#alice', email: 'alice@test.email', template, createPod: true };
const podUrl = `${baseUrl}${settings.podName}/`; const podUrl = `${baseUrl}${settings.podName}/`;
beforeAll(async(): Promise<void> => { beforeAll(async(): Promise<void> => {
const variables: Record<string, any> = { const variables: Record<string, any> = {
'urn:solid-server:default:variable:baseUrl': baseUrl, ...getDefaultVariables(port, baseUrl),
'urn:solid-server:default:variable:rootFilePath': rootFilePath, 'urn:solid-server:default:variable:rootFilePath': rootFilePath,
'urn:solid-server:default:variable:podConfigJson': podConfigJson, 'urn:solid-server:default:variable:podConfigJson': podConfigJson,
'urn:solid-server:default:variable:showStackTrace': true,
'urn:solid-server:default:variable:idpTemplateFolder': joinFilePath(__dirname, '../../templates/idp'),
}; };
// Need to make sure the temp folder exists so the podConfigJson can be written to it // Need to make sure the temp folder exists so the podConfigJson can be written to it
mkdirSync(rootFilePath, { recursive: true }); mkdirSync(rootFilePath, { recursive: true });
// Create and initialize the HTTP handler and related components // Create and start the HTTP handler and related components
const instances = await instantiateFromConfig( const instances = await instantiateFromConfig(
'urn:solid-server:test:Instances', 'urn:solid-server:test:Instances',
getTestConfigPath('server-dynamic-unsafe.json'), getTestConfigPath('server-dynamic-unsafe.json'),
variables, variables,
) as Record<string, any>; ) as Record<string, any>;
({ factory, initializer, expiringStorage } = instances); ({ app } = instances);
// Set up the internal store await app.start();
await initializer.handleSafe();
server = factory.startServer(port);
}); });
afterAll(async(): Promise<void> => { afterAll(async(): Promise<void> => {
expiringStorage.finalize();
await new Promise((resolve, reject): void => {
server.close((error): void => error ? reject(error) : resolve());
});
await teardown(); await teardown();
await app.stop();
}); });
it('creates a pod with the given config.', async(): Promise<void> => { it('creates a pod with the given config.', async(): Promise<void> => {

View File

@ -1,17 +1,13 @@
import type { Server } from 'http';
import { stringify } from 'querystring'; import { stringify } from 'querystring';
import { URL } from 'url'; import { URL } from 'url';
import { load } from 'cheerio'; import { load } from 'cheerio';
import type { Response } from 'cross-fetch'; import type { Response } from 'cross-fetch';
import { fetch } from 'cross-fetch'; import { fetch } from 'cross-fetch';
import urljoin from 'url-join'; import urljoin from 'url-join';
import type { Initializer } from '../../src/init/Initializer'; import type { App } from '../../src/init/App';
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
import type { WrappedExpiringStorage } from '../../src/storage/keyvalue/WrappedExpiringStorage';
import { APPLICATION_X_WWW_FORM_URLENCODED } from '../../src/util/ContentTypes'; import { APPLICATION_X_WWW_FORM_URLENCODED } from '../../src/util/ContentTypes';
import { joinFilePath } from '../../src/util/PathUtil';
import { getPort } from '../util/Util'; import { getPort } from '../util/Util';
import { getTestConfigPath, instantiateFromConfig } from './Config'; import { getDefaultVariables, getTestConfigPath, instantiateFromConfig } from './Config';
import { IdentityTestState } from './IdentityTestState'; import { IdentityTestState } from './IdentityTestState';
const port = getPort('Identity'); const port = getPort('Identity');
@ -39,10 +35,7 @@ async function postForm(url: string, formBody: string): Promise<Response> {
// This is why the redirects are handled manually. // This is why the redirects are handled manually.
// We also need to parse the HTML in several steps since there is no API. // We also need to parse the HTML in several steps since there is no API.
describe('A Solid server with IDP', (): void => { describe('A Solid server with IDP', (): void => {
let server: Server; let app: App;
let initializer: Initializer;
let expiringStorage: WrappedExpiringStorage<any, any>;
let factory: HttpServerFactory;
const redirectUrl = 'http://mockedredirect/'; const redirectUrl = 'http://mockedredirect/';
const oidcIssuer = baseUrl; const oidcIssuer = baseUrl;
const card = urljoin(baseUrl, 'profile/card'); const card = urljoin(baseUrl, 'profile/card');
@ -61,15 +54,10 @@ describe('A Solid server with IDP', (): void => {
const instances = await instantiateFromConfig( const instances = await instantiateFromConfig(
'urn:solid-server:test:Instances', 'urn:solid-server:test:Instances',
getTestConfigPath('server-memory.json'), getTestConfigPath('server-memory.json'),
{ getDefaultVariables(port, baseUrl),
'urn:solid-server:default:variable:baseUrl': baseUrl,
'urn:solid-server:default:variable:showStackTrace': true,
'urn:solid-server:default:variable:idpTemplateFolder': joinFilePath(__dirname, '../../templates/idp'),
},
) as Record<string, any>; ) as Record<string, any>;
({ factory, initializer, expiringStorage } = instances); ({ app } = instances);
await initializer.handleSafe(); await app.start();
server = factory.startServer(port);
// Create a simple webId // Create a simple webId
const turtle = `<${webId}> <http://www.w3.org/ns/solid/terms#oidcIssuer> <${baseUrl}> .`; const turtle = `<${webId}> <http://www.w3.org/ns/solid/terms#oidcIssuer> <${baseUrl}> .`;
@ -81,10 +69,7 @@ describe('A Solid server with IDP', (): void => {
}); });
afterAll(async(): Promise<void> => { afterAll(async(): Promise<void> => {
expiringStorage.finalize(); await app.stop();
await new Promise<void>((resolve, reject): void => {
server.close((error): void => error ? reject(error) : resolve());
});
}); });
describe('doing registration', (): void => { describe('doing registration', (): void => {

View File

@ -1,12 +1,11 @@
import type { Server } from 'http';
import fetch from 'cross-fetch'; import fetch from 'cross-fetch';
import type { Initializer, ResourceStore } from '../../src/'; import type { ResourceStore, App } from '../../src/';
import { BasicRepresentation } from '../../src/'; import { BasicRepresentation } from '../../src/';
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
import { AclHelper } from '../util/AclHelper'; import { AclHelper } from '../util/AclHelper';
import { deleteResource, getResource, postResource, putResource } from '../util/FetchUtil'; import { deleteResource, getResource, postResource, putResource } from '../util/FetchUtil';
import { getPort } from '../util/Util'; import { getPort } from '../util/Util';
import { import {
getDefaultVariables,
getPresetConfigPath, getPresetConfigPath,
getTestConfigPath, getTestConfigPath,
getTestFolder, getTestFolder,
@ -30,21 +29,18 @@ const stores: [string, any][] = [
]; ];
describe.each(stores)('An LDP handler with auth using %s', (name, { storeConfig, teardown }): void => { describe.each(stores)('An LDP handler with auth using %s', (name, { storeConfig, teardown }): void => {
let server: Server; let app: App;
let initializer: Initializer;
let factory: HttpServerFactory;
let store: ResourceStore; let store: ResourceStore;
let aclHelper: AclHelper; let aclHelper: AclHelper;
const permanent = `${baseUrl}document.txt`; const permanent = `${baseUrl}document.txt`;
beforeAll(async(): Promise<void> => { beforeAll(async(): Promise<void> => {
const variables: Record<string, any> = { const variables = {
'urn:solid-server:default:variable:baseUrl': baseUrl, ...getDefaultVariables(port, baseUrl),
'urn:solid-server:default:variable:showStackTrace': true,
'urn:solid-server:default:variable:rootFilePath': rootFilePath, 'urn:solid-server:default:variable:rootFilePath': rootFilePath,
}; };
// Create and initialize the server // Create and start the server
const instances = await instantiateFromConfig( const instances = await instantiateFromConfig(
'urn:solid-server:test:Instances', 'urn:solid-server:test:Instances',
[ [
@ -53,10 +49,9 @@ describe.each(stores)('An LDP handler with auth using %s', (name, { storeConfig,
], ],
variables, variables,
) as Record<string, any>; ) as Record<string, any>;
({ factory, initializer, store } = instances); ({ app, store } = instances);
await initializer.handleSafe(); await app.start();
server = factory.startServer(port);
// Create test helper for manipulating acl // Create test helper for manipulating acl
aclHelper = new AclHelper(store); aclHelper = new AclHelper(store);
@ -75,9 +70,7 @@ describe.each(stores)('An LDP handler with auth using %s', (name, { storeConfig,
afterAll(async(): Promise<void> => { afterAll(async(): Promise<void> => {
await teardown(); await teardown();
await new Promise((resolve, reject): void => { await app.stop();
server.close((error): void => error ? reject(error) : resolve());
});
}); });
it('can add a document, read it and delete it if allowed.', async(): Promise<void> => { it('can add a document, read it and delete it if allowed.', async(): Promise<void> => {

View File

@ -1,14 +1,18 @@
import { createReadStream } from 'fs'; import { createReadStream } from 'fs';
import type { Server } from 'http';
import fetch from 'cross-fetch'; import fetch from 'cross-fetch';
import { DataFactory, Parser } from 'n3'; import { DataFactory, Parser } from 'n3';
import { joinFilePath, PIM, RDF } from '../../src/'; import { joinFilePath, PIM, RDF } from '../../src/';
import type { Initializer } from '../../src/'; import type { App } from '../../src/';
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
import { LDP } from '../../src/util/Vocabularies'; import { LDP } from '../../src/util/Vocabularies';
import { deleteResource, expectQuads, getResource, patchResource, postResource, putResource } from '../util/FetchUtil'; import { deleteResource, expectQuads, getResource, patchResource, postResource, putResource } from '../util/FetchUtil';
import { getPort } from '../util/Util'; import { getPort } from '../util/Util';
import { getPresetConfigPath, getTestConfigPath, getTestFolder, instantiateFromConfig, removeFolder } from './Config'; import {
getDefaultVariables,
getPresetConfigPath,
getTestConfigPath,
getTestFolder,
instantiateFromConfig, removeFolder,
} from './Config';
const { literal, namedNode, quad } = DataFactory; const { literal, namedNode, quad } = DataFactory;
const port = getPort('LpdHandlerWithoutAuth'); const port = getPort('LpdHandlerWithoutAuth');
@ -27,18 +31,15 @@ const stores: [string, any][] = [
]; ];
describe.each(stores)('An LDP handler allowing all requests %s', (name, { storeConfig, teardown }): void => { describe.each(stores)('An LDP handler allowing all requests %s', (name, { storeConfig, teardown }): void => {
let server: Server; let app: App;
let initializer: Initializer;
let factory: HttpServerFactory;
beforeAll(async(): Promise<void> => { beforeAll(async(): Promise<void> => {
const variables: Record<string, any> = { const variables = {
'urn:solid-server:default:variable:baseUrl': baseUrl, ...getDefaultVariables(port, baseUrl),
'urn:solid-server:default:variable:showStackTrace': true,
'urn:solid-server:default:variable:rootFilePath': rootFilePath, 'urn:solid-server:default:variable:rootFilePath': rootFilePath,
}; };
// Create and initialize the server // Create and start the server
const instances = await instantiateFromConfig( const instances = await instantiateFromConfig(
'urn:solid-server:test:Instances', 'urn:solid-server:test:Instances',
[ [
@ -47,17 +48,14 @@ describe.each(stores)('An LDP handler allowing all requests %s', (name, { storeC
], ],
variables, variables,
) as Record<string, any>; ) as Record<string, any>;
({ factory, initializer } = instances); ({ app } = instances);
await initializer.handleSafe(); await app.start();
server = factory.startServer(port);
}); });
afterAll(async(): Promise<void> => { afterAll(async(): Promise<void> => {
await teardown(); await teardown();
await new Promise((resolve, reject): void => { await app.stop();
server.close((error): void => error ? reject(error) : resolve());
});
}); });
it('can read a container listing.', async(): Promise<void> => { it('can read a container listing.', async(): Promise<void> => {

View File

@ -15,9 +15,6 @@ import type { ReadWriteLocker } from '../../src/util/locking/ReadWriteLocker';
import { SingleThreadedResourceLocker } from '../../src/util/locking/SingleThreadedResourceLocker'; import { SingleThreadedResourceLocker } from '../../src/util/locking/SingleThreadedResourceLocker';
import { WrappedExpiringReadWriteLocker } from '../../src/util/locking/WrappedExpiringReadWriteLocker'; import { WrappedExpiringReadWriteLocker } from '../../src/util/locking/WrappedExpiringReadWriteLocker';
import { guardedStreamFrom } from '../../src/util/StreamUtil'; import { guardedStreamFrom } from '../../src/util/StreamUtil';
import { BASE } from './Config';
// The modern fake timers implementation blocks certain parts with the current setup
jest.useFakeTimers('legacy'); jest.useFakeTimers('legacy');
describe('A LockingResourceStore', (): void => { describe('A LockingResourceStore', (): void => {
@ -44,7 +41,7 @@ describe('A LockingResourceStore', (): void => {
); );
// Initialize store // Initialize store
const initializer = new RootContainerInitializer({ store: source, baseUrl: BASE }); const initializer = new RootContainerInitializer({ store: source, baseUrl: base });
await initializer.handleSafe(); await initializer.handleSafe();
locker = new EqualReadWriteLocker(new SingleThreadedResourceLocker()); locker = new EqualReadWriteLocker(new SingleThreadedResourceLocker());

View File

@ -1,9 +1,8 @@
import type { Server } from 'http';
import fetch from 'cross-fetch'; import fetch from 'cross-fetch';
import type { RedisResourceLocker } from '../../src'; import type { App, RedisResourceLocker } from '../../src';
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
import { describeIf, getPort } from '../util/Util'; import { describeIf, getPort } from '../util/Util';
import { getTestConfigPath, instantiateFromConfig } from './Config'; import { getDefaultVariables, getTestConfigPath, instantiateFromConfig } from './Config';
/** /**
* Test the general functionality of the server using a RedisResourceLocker * Test the general functionality of the server using a RedisResourceLocker
@ -11,28 +10,21 @@ import { getTestConfigPath, instantiateFromConfig } from './Config';
describeIf('docker', 'A server with a RedisResourceLocker as ResourceLocker', (): void => { describeIf('docker', 'A server with a RedisResourceLocker as ResourceLocker', (): void => {
const port = getPort('RedisResourceLocker'); const port = getPort('RedisResourceLocker');
const baseUrl = `http://localhost:${port}/`; const baseUrl = `http://localhost:${port}/`;
let server: Server; let app: App;
let locker: RedisResourceLocker; let locker: RedisResourceLocker;
let factory: HttpServerFactory;
beforeAll(async(): Promise<void> => { beforeAll(async(): Promise<void> => {
const instances = await instantiateFromConfig( const instances = await instantiateFromConfig(
'urn:solid-server:test:Instances', 'urn:solid-server:test:Instances',
getTestConfigPath('run-with-redlock.json'), getTestConfigPath('run-with-redlock.json'),
{ getDefaultVariables(port, baseUrl),
'urn:solid-server:default:variable:baseUrl': baseUrl,
'urn:solid-server:default:variable:showStackTrace': true,
},
) as Record<string, any>; ) as Record<string, any>;
({ factory, locker } = instances); ({ app, locker } = instances);
server = factory.startServer(port); await app.start();
}); });
afterAll(async(): Promise<void> => { afterAll(async(): Promise<void> => {
await locker.finalize(); await app.stop();
await new Promise<void>((resolve, reject): void => {
server.close((error): void => error ? reject(error) : resolve());
});
}); });
it('can add a file to the store, read it and delete it.', async(): Promise<void> => { it('can add a file to the store, read it and delete it.', async(): Promise<void> => {

View File

@ -1,41 +1,27 @@
import type { Server } from 'http';
import fetch from 'cross-fetch'; import fetch from 'cross-fetch';
import type { Initializer } from '../../src/init/Initializer'; import type { App } from '../../src/init/App';
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
import type { WrappedExpiringStorage } from '../../src/storage/keyvalue/WrappedExpiringStorage';
import { getPort } from '../util/Util'; import { getPort } from '../util/Util';
import { getTestConfigPath, instantiateFromConfig } from './Config'; import { getDefaultVariables, getTestConfigPath, instantiateFromConfig } from './Config';
const port = getPort('ServerFetch'); const port = getPort('ServerFetch');
const baseUrl = `http://localhost:${port}/`; const baseUrl = `http://localhost:${port}/`;
// Some tests with real Requests/Responses until the mocking library has been removed from the tests // Some tests with real Requests/Responses until the mocking library has been removed from the tests
describe('A Solid server', (): void => { describe('A Solid server', (): void => {
let server: Server; let app: App;
let initializer: Initializer;
let expiringStorage: WrappedExpiringStorage<any, any>;
let factory: HttpServerFactory;
beforeAll(async(): Promise<void> => { beforeAll(async(): Promise<void> => {
const instances = await instantiateFromConfig( const instances = await instantiateFromConfig(
'urn:solid-server:test:Instances', 'urn:solid-server:test:Instances',
getTestConfigPath('server-memory.json'), getTestConfigPath('server-memory.json'),
{ getDefaultVariables(port, baseUrl),
'urn:solid-server:default:variable:baseUrl': baseUrl,
'urn:solid-server:default:variable:showStackTrace': true,
'urn:solid-server:default:variable:idpTemplateFolder': '',
},
) as Record<string, any>; ) as Record<string, any>;
({ factory, initializer, expiringStorage } = instances); ({ app } = instances);
await initializer.handleSafe(); await app.start();
server = factory.startServer(port);
}); });
afterAll(async(): Promise<void> => { afterAll(async(): Promise<void> => {
expiringStorage.finalize(); await app.stop();
await new Promise<void>((resolve, reject): void => {
server.close((error): void => error ? reject(error) : resolve());
});
}); });
it('can do a successful HEAD request to a container.', async(): Promise<void> => { it('can do a successful HEAD request to a container.', async(): Promise<void> => {

View File

@ -1,28 +1,23 @@
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import type { Server } from 'http';
import { joinFilePath } from '../../src/'; import { joinFilePath } from '../../src/';
import type { Initializer } from '../../src/'; import type { App } from '../../src/';
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
import { putResource } from '../util/FetchUtil'; import { putResource } from '../util/FetchUtil';
import { describeIf, getPort } from '../util/Util'; import { describeIf, getPort } from '../util/Util';
import { getPresetConfigPath, getTestConfigPath, instantiateFromConfig } from './Config'; import { getDefaultVariables, getPresetConfigPath, getTestConfigPath, instantiateFromConfig } from './Config';
const port = getPort('SparqlStorage'); const port = getPort('SparqlStorage');
const baseUrl = `http://localhost:${port}/`; const baseUrl = `http://localhost:${port}/`;
describeIf('docker', 'A server with a SPARQL endpoint as storage', (): void => { describeIf('docker', 'A server with a SPARQL endpoint as storage', (): void => {
let server: Server; let app: App;
let initializer: Initializer;
let factory: HttpServerFactory;
beforeAll(async(): Promise<void> => { beforeAll(async(): Promise<void> => {
const variables: Record<string, any> = { const variables = {
'urn:solid-server:default:variable:baseUrl': baseUrl, ...getDefaultVariables(port, baseUrl),
'urn:solid-server:default:variable:showStackTrace': true,
'urn:solid-server:default:variable:sparqlEndpoint': 'http://localhost:4000/sparql', 'urn:solid-server:default:variable:sparqlEndpoint': 'http://localhost:4000/sparql',
}; };
// Create and initialize the server // Create and start the server
const instances = await instantiateFromConfig( const instances = await instantiateFromConfig(
'urn:solid-server:test:Instances', 'urn:solid-server:test:Instances',
[ [
@ -31,16 +26,13 @@ describeIf('docker', 'A server with a SPARQL endpoint as storage', (): void => {
], ],
variables, variables,
) as Record<string, any>; ) as Record<string, any>;
({ factory, initializer } = instances); ({ app } = instances);
await initializer.handleSafe(); await app.start();
server = factory.startServer(port);
}); });
afterAll(async(): Promise<void> => { afterAll(async(): Promise<void> => {
await new Promise((resolve, reject): void => { await app.stop();
server.close((error): void => error ? reject(error) : resolve());
});
}); });
it('can add a Turtle file to the store.', async(): Promise<void> => { it('can add a Turtle file to the store.', async(): Promise<void> => {

View File

@ -1,12 +1,15 @@
import type { Server } from 'http';
import { stringify } from 'querystring'; import { stringify } from 'querystring';
import fetch from 'cross-fetch'; import fetch from 'cross-fetch';
import type { Initializer } from '../../src/init/Initializer'; import type { App } from '../../src/init/App';
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
import type { WrappedExpiringStorage } from '../../src/storage/keyvalue/WrappedExpiringStorage';
import { joinFilePath } from '../../src/util/PathUtil';
import { getPort } from '../util/Util'; import { getPort } from '../util/Util';
import { getPresetConfigPath, getTestConfigPath, getTestFolder, instantiateFromConfig, removeFolder } from './Config'; import {
getDefaultVariables,
getPresetConfigPath,
getTestConfigPath,
getTestFolder,
instantiateFromConfig,
removeFolder,
} from './Config';
const port = getPort('Subdomains'); const port = getPort('Subdomains');
const baseUrl = `http://localhost:${port}/`; const baseUrl = `http://localhost:${port}/`;
@ -25,23 +28,18 @@ const stores: [string, any][] = [
// Simulating subdomains using the forwarded header so no DNS changes are required // Simulating subdomains using the forwarded header so no DNS changes are required
describe.each(stores)('A subdomain server with %s', (name, { storeConfig, teardown }): void => { describe.each(stores)('A subdomain server with %s', (name, { storeConfig, teardown }): void => {
let server: Server; let app: App;
let initializer: Initializer;
let factory: HttpServerFactory;
let expiringStorage: WrappedExpiringStorage<any, any>;
const settings = { podName: 'alice', webId: 'http://test.com/#alice', email: 'alice@test.email', createPod: true }; const settings = { podName: 'alice', webId: 'http://test.com/#alice', email: 'alice@test.email', createPod: true };
const podHost = `alice.localhost:${port}`; const podHost = `alice.localhost:${port}`;
const podUrl = `http://${podHost}/`; const podUrl = `http://${podHost}/`;
beforeAll(async(): Promise<void> => { beforeAll(async(): Promise<void> => {
const variables: Record<string, any> = { const variables: Record<string, any> = {
'urn:solid-server:default:variable:baseUrl': baseUrl, ...getDefaultVariables(port, baseUrl),
'urn:solid-server:default:variable:rootFilePath': rootFilePath, 'urn:solid-server:default:variable:rootFilePath': rootFilePath,
'urn:solid-server:default:variable:showStackTrace': true,
'urn:solid-server:default:variable:idpTemplateFolder': joinFilePath(__dirname, '../../templates/idp'),
}; };
// Create and initialize the server // Create and start the server
const instances = await instantiateFromConfig( const instances = await instantiateFromConfig(
'urn:solid-server:test:Instances', 'urn:solid-server:test:Instances',
[ [
@ -50,18 +48,14 @@ describe.each(stores)('A subdomain server with %s', (name, { storeConfig, teardo
], ],
variables, variables,
) as Record<string, any>; ) as Record<string, any>;
({ factory, initializer, expiringStorage } = instances); ({ app } = instances);
await initializer.handleSafe(); await app.start();
server = factory.startServer(port);
}); });
afterAll(async(): Promise<void> => { afterAll(async(): Promise<void> => {
expiringStorage.finalize();
await new Promise((resolve, reject): void => {
server.close((error): void => error ? reject(error) : resolve());
});
await teardown(); await teardown();
await app.stop();
}); });
describe('handling resources', (): void => { describe('handling resources', (): void => {

View File

@ -1,37 +1,31 @@
import type { Server } from 'http';
import fetch from 'cross-fetch'; import fetch from 'cross-fetch';
import WebSocket from 'ws'; import WebSocket from 'ws';
import type { HttpServerFactory } from '../../src/server/HttpServerFactory'; import type { App } from '../../src/init/App';
import { getPort } from '../util/Util'; import { getPort } from '../util/Util';
import { getTestConfigPath, instantiateFromConfig } from './Config'; import { getDefaultVariables, getTestConfigPath, instantiateFromConfig } from './Config';
const port = getPort('WebSocketsProtocol'); const port = getPort('WebSocketsProtocol');
const serverUrl = `http://localhost:${port}/`; const serverUrl = `http://localhost:${port}/`;
const headers = { forwarded: 'host=example.pod;proto=https' }; const headers = { forwarded: 'host=example.pod;proto=https' };
describe('A server with the Solid WebSockets API behind a proxy', (): void => { describe('A server with the Solid WebSockets API behind a proxy', (): void => {
let server: Server; let app: App;
beforeAll(async(): Promise<void> => { beforeAll(async(): Promise<void> => {
const factory = await instantiateFromConfig( app = await instantiateFromConfig(
'urn:solid-server:default:ServerFactory', 'urn:solid-server:default:App',
getTestConfigPath('server-without-auth.json'), getTestConfigPath('server-without-auth.json'),
{ getDefaultVariables(port, 'https://example.pod/'),
'urn:solid-server:default:variable:baseUrl': 'https://example.pod/', ) as App;
'urn:solid-server:default:variable:showStackTrace': true, await app.start();
},
) as HttpServerFactory;
server = factory.startServer(port);
}); });
afterAll(async(): Promise<void> => { afterAll(async(): Promise<void> => {
await new Promise<void>((resolve, reject): void => { await app.stop();
server.close((error): void => error ? reject(error) : resolve());
});
}); });
it('returns a 404 if no data was initialized.', async(): Promise<void> => { it('returns a 404 if there is no data.', async(): Promise<void> => {
const response = await fetch(serverUrl, { headers }); const response = await fetch(`${serverUrl}foo`, { headers });
expect(response.status).toBe(404); expect(response.status).toBe(404);
}); });

View File

@ -1,11 +1,12 @@
{ {
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld", "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"import": [ "import": [
"files-scs:config/app/app/default.json",
"files-scs:config/app/init/default.json",
"files-scs:config/http/handler/simple.json", "files-scs:config/http/handler/simple.json",
"files-scs:config/http/middleware/no-websockets.json", "files-scs:config/http/middleware/no-websockets.json",
"files-scs:config/http/server-factory/no-websockets.json", "files-scs:config/http/server-factory/no-websockets.json",
"files-scs:config/http/static/default.json", "files-scs:config/http/static/default.json",
"files-scs:config/init/handler/default.json",
"files-scs:config/ldp/authentication/debug-auth-header.json", "files-scs:config/ldp/authentication/debug-auth-header.json",
"files-scs:config/ldp/authorization/webacl.json", "files-scs:config/ldp/authorization/webacl.json",
"files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/handler/default.json",
@ -28,17 +29,12 @@
"@type": "RecordObject", "@type": "RecordObject",
"record": [ "record": [
{ {
"comment": "Only use the parallel initializer. Starting the server is done in the test code..", "RecordObject:_record_key": "app",
"RecordObject:_record_key": "initializer", "RecordObject:_record_value": { "@id": "urn:solid-server:default:App" }
"RecordObject:_record_value": { "@id": "urn:solid-server:default:ParallelInitializer" }
}, },
{ {
"RecordObject:_record_key": "store", "RecordObject:_record_key": "store",
"RecordObject:_record_value": { "@id": "urn:solid-server:default:ResourceStore" } "RecordObject:_record_value": { "@id": "urn:solid-server:default:ResourceStore" }
},
{
"RecordObject:_record_key": "factory",
"RecordObject:_record_value": { "@id": "urn:solid-server:default:ServerFactory" }
} }
] ]
} }

View File

@ -1,11 +1,12 @@
{ {
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld", "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"import": [ "import": [
"files-scs:config/app/app/default.json",
"files-scs:config/app/init/default.json",
"files-scs:config/http/handler/simple.json", "files-scs:config/http/handler/simple.json",
"files-scs:config/http/middleware/no-websockets.json", "files-scs:config/http/middleware/no-websockets.json",
"files-scs:config/http/server-factory/no-websockets.json", "files-scs:config/http/server-factory/no-websockets.json",
"files-scs:config/http/static/default.json", "files-scs:config/http/static/default.json",
"files-scs:config/init/handler/default.json",
"files-scs:config/ldp/authentication/debug-auth-header.json", "files-scs:config/ldp/authentication/debug-auth-header.json",
"files-scs:config/ldp/authorization/allow-everything.json", "files-scs:config/ldp/authorization/allow-everything.json",
"files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/handler/default.json",
@ -29,17 +30,12 @@
"@type": "RecordObject", "@type": "RecordObject",
"record": [ "record": [
{ {
"comment": "Only use the parallel initializer. Starting the server is done in the test code..", "RecordObject:_record_key": "app",
"RecordObject:_record_key": "initializer", "RecordObject:_record_value": { "@id": "urn:solid-server:default:App" }
"RecordObject:_record_value": { "@id": "urn:solid-server:default:ParallelInitializer" }
}, },
{ {
"RecordObject:_record_key": "locker", "RecordObject:_record_key": "locker",
"RecordObject:_record_value": { "@id": "urn:solid-server:default:RedisResourceLocker" } "RecordObject:_record_value": { "@id": "urn:solid-server:default:RedisResourceLocker" }
},
{
"RecordObject:_record_key": "factory",
"RecordObject:_record_value": { "@id": "urn:solid-server:default:ServerFactory" }
} }
] ]
} }

View File

@ -1,6 +1,8 @@
{ {
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld", "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"import": [ "import": [
"files-scs:config/app/app/default.json",
"files-scs:config/app/init/default.json",
"files-scs:config/http/handler/default.json", "files-scs:config/http/handler/default.json",
"files-scs:config/http/middleware/no-websockets.json", "files-scs:config/http/middleware/no-websockets.json",
"files-scs:config/http/server-factory/no-websockets.json", "files-scs:config/http/server-factory/no-websockets.json",
@ -9,7 +11,6 @@
"files-scs:config/identity/handler/default.json", "files-scs:config/identity/handler/default.json",
"files-scs:config/identity/ownership/unsafe-no-check.json", "files-scs:config/identity/ownership/unsafe-no-check.json",
"files-scs:config/identity/pod/dynamic.json", "files-scs:config/identity/pod/dynamic.json",
"files-scs:config/init/handler/default.json",
"files-scs:config/ldp/authentication/debug-auth-header.json", "files-scs:config/ldp/authentication/debug-auth-header.json",
"files-scs:config/ldp/authorization/webacl.json", "files-scs:config/ldp/authorization/webacl.json",
"files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/handler/default.json",
@ -33,24 +34,10 @@
"@type": "RecordObject", "@type": "RecordObject",
"record": [ "record": [
{ {
"comment": "Only use the parallel initializer. Starting the server is done in the test code..", "RecordObject:_record_key": "app",
"RecordObject:_record_key": "initializer", "RecordObject:_record_value": { "@id": "urn:solid-server:default:App" }
"RecordObject:_record_value": { "@id": "urn:solid-server:default:ParallelInitializer" } }
}, ]
{
"RecordObject:_record_key": "factory",
"RecordObject:_record_value": { "@id": "urn:solid-server:default:ServerFactory" }
},
{
"comment": "Timer needs to be stopped when tests are finished.",
"RecordObject:_record_key": "expiringStorage",
"RecordObject:_record_value": { "@id": "urn:solid-server:default:ExpiringIdpStorage" }
}
]
},
{
"@id": "urn:solid-server:default:ResourcesGenerator",
"TemplatedResourcesGenerator:_templateFolder": "$PACKAGE_ROOT/test/assets/templates"
} }
] ]
} }

View File

@ -1,6 +1,8 @@
{ {
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld", "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"import": [ "import": [
"files-scs:config/app/app/default.json",
"files-scs:config/app/init/default.json",
"files-scs:config/http/handler/default.json", "files-scs:config/http/handler/default.json",
"files-scs:config/http/middleware/websockets.json", "files-scs:config/http/middleware/websockets.json",
"files-scs:config/http/server-factory/websockets.json", "files-scs:config/http/server-factory/websockets.json",
@ -8,7 +10,6 @@
"files-scs:config/identity/handler/default.json", "files-scs:config/identity/handler/default.json",
"files-scs:config/identity/ownership/token.json", "files-scs:config/identity/ownership/token.json",
"files-scs:config/identity/pod/static.json", "files-scs:config/identity/pod/static.json",
"files-scs:config/init/handler/default.json",
"files-scs:config/ldp/authentication/dpop-bearer.json", "files-scs:config/ldp/authentication/dpop-bearer.json",
"files-scs:config/ldp/authorization/webacl.json", "files-scs:config/ldp/authorization/webacl.json",
"files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/handler/default.json",
@ -31,18 +32,8 @@
"@type": "RecordObject", "@type": "RecordObject",
"RecordObject:_record": [ "RecordObject:_record": [
{ {
"comment": "Only use the parallel initializer. Starting the server is done in the test code..", "RecordObject:_record_key": "app",
"RecordObject:_record_key": "initializer", "RecordObject:_record_value": { "@id": "urn:solid-server:default:App" }
"RecordObject:_record_value": { "@id": "urn:solid-server:default:ParallelInitializer" }
},
{
"RecordObject:_record_key": "factory",
"RecordObject:_record_value": { "@id": "urn:solid-server:default:ServerFactory" }
},
{
"comment": "Timer needs to be stopped when tests are finished.",
"RecordObject:_record_key": "expiringStorage",
"RecordObject:_record_value": { "@id": "urn:solid-server:default:ExpiringIdpStorage" }
} }
] ]
}, },

View File

@ -1,6 +1,8 @@
{ {
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld", "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"import": [ "import": [
"files-scs:config/app/app/default.json",
"files-scs:config/app/init/default.json",
"files-scs:config/http/handler/default.json", "files-scs:config/http/handler/default.json",
"files-scs:config/http/middleware/no-websockets.json", "files-scs:config/http/middleware/no-websockets.json",
"files-scs:config/http/server-factory/no-websockets.json", "files-scs:config/http/server-factory/no-websockets.json",
@ -9,7 +11,6 @@
"files-scs:config/identity/handler/default.json", "files-scs:config/identity/handler/default.json",
"files-scs:config/identity/ownership/unsafe-no-check.json", "files-scs:config/identity/ownership/unsafe-no-check.json",
"files-scs:config/identity/pod/static.json", "files-scs:config/identity/pod/static.json",
"files-scs:config/init/handler/default.json",
"files-scs:config/ldp/authentication/debug-auth-header.json", "files-scs:config/ldp/authentication/debug-auth-header.json",
"files-scs:config/ldp/authorization/webacl.json", "files-scs:config/ldp/authorization/webacl.json",
"files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/handler/default.json",
@ -32,18 +33,8 @@
"@type": "RecordObject", "@type": "RecordObject",
"record": [ "record": [
{ {
"comment": "Only use the parallel initializer. Starting the server is done in the test code..", "RecordObject:_record_key": "app",
"RecordObject:_record_key": "initializer", "RecordObject:_record_value": { "@id": "urn:solid-server:default:App" }
"RecordObject:_record_value": { "@id": "urn:solid-server:default:ParallelInitializer" }
},
{
"RecordObject:_record_key": "factory",
"RecordObject:_record_value": { "@id": "urn:solid-server:default:ServerFactory" }
},
{
"comment": "Timer needs to be stopped when tests are finished.",
"RecordObject:_record_key": "expiringStorage",
"RecordObject:_record_value": { "@id": "urn:solid-server:default:ExpiringIdpStorage" }
} }
] ]
} }

View File

@ -1,11 +1,12 @@
{ {
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld", "@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"import": [ "import": [
"files-scs:config/app/app/default.json",
"files-scs:config/app/init/default.json",
"files-scs:config/http/handler/default.json", "files-scs:config/http/handler/default.json",
"files-scs:config/http/middleware/websockets.json", "files-scs:config/http/middleware/websockets.json",
"files-scs:config/http/server-factory/websockets.json", "files-scs:config/http/server-factory/websockets.json",
"files-scs:config/http/static/default.json", "files-scs:config/http/static/default.json",
"files-scs:config/init/handler/default.json",
"files-scs:config/ldp/authentication/dpop-bearer.json", "files-scs:config/ldp/authentication/dpop-bearer.json",
"files-scs:config/ldp/authorization/allow-everything.json", "files-scs:config/ldp/authorization/allow-everything.json",
"files-scs:config/ldp/handler/default.json", "files-scs:config/ldp/handler/default.json",
@ -23,22 +24,6 @@
"files-scs:config/util/variables/default.json" "files-scs:config/util/variables/default.json"
], ],
"@graph": [ "@graph": [
{
"@id": "urn:solid-server:test:Instances",
"@type": "RecordObject",
"RecordObject:_record": [
{
"comment": "Only use the parallel initializer. Starting the server is done in the test code..",
"RecordObject:_record_key": "initializer",
"RecordObject:_record_value": { "@id": "urn:solid-server:default:ParallelInitializer" }
},
{
"RecordObject:_record_key": "factory",
"RecordObject:_record_value": { "@id": "urn:solid-server:default:ServerFactory" }
}
]
},
{ {
"@id": "urn:solid-server:default:IdentityProviderHandler", "@id": "urn:solid-server:default:IdentityProviderHandler",
"@type": "UnsupportedAsyncHandler" "@type": "UnsupportedAsyncHandler"

View File

@ -0,0 +1,25 @@
import { App } from '../../../src/init/App';
import type { Finalizable } from '../../../src/init/final/Finalizable';
import type { Initializer } from '../../../src/init/Initializer';
describe('An App', (): void => {
let initializer: Initializer;
let finalizer: Finalizable;
let app: App;
beforeEach(async(): Promise<void> => {
initializer = { handleSafe: jest.fn() } as any;
finalizer = { finalize: jest.fn() };
app = new App(initializer, finalizer);
});
it('can start with the initializer.', async(): Promise<void> => {
await expect(app.start()).resolves.toBeUndefined();
expect(initializer.handleSafe).toHaveBeenCalledTimes(1);
});
it('can stop with the finalizer.', async(): Promise<void> => {
await expect(app.stop()).resolves.toBeUndefined();
expect(finalizer.finalize).toHaveBeenCalledTimes(1);
});
});

View File

@ -1,14 +1,14 @@
import { ComponentsManager } from 'componentsjs'; import { ComponentsManager } from 'componentsjs';
import type { App } from '../../../src/init/App';
import { AppRunner } from '../../../src/init/AppRunner'; import { AppRunner } from '../../../src/init/AppRunner';
import type { Initializer } from '../../../src/init/Initializer';
import { joinFilePath } from '../../../src/util/PathUtil'; import { joinFilePath } from '../../../src/util/PathUtil';
const initializer: jest.Mocked<Initializer> = { const app: jest.Mocked<App> = {
handleSafe: jest.fn(), start: jest.fn(),
} as any; } as any;
const manager: jest.Mocked<ComponentsManager<Initializer>> = { const manager: jest.Mocked<ComponentsManager<App>> = {
instantiate: jest.fn(async(): Promise<Initializer> => initializer), instantiate: jest.fn(async(): Promise<App> => app),
configRegistry: { configRegistry: {
register: jest.fn(), register: jest.fn(),
}, },
@ -17,7 +17,7 @@ const manager: jest.Mocked<ComponentsManager<Initializer>> = {
jest.mock('componentsjs', (): any => ({ jest.mock('componentsjs', (): any => ({
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
ComponentsManager: { ComponentsManager: {
build: jest.fn(async(): Promise<ComponentsManager<Initializer>> => manager), build: jest.fn(async(): Promise<ComponentsManager<App>> => manager),
}, },
})); }));
@ -60,7 +60,7 @@ describe('AppRunner', (): void => {
.toHaveBeenCalledWith(joinFilePath(__dirname, '/../../../config/default.json')); .toHaveBeenCalledWith(joinFilePath(__dirname, '/../../../config/default.json'));
expect(manager.instantiate).toHaveBeenCalledTimes(1); expect(manager.instantiate).toHaveBeenCalledTimes(1);
expect(manager.instantiate).toHaveBeenCalledWith( expect(manager.instantiate).toHaveBeenCalledWith(
'urn:solid-server:default:Initializer', 'urn:solid-server:default:App',
{ {
variables: { variables: {
'urn:solid-server:default:variable:port': 3000, 'urn:solid-server:default:variable:port': 3000,
@ -74,8 +74,8 @@ describe('AppRunner', (): void => {
}, },
}, },
); );
expect(initializer.handleSafe).toHaveBeenCalledTimes(1); expect(app.start).toHaveBeenCalledTimes(1);
expect(initializer.handleSafe).toHaveBeenCalledWith(); expect(app.start).toHaveBeenCalledWith();
}); });
}); });
@ -85,7 +85,7 @@ describe('AppRunner', (): void => {
argv: [ 'node', 'script' ], argv: [ 'node', 'script' ],
}); });
// Wait until initializer has been called, because we can't await AppRunner.run. // Wait until app.start has been called, because we can't await AppRunner.run.
await new Promise((resolve): void => { await new Promise((resolve): void => {
setImmediate(resolve); setImmediate(resolve);
}); });
@ -101,7 +101,7 @@ describe('AppRunner', (): void => {
.toHaveBeenCalledWith(joinFilePath(__dirname, '/../../../config/default.json')); .toHaveBeenCalledWith(joinFilePath(__dirname, '/../../../config/default.json'));
expect(manager.instantiate).toHaveBeenCalledTimes(1); expect(manager.instantiate).toHaveBeenCalledTimes(1);
expect(manager.instantiate).toHaveBeenCalledWith( expect(manager.instantiate).toHaveBeenCalledWith(
'urn:solid-server:default:Initializer', 'urn:solid-server:default:App',
{ {
variables: { variables: {
'urn:solid-server:default:variable:port': 3000, 'urn:solid-server:default:variable:port': 3000,
@ -115,8 +115,8 @@ describe('AppRunner', (): void => {
}, },
}, },
); );
expect(initializer.handleSafe).toHaveBeenCalledTimes(1); expect(app.start).toHaveBeenCalledTimes(1);
expect(initializer.handleSafe).toHaveBeenCalledWith(); expect(app.start).toHaveBeenCalledWith();
}); });
it('accepts abbreviated flags.', async(): Promise<void> => { it('accepts abbreviated flags.', async(): Promise<void> => {
@ -136,7 +136,7 @@ describe('AppRunner', (): void => {
], ],
}); });
// Wait until initializer has been called, because we can't await AppRunner.run. // Wait until app.start has been called, because we can't await AppRunner.run.
await new Promise((resolve): void => { await new Promise((resolve): void => {
setImmediate(resolve); setImmediate(resolve);
}); });
@ -150,7 +150,7 @@ describe('AppRunner', (): void => {
expect(manager.configRegistry.register).toHaveBeenCalledTimes(1); expect(manager.configRegistry.register).toHaveBeenCalledTimes(1);
expect(manager.configRegistry.register).toHaveBeenCalledWith('/var/cwd/myconfig.json'); expect(manager.configRegistry.register).toHaveBeenCalledWith('/var/cwd/myconfig.json');
expect(manager.instantiate).toHaveBeenCalledWith( expect(manager.instantiate).toHaveBeenCalledWith(
'urn:solid-server:default:Initializer', 'urn:solid-server:default:App',
{ {
variables: { variables: {
'urn:solid-server:default:variable:baseUrl': 'http://pod.example/', 'urn:solid-server:default:variable:baseUrl': 'http://pod.example/',
@ -183,7 +183,7 @@ describe('AppRunner', (): void => {
], ],
}); });
// Wait until initializer has been called, because we can't await AppRunner.run. // Wait until app.start has been called, because we can't await AppRunner.run.
await new Promise((resolve): void => { await new Promise((resolve): void => {
setImmediate(resolve); setImmediate(resolve);
}); });
@ -197,7 +197,7 @@ describe('AppRunner', (): void => {
expect(manager.configRegistry.register).toHaveBeenCalledTimes(1); expect(manager.configRegistry.register).toHaveBeenCalledTimes(1);
expect(manager.configRegistry.register).toHaveBeenCalledWith('/var/cwd/myconfig.json'); expect(manager.configRegistry.register).toHaveBeenCalledWith('/var/cwd/myconfig.json');
expect(manager.instantiate).toHaveBeenCalledWith( expect(manager.instantiate).toHaveBeenCalledWith(
'urn:solid-server:default:Initializer', 'urn:solid-server:default:App',
{ {
variables: { variables: {
'urn:solid-server:default:variable:baseUrl': 'http://pod.example/', 'urn:solid-server:default:variable:baseUrl': 'http://pod.example/',
@ -231,7 +231,7 @@ describe('AppRunner', (): void => {
new AppRunner().runCli(); new AppRunner().runCli();
// Wait until initializer has been called, because we can't await AppRunner.run. // Wait until app.start has been called, because we can't await AppRunner.run.
await new Promise((resolve): void => { await new Promise((resolve): void => {
setImmediate(resolve); setImmediate(resolve);
}); });
@ -245,7 +245,7 @@ describe('AppRunner', (): void => {
expect(manager.configRegistry.register).toHaveBeenCalledTimes(1); expect(manager.configRegistry.register).toHaveBeenCalledTimes(1);
expect(manager.configRegistry.register).toHaveBeenCalledWith('/var/cwd/myconfig.json'); expect(manager.configRegistry.register).toHaveBeenCalledWith('/var/cwd/myconfig.json');
expect(manager.instantiate).toHaveBeenCalledWith( expect(manager.instantiate).toHaveBeenCalledWith(
'urn:solid-server:default:Initializer', 'urn:solid-server:default:App',
{ {
variables: { variables: {
'urn:solid-server:default:variable:baseUrl': 'http://pod.example/', 'urn:solid-server:default:variable:baseUrl': 'http://pod.example/',
@ -269,7 +269,7 @@ describe('AppRunner', (): void => {
argv: [ 'node', 'script' ], argv: [ 'node', 'script' ],
}); });
// Wait until initializer has been called, because we can't await AppRunner.run. // Wait until app.start has been called, because we can't await AppRunner.run.
await new Promise((resolve): void => { await new Promise((resolve): void => {
setImmediate(resolve); setImmediate(resolve);
}); });
@ -285,12 +285,12 @@ describe('AppRunner', (): void => {
}); });
it('exits without output to stderr when initialization fails.', async(): Promise<void> => { it('exits without output to stderr when initialization fails.', async(): Promise<void> => {
initializer.handleSafe.mockRejectedValueOnce(new Error('Fatal')); app.start.mockRejectedValueOnce(new Error('Fatal'));
new AppRunner().runCli({ new AppRunner().runCli({
argv: [ 'node', 'script' ], argv: [ 'node', 'script' ],
}); });
// Wait until initializer has been called, because we can't await AppRunner.run. // Wait until app.start has been called, because we can't await AppRunner.run.
await new Promise((resolve): void => { await new Promise((resolve): void => {
setImmediate(resolve); setImmediate(resolve);
}); });
@ -305,7 +305,7 @@ describe('AppRunner', (): void => {
argv: [ 'node', 'script', '--foo' ], argv: [ 'node', 'script', '--foo' ],
}); });
// Wait until initializer has been called, because we can't await AppRunner.run. // Wait until app.start has been called, because we can't await AppRunner.run.
await new Promise((resolve): void => { await new Promise((resolve): void => {
setImmediate(resolve); setImmediate(resolve);
}); });
@ -320,7 +320,7 @@ describe('AppRunner', (): void => {
argv: [ 'node', 'script', '-s' ], argv: [ 'node', 'script', '-s' ],
}); });
// Wait until initializer has been called, because we can't await AppRunner.run. // Wait until app.start has been called, because we can't await AppRunner.run.
await new Promise((resolve): void => { await new Promise((resolve): void => {
setImmediate(resolve); setImmediate(resolve);
}); });
@ -335,7 +335,7 @@ describe('AppRunner', (): void => {
argv: [ 'node', 'script', 'foo', 'bar', 'foo.txt', 'bar.txt' ], argv: [ 'node', 'script', 'foo', 'bar', 'foo.txt', 'bar.txt' ],
}); });
// Wait until initializer has been called, because we can't await AppRunner.run. // Wait until app.start has been called, because we can't await AppRunner.run.
await new Promise((resolve): void => { await new Promise((resolve): void => {
setImmediate(resolve); setImmediate(resolve);
}); });
@ -350,7 +350,7 @@ describe('AppRunner', (): void => {
argv: [ 'node', 'script', '-l', 'info', '-l', 'debug' ], argv: [ 'node', 'script', '-l', 'info', '-l', 'debug' ],
}); });
// Wait until initializer has been called, because we can't await AppRunner.run. // Wait until app.start has been called, because we can't await AppRunner.run.
await new Promise((resolve): void => { await new Promise((resolve): void => {
setImmediate(resolve); setImmediate(resolve);
}); });

View File

@ -0,0 +1,30 @@
import type { Finalizable } from '../../../../src/init/final/Finalizable';
import { ParallelFinalizer } from '../../../../src/init/final/ParallelFinalizer';
describe('A ParallelFinalizer', (): void => {
let finalizers: Finalizable[];
let finalizer: ParallelFinalizer;
let results: number[];
beforeEach(async(): Promise<void> => {
results = [];
finalizers = [
{ finalize: jest.fn((): any => results.push(0)) },
{ finalize: jest.fn((): any => results.push(1)) },
];
finalizer = new ParallelFinalizer(finalizers);
});
it('is finished when all finalizers are finished.', async(): Promise<void> => {
await expect(finalizer.finalize()).resolves.toBeUndefined();
expect(finalizers[0].finalize).toHaveBeenCalledTimes(1);
expect(finalizers[1].finalize).toHaveBeenCalledTimes(1);
expect(results).toEqual([ 0, 1 ]);
});
it('works if there are no input finalizers.', async(): Promise<void> => {
finalizer = new ParallelFinalizer();
await expect(finalizer.finalize()).resolves.toBeUndefined();
});
});