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
Options related to the server initialization.
This is the entry point to the main server setup.
# App
Options related to the server startup.
## 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.
For example, when acl authorization is used,
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",
"import": [
"files-scs:config/init/handler/initializers/logger.json",
"files-scs:config/init/handler/initializers/server.json"
"files-scs:config/app/init/initializers/logger.json",
"files-scs:config/app/init/initializers/server.json"
],
"@graph": [
{

View File

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

View File

@ -1,6 +1,8 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"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/middleware/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/ownership/token.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/authorization/webacl.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",
"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/middleware/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/ownership/token.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/authorization/webacl.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",
"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/middleware/websockets.json",
@ -8,7 +10,6 @@
"files-scs:config/identity/email/default.json",
"files-scs:config/identity/handler/default.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/authorization/webacl.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",
"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/middleware/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/ownership/token.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/authorization/webacl.json",
"files-scs:config/ldp/handler/default.json",

View File

@ -14,6 +14,11 @@
"@id": "urn:solid-server:default:ExpiringIdpStorage",
"@type": "WrappedExpiringStorage",
"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",
"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/middleware/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/ownership/token.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/authorization/webacl.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",
"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/middleware/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/ownership/token.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/authorization/webacl.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",
"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/middleware/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/ownership/token.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/authorization/webacl.json",
"files-scs:config/ldp/handler/default.json",

View File

@ -18,6 +18,11 @@
"suffixes_write": "write"
},
"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/IdentityProviderHttpHandler';
// Init/Final
export * from './init/final/Finalizable';
export * from './init/final/ParallelFinalizer';
// Init
export * from './init/AclInitializer';
export * from './init/App';
export * from './init/AppRunner';
export * from './init/ConfigPodInitializer';
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 { getLoggerFor } from '../logging/LogUtil';
import { absoluteFilePath, ensureTrailingSlash, joinFilePath } from '../util/PathUtil';
import type { Initializer } from './Initializer';
import type { App } from './App';
export class AppRunner {
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.
* @param loaderProperties - Components.js loader properties.
@ -18,13 +32,12 @@ export class AppRunner {
* @param variableParams - Variables to pass into the config file.
*/
public async run(
loaderProperties: IComponentsManagerBuilderOptions<Initializer>,
loaderProperties: IComponentsManagerBuilderOptions<App>,
configFile: string,
variableParams: ConfigVariables,
): Promise<void> {
const variables = this.createVariables(variableParams);
const initializer = await this.createInitializer(loaderProperties, configFile, variables);
await initializer.handleSafe();
const app = await this.getApp(loaderProperties, configFile, variableParams);
await app.start();
}
/**
@ -74,18 +87,17 @@ export class AppRunner {
.help();
// Gather settings for instantiating the server
const loaderProperties: IComponentsManagerBuilderOptions<Initializer> = {
const loaderProperties: IComponentsManagerBuilderOptions<App> = {
mainModulePath: this.resolveFilePath(params.mainModulePath),
dumpErrorState: true,
logLevel: params.loggingLevel as LogLevel,
};
const configFile = this.resolveFilePath(params.config, 'config/default.json');
const variables = this.createVariables(params);
// Create and execute the server initializer
this.createInitializer(loaderProperties, configFile, variables)
this.getApp(loaderProperties, configFile, params)
.then(
async(initializer): Promise<void> => initializer.handleSafe(),
async(app): Promise<void> => app.start(),
(error: Error): void => {
// Instantiation of components has failed, so there is no logger to use
stderr.write(`Error: could not instantiate server from ${configFile}\n`);
@ -93,7 +105,7 @@ export class AppRunner {
process.exit(1);
},
).catch((error): void => {
this.logger.error(`Could not initialize server: ${error}`, { error });
this.logger.error(`Could not start server: ${error}`, { error });
process.exit(1);
});
}
@ -131,14 +143,14 @@ export class AppRunner {
/**
* Creates the server initializer
*/
protected async createInitializer(
componentsProperties: IComponentsManagerBuilderOptions<Initializer>,
protected async createApp(
componentsProperties: IComponentsManagerBuilderOptions<App>,
configFile: string,
variables: Record<string, any>,
): Promise<Initializer> {
): Promise<App> {
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);
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 { joinFilePath } from '../../src/util/PathUtil';
export const BASE = 'http://test.com';
let cachedModuleState: IModuleState;
/**
@ -42,3 +41,13 @@ export function getTestFolder(name: string): string {
export function removeFolder(folder: string): void {
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 type { Server } from 'http';
import { stringify } from 'querystring';
import fetch from 'cross-fetch';
import type { Initializer } from '../../src/init/Initializer';
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
import type { WrappedExpiringStorage } from '../../src/storage/keyvalue/WrappedExpiringStorage';
import type { App } from '../../src/init/App';
import { joinFilePath } from '../../src/util/PathUtil';
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 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
// 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 => {
let server: Server;
let initializer: Initializer;
let factory: HttpServerFactory;
let expiringStorage: WrappedExpiringStorage<any, any>;
let app: App;
const settings = { podName: 'alice', webId: 'http://test.com/#alice', email: 'alice@test.email', template, createPod: true };
const podUrl = `${baseUrl}${settings.podName}/`;
beforeAll(async(): Promise<void> => {
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: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
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(
'urn:solid-server:test:Instances',
getTestConfigPath('server-dynamic-unsafe.json'),
variables,
) as Record<string, any>;
({ factory, initializer, expiringStorage } = instances);
({ app } = instances);
// Set up the internal store
await initializer.handleSafe();
server = factory.startServer(port);
await app.start();
});
afterAll(async(): Promise<void> => {
expiringStorage.finalize();
await new Promise((resolve, reject): void => {
server.close((error): void => error ? reject(error) : resolve());
});
await teardown();
await app.stop();
});
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 { URL } from 'url';
import { load } from 'cheerio';
import type { Response } from 'cross-fetch';
import { fetch } from 'cross-fetch';
import urljoin from 'url-join';
import type { Initializer } from '../../src/init/Initializer';
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
import type { WrappedExpiringStorage } from '../../src/storage/keyvalue/WrappedExpiringStorage';
import type { App } from '../../src/init/App';
import { APPLICATION_X_WWW_FORM_URLENCODED } from '../../src/util/ContentTypes';
import { joinFilePath } from '../../src/util/PathUtil';
import { getPort } from '../util/Util';
import { getTestConfigPath, instantiateFromConfig } from './Config';
import { getDefaultVariables, getTestConfigPath, instantiateFromConfig } from './Config';
import { IdentityTestState } from './IdentityTestState';
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.
// We also need to parse the HTML in several steps since there is no API.
describe('A Solid server with IDP', (): void => {
let server: Server;
let initializer: Initializer;
let expiringStorage: WrappedExpiringStorage<any, any>;
let factory: HttpServerFactory;
let app: App;
const redirectUrl = 'http://mockedredirect/';
const oidcIssuer = baseUrl;
const card = urljoin(baseUrl, 'profile/card');
@ -61,15 +54,10 @@ describe('A Solid server with IDP', (): void => {
const instances = await instantiateFromConfig(
'urn:solid-server:test:Instances',
getTestConfigPath('server-memory.json'),
{
'urn:solid-server:default:variable:baseUrl': baseUrl,
'urn:solid-server:default:variable:showStackTrace': true,
'urn:solid-server:default:variable:idpTemplateFolder': joinFilePath(__dirname, '../../templates/idp'),
},
getDefaultVariables(port, baseUrl),
) as Record<string, any>;
({ factory, initializer, expiringStorage } = instances);
await initializer.handleSafe();
server = factory.startServer(port);
({ app } = instances);
await app.start();
// Create a simple webId
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> => {
expiringStorage.finalize();
await new Promise<void>((resolve, reject): void => {
server.close((error): void => error ? reject(error) : resolve());
});
await app.stop();
});
describe('doing registration', (): void => {

View File

@ -1,12 +1,11 @@
import type { Server } from 'http';
import fetch from 'cross-fetch';
import type { Initializer, ResourceStore } from '../../src/';
import type { ResourceStore, App } from '../../src/';
import { BasicRepresentation } from '../../src/';
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
import { AclHelper } from '../util/AclHelper';
import { deleteResource, getResource, postResource, putResource } from '../util/FetchUtil';
import { getPort } from '../util/Util';
import {
getDefaultVariables,
getPresetConfigPath,
getTestConfigPath,
getTestFolder,
@ -30,21 +29,18 @@ const stores: [string, any][] = [
];
describe.each(stores)('An LDP handler with auth using %s', (name, { storeConfig, teardown }): void => {
let server: Server;
let initializer: Initializer;
let factory: HttpServerFactory;
let app: App;
let store: ResourceStore;
let aclHelper: AclHelper;
const permanent = `${baseUrl}document.txt`;
beforeAll(async(): Promise<void> => {
const variables: Record<string, any> = {
'urn:solid-server:default:variable:baseUrl': baseUrl,
'urn:solid-server:default:variable:showStackTrace': true,
const variables = {
...getDefaultVariables(port, baseUrl),
'urn:solid-server:default:variable:rootFilePath': rootFilePath,
};
// Create and initialize the server
// Create and start the server
const instances = await instantiateFromConfig(
'urn:solid-server:test:Instances',
[
@ -53,10 +49,9 @@ describe.each(stores)('An LDP handler with auth using %s', (name, { storeConfig,
],
variables,
) as Record<string, any>;
({ factory, initializer, store } = instances);
({ app, store } = instances);
await initializer.handleSafe();
server = factory.startServer(port);
await app.start();
// Create test helper for manipulating acl
aclHelper = new AclHelper(store);
@ -75,9 +70,7 @@ describe.each(stores)('An LDP handler with auth using %s', (name, { storeConfig,
afterAll(async(): Promise<void> => {
await teardown();
await new Promise((resolve, reject): void => {
server.close((error): void => error ? reject(error) : resolve());
});
await app.stop();
});
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 type { Server } from 'http';
import fetch from 'cross-fetch';
import { DataFactory, Parser } from 'n3';
import { joinFilePath, PIM, RDF } from '../../src/';
import type { Initializer } from '../../src/';
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
import type { App } from '../../src/';
import { LDP } from '../../src/util/Vocabularies';
import { deleteResource, expectQuads, getResource, patchResource, postResource, putResource } from '../util/FetchUtil';
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 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 => {
let server: Server;
let initializer: Initializer;
let factory: HttpServerFactory;
let app: App;
beforeAll(async(): Promise<void> => {
const variables: Record<string, any> = {
'urn:solid-server:default:variable:baseUrl': baseUrl,
'urn:solid-server:default:variable:showStackTrace': true,
const variables = {
...getDefaultVariables(port, baseUrl),
'urn:solid-server:default:variable:rootFilePath': rootFilePath,
};
// Create and initialize the server
// Create and start the server
const instances = await instantiateFromConfig(
'urn:solid-server:test:Instances',
[
@ -47,17 +48,14 @@ describe.each(stores)('An LDP handler allowing all requests %s', (name, { storeC
],
variables,
) as Record<string, any>;
({ factory, initializer } = instances);
({ app } = instances);
await initializer.handleSafe();
server = factory.startServer(port);
await app.start();
});
afterAll(async(): Promise<void> => {
await teardown();
await new Promise((resolve, reject): void => {
server.close((error): void => error ? reject(error) : resolve());
});
await app.stop();
});
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 { WrappedExpiringReadWriteLocker } from '../../src/util/locking/WrappedExpiringReadWriteLocker';
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');
describe('A LockingResourceStore', (): void => {
@ -44,7 +41,7 @@ describe('A LockingResourceStore', (): void => {
);
// Initialize store
const initializer = new RootContainerInitializer({ store: source, baseUrl: BASE });
const initializer = new RootContainerInitializer({ store: source, baseUrl: base });
await initializer.handleSafe();
locker = new EqualReadWriteLocker(new SingleThreadedResourceLocker());

View File

@ -1,9 +1,8 @@
import type { Server } from 'http';
import fetch from 'cross-fetch';
import type { RedisResourceLocker } from '../../src';
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
import type { App, RedisResourceLocker } from '../../src';
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
@ -11,28 +10,21 @@ import { getTestConfigPath, instantiateFromConfig } from './Config';
describeIf('docker', 'A server with a RedisResourceLocker as ResourceLocker', (): void => {
const port = getPort('RedisResourceLocker');
const baseUrl = `http://localhost:${port}/`;
let server: Server;
let app: App;
let locker: RedisResourceLocker;
let factory: HttpServerFactory;
beforeAll(async(): Promise<void> => {
const instances = await instantiateFromConfig(
'urn:solid-server:test:Instances',
getTestConfigPath('run-with-redlock.json'),
{
'urn:solid-server:default:variable:baseUrl': baseUrl,
'urn:solid-server:default:variable:showStackTrace': true,
},
getDefaultVariables(port, baseUrl),
) as Record<string, any>;
({ factory, locker } = instances);
server = factory.startServer(port);
({ app, locker } = instances);
await app.start();
});
afterAll(async(): Promise<void> => {
await locker.finalize();
await new Promise<void>((resolve, reject): void => {
server.close((error): void => error ? reject(error) : resolve());
});
await app.stop();
});
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 type { Initializer } from '../../src/init/Initializer';
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
import type { WrappedExpiringStorage } from '../../src/storage/keyvalue/WrappedExpiringStorage';
import type { App } from '../../src/init/App';
import { getPort } from '../util/Util';
import { getTestConfigPath, instantiateFromConfig } from './Config';
import { getDefaultVariables, getTestConfigPath, instantiateFromConfig } from './Config';
const port = getPort('ServerFetch');
const baseUrl = `http://localhost:${port}/`;
// Some tests with real Requests/Responses until the mocking library has been removed from the tests
describe('A Solid server', (): void => {
let server: Server;
let initializer: Initializer;
let expiringStorage: WrappedExpiringStorage<any, any>;
let factory: HttpServerFactory;
let app: App;
beforeAll(async(): Promise<void> => {
const instances = await instantiateFromConfig(
'urn:solid-server:test:Instances',
getTestConfigPath('server-memory.json'),
{
'urn:solid-server:default:variable:baseUrl': baseUrl,
'urn:solid-server:default:variable:showStackTrace': true,
'urn:solid-server:default:variable:idpTemplateFolder': '',
},
getDefaultVariables(port, baseUrl),
) as Record<string, any>;
({ factory, initializer, expiringStorage } = instances);
await initializer.handleSafe();
server = factory.startServer(port);
({ app } = instances);
await app.start();
});
afterAll(async(): Promise<void> => {
expiringStorage.finalize();
await new Promise<void>((resolve, reject): void => {
server.close((error): void => error ? reject(error) : resolve());
});
await app.stop();
});
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 type { Server } from 'http';
import { joinFilePath } from '../../src/';
import type { Initializer } from '../../src/';
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
import type { App } from '../../src/';
import { putResource } from '../util/FetchUtil';
import { describeIf, getPort } from '../util/Util';
import { getPresetConfigPath, getTestConfigPath, instantiateFromConfig } from './Config';
import { getDefaultVariables, getPresetConfigPath, getTestConfigPath, instantiateFromConfig } from './Config';
const port = getPort('SparqlStorage');
const baseUrl = `http://localhost:${port}/`;
describeIf('docker', 'A server with a SPARQL endpoint as storage', (): void => {
let server: Server;
let initializer: Initializer;
let factory: HttpServerFactory;
let app: App;
beforeAll(async(): Promise<void> => {
const variables: Record<string, any> = {
'urn:solid-server:default:variable:baseUrl': baseUrl,
'urn:solid-server:default:variable:showStackTrace': true,
const variables = {
...getDefaultVariables(port, baseUrl),
'urn:solid-server:default:variable:sparqlEndpoint': 'http://localhost:4000/sparql',
};
// Create and initialize the server
// Create and start the server
const instances = await instantiateFromConfig(
'urn:solid-server:test:Instances',
[
@ -31,16 +26,13 @@ describeIf('docker', 'A server with a SPARQL endpoint as storage', (): void => {
],
variables,
) as Record<string, any>;
({ factory, initializer } = instances);
({ app } = instances);
await initializer.handleSafe();
server = factory.startServer(port);
await app.start();
});
afterAll(async(): Promise<void> => {
await new Promise((resolve, reject): void => {
server.close((error): void => error ? reject(error) : resolve());
});
await app.stop();
});
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 fetch from 'cross-fetch';
import type { Initializer } from '../../src/init/Initializer';
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
import type { WrappedExpiringStorage } from '../../src/storage/keyvalue/WrappedExpiringStorage';
import { joinFilePath } from '../../src/util/PathUtil';
import type { App } from '../../src/init/App';
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 baseUrl = `http://localhost:${port}/`;
@ -25,23 +28,18 @@ const stores: [string, any][] = [
// Simulating subdomains using the forwarded header so no DNS changes are required
describe.each(stores)('A subdomain server with %s', (name, { storeConfig, teardown }): void => {
let server: Server;
let initializer: Initializer;
let factory: HttpServerFactory;
let expiringStorage: WrappedExpiringStorage<any, any>;
let app: App;
const settings = { podName: 'alice', webId: 'http://test.com/#alice', email: 'alice@test.email', createPod: true };
const podHost = `alice.localhost:${port}`;
const podUrl = `http://${podHost}/`;
beforeAll(async(): Promise<void> => {
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: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(
'urn:solid-server:test:Instances',
[
@ -50,18 +48,14 @@ describe.each(stores)('A subdomain server with %s', (name, { storeConfig, teardo
],
variables,
) as Record<string, any>;
({ factory, initializer, expiringStorage } = instances);
({ app } = instances);
await initializer.handleSafe();
server = factory.startServer(port);
await app.start();
});
afterAll(async(): Promise<void> => {
expiringStorage.finalize();
await new Promise((resolve, reject): void => {
server.close((error): void => error ? reject(error) : resolve());
});
await teardown();
await app.stop();
});
describe('handling resources', (): void => {

View File

@ -1,37 +1,31 @@
import type { Server } from 'http';
import fetch from 'cross-fetch';
import WebSocket from 'ws';
import type { HttpServerFactory } from '../../src/server/HttpServerFactory';
import type { App } from '../../src/init/App';
import { getPort } from '../util/Util';
import { getTestConfigPath, instantiateFromConfig } from './Config';
import { getDefaultVariables, getTestConfigPath, instantiateFromConfig } from './Config';
const port = getPort('WebSocketsProtocol');
const serverUrl = `http://localhost:${port}/`;
const headers = { forwarded: 'host=example.pod;proto=https' };
describe('A server with the Solid WebSockets API behind a proxy', (): void => {
let server: Server;
let app: App;
beforeAll(async(): Promise<void> => {
const factory = await instantiateFromConfig(
'urn:solid-server:default:ServerFactory',
app = await instantiateFromConfig(
'urn:solid-server:default:App',
getTestConfigPath('server-without-auth.json'),
{
'urn:solid-server:default:variable:baseUrl': 'https://example.pod/',
'urn:solid-server:default:variable:showStackTrace': true,
},
) as HttpServerFactory;
server = factory.startServer(port);
getDefaultVariables(port, 'https://example.pod/'),
) as App;
await app.start();
});
afterAll(async(): Promise<void> => {
await new Promise<void>((resolve, reject): void => {
server.close((error): void => error ? reject(error) : resolve());
});
await app.stop();
});
it('returns a 404 if no data was initialized.', async(): Promise<void> => {
const response = await fetch(serverUrl, { headers });
it('returns a 404 if there is no data.', async(): Promise<void> => {
const response = await fetch(`${serverUrl}foo`, { headers });
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",
"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/middleware/no-websockets.json",
"files-scs:config/http/server-factory/no-websockets.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/authorization/webacl.json",
"files-scs:config/ldp/handler/default.json",
@ -28,17 +29,12 @@
"@type": "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": "app",
"RecordObject:_record_value": { "@id": "urn:solid-server:default:App" }
},
{
"RecordObject:_record_key": "store",
"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",
"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/middleware/no-websockets.json",
"files-scs:config/http/server-factory/no-websockets.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/authorization/allow-everything.json",
"files-scs:config/ldp/handler/default.json",
@ -29,17 +30,12 @@
"@type": "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": "app",
"RecordObject:_record_value": { "@id": "urn:solid-server:default:App" }
},
{
"RecordObject:_record_key": "locker",
"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",
"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/middleware/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/ownership/unsafe-no-check.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/authorization/webacl.json",
"files-scs:config/ldp/handler/default.json",
@ -33,24 +34,10 @@
"@type": "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" }
},
{
"comment": "Timer needs to be stopped when tests are finished.",
"RecordObject:_record_key": "expiringStorage",
"RecordObject:_record_value": { "@id": "urn:solid-server:default:ExpiringIdpStorage" }
"RecordObject:_record_key": "app",
"RecordObject:_record_value": { "@id": "urn:solid-server:default:App" }
}
]
},
{
"@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",
"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/middleware/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/ownership/token.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/authorization/webacl.json",
"files-scs:config/ldp/handler/default.json",
@ -31,18 +32,8 @@
"@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" }
},
{
"comment": "Timer needs to be stopped when tests are finished.",
"RecordObject:_record_key": "expiringStorage",
"RecordObject:_record_value": { "@id": "urn:solid-server:default:ExpiringIdpStorage" }
"RecordObject:_record_key": "app",
"RecordObject:_record_value": { "@id": "urn:solid-server:default:App" }
}
]
},

View File

@ -1,6 +1,8 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"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/middleware/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/ownership/unsafe-no-check.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/authorization/webacl.json",
"files-scs:config/ldp/handler/default.json",
@ -32,18 +33,8 @@
"@type": "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" }
},
{
"comment": "Timer needs to be stopped when tests are finished.",
"RecordObject:_record_key": "expiringStorage",
"RecordObject:_record_value": { "@id": "urn:solid-server:default:ExpiringIdpStorage" }
"RecordObject:_record_key": "app",
"RecordObject:_record_value": { "@id": "urn:solid-server:default:App" }
}
]
}

View File

@ -1,11 +1,12 @@
{
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
"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/middleware/websockets.json",
"files-scs:config/http/server-factory/websockets.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/authorization/allow-everything.json",
"files-scs:config/ldp/handler/default.json",
@ -23,22 +24,6 @@
"files-scs:config/util/variables/default.json"
],
"@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",
"@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 type { App } from '../../../src/init/App';
import { AppRunner } from '../../../src/init/AppRunner';
import type { Initializer } from '../../../src/init/Initializer';
import { joinFilePath } from '../../../src/util/PathUtil';
const initializer: jest.Mocked<Initializer> = {
handleSafe: jest.fn(),
const app: jest.Mocked<App> = {
start: jest.fn(),
} as any;
const manager: jest.Mocked<ComponentsManager<Initializer>> = {
instantiate: jest.fn(async(): Promise<Initializer> => initializer),
const manager: jest.Mocked<ComponentsManager<App>> = {
instantiate: jest.fn(async(): Promise<App> => app),
configRegistry: {
register: jest.fn(),
},
@ -17,7 +17,7 @@ const manager: jest.Mocked<ComponentsManager<Initializer>> = {
jest.mock('componentsjs', (): any => ({
// eslint-disable-next-line @typescript-eslint/naming-convention
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'));
expect(manager.instantiate).toHaveBeenCalledTimes(1);
expect(manager.instantiate).toHaveBeenCalledWith(
'urn:solid-server:default:Initializer',
'urn:solid-server:default:App',
{
variables: {
'urn:solid-server:default:variable:port': 3000,
@ -74,8 +74,8 @@ describe('AppRunner', (): void => {
},
},
);
expect(initializer.handleSafe).toHaveBeenCalledTimes(1);
expect(initializer.handleSafe).toHaveBeenCalledWith();
expect(app.start).toHaveBeenCalledTimes(1);
expect(app.start).toHaveBeenCalledWith();
});
});
@ -85,7 +85,7 @@ describe('AppRunner', (): void => {
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 => {
setImmediate(resolve);
});
@ -101,7 +101,7 @@ describe('AppRunner', (): void => {
.toHaveBeenCalledWith(joinFilePath(__dirname, '/../../../config/default.json'));
expect(manager.instantiate).toHaveBeenCalledTimes(1);
expect(manager.instantiate).toHaveBeenCalledWith(
'urn:solid-server:default:Initializer',
'urn:solid-server:default:App',
{
variables: {
'urn:solid-server:default:variable:port': 3000,
@ -115,8 +115,8 @@ describe('AppRunner', (): void => {
},
},
);
expect(initializer.handleSafe).toHaveBeenCalledTimes(1);
expect(initializer.handleSafe).toHaveBeenCalledWith();
expect(app.start).toHaveBeenCalledTimes(1);
expect(app.start).toHaveBeenCalledWith();
});
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 => {
setImmediate(resolve);
});
@ -150,7 +150,7 @@ describe('AppRunner', (): void => {
expect(manager.configRegistry.register).toHaveBeenCalledTimes(1);
expect(manager.configRegistry.register).toHaveBeenCalledWith('/var/cwd/myconfig.json');
expect(manager.instantiate).toHaveBeenCalledWith(
'urn:solid-server:default:Initializer',
'urn:solid-server:default:App',
{
variables: {
'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 => {
setImmediate(resolve);
});
@ -197,7 +197,7 @@ describe('AppRunner', (): void => {
expect(manager.configRegistry.register).toHaveBeenCalledTimes(1);
expect(manager.configRegistry.register).toHaveBeenCalledWith('/var/cwd/myconfig.json');
expect(manager.instantiate).toHaveBeenCalledWith(
'urn:solid-server:default:Initializer',
'urn:solid-server:default:App',
{
variables: {
'urn:solid-server:default:variable:baseUrl': 'http://pod.example/',
@ -231,7 +231,7 @@ describe('AppRunner', (): void => {
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 => {
setImmediate(resolve);
});
@ -245,7 +245,7 @@ describe('AppRunner', (): void => {
expect(manager.configRegistry.register).toHaveBeenCalledTimes(1);
expect(manager.configRegistry.register).toHaveBeenCalledWith('/var/cwd/myconfig.json');
expect(manager.instantiate).toHaveBeenCalledWith(
'urn:solid-server:default:Initializer',
'urn:solid-server:default:App',
{
variables: {
'urn:solid-server:default:variable:baseUrl': 'http://pod.example/',
@ -269,7 +269,7 @@ describe('AppRunner', (): void => {
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 => {
setImmediate(resolve);
});
@ -285,12 +285,12 @@ describe('AppRunner', (): 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({
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 => {
setImmediate(resolve);
});
@ -305,7 +305,7 @@ describe('AppRunner', (): void => {
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 => {
setImmediate(resolve);
});
@ -320,7 +320,7 @@ describe('AppRunner', (): void => {
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 => {
setImmediate(resolve);
});
@ -335,7 +335,7 @@ describe('AppRunner', (): void => {
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 => {
setImmediate(resolve);
});
@ -350,7 +350,7 @@ describe('AppRunner', (): void => {
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 => {
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();
});
});