feat: Accept both Settings and VariableBindings to create an App

This commit is contained in:
Joachim Van Herwegen 2022-07-15 15:27:35 +02:00
parent 8609704528
commit f609f1a9c5
19 changed files with 286 additions and 131 deletions

View File

@ -19,7 +19,7 @@
"Promise",
"Readonly",
"RegExp",
"Settings",
"Shorthand",
"Template",
"TemplateEngine",
"ValuePreferencesArg",

View File

@ -7,6 +7,7 @@
you should also upgrade to prevent warnings and conflicts.
- A new FileSystemResourceLocker has been added. It allows for true threadsafe locking without external dependencies.
- The CSS can now run multithreaded with multiple workers, this is done with the `--workers` or `-w` flag.
- When starting the server through code, it is now possible to provide CLI value bindings as well in `AppRunner`.
### Data migration
The following actions are required if you are upgrading from a v4 server and want to retain your data.
@ -25,7 +26,7 @@ The following changes pertain to the imports in the default configs:
making them threadsafe.
The following changes are relevant for v4 custom configs that replaced certain features.
- `config/app/variables/cli.json` was changed to support the new `YargsCliExtractor` format.
- `config/app/variables/*` was changed to support the new `YargsCliExtractor` format and `SettingsResolver` rename.
- `config/util/resource-locker/memory.json` had the locker @type changed from `SingleThreadedResourceLocker` to `MemoryResourceLocker`.
- The content-length parser has been moved from the default configuration to the quota configurations.
- `/ldp/metadata-parser/default.json`
@ -73,6 +74,7 @@ These changes are relevant if you wrote custom modules for the server that depen
- `ResourceStore` functions that change a resource now return metadata for every changed resource.
- All permission related interfaces have changed to support permissions over multiple identifiers.
- `IdentifierStrategy` has a new `contains` method.
- `SettingsResolver` was renamed to `ShorthandResolver`, together with all related classes and parameters.
A new interface `SingleThreaded` has been added. This empty interface can be implemented to mark a component as not-threadsafe. When the CSS starts in multithreaded mode, it will error and halt if any SingleThreaded components are instantiated.

View File

@ -6,11 +6,11 @@
],
"@graph": [
{
"comment": "Combines a CliExtractor and SettingsResolver to be used by the AppRunner.",
"comment": "Combines a CliExtractor and ShorthandResolver to be used by the AppRunner.",
"@id": "urn:solid-server-app-setup:default:CliResolver",
"@type": "CliResolver",
"cliExtractor": { "@id": "urn:solid-server-app-setup:default:CliExtractor" },
"settingsResolver": { "@id": "urn:solid-server-app-setup:default:SettingsResolver" }
"shorthandResolver": { "@id": "urn:solid-server-app-setup:default:ShorthandResolver" }
}
]
}

View File

@ -3,72 +3,72 @@
"@graph": [
{
"comment": "Converts an input key/value object into an object mapping values to Components.js variables",
"@id": "urn:solid-server-app-setup:default:SettingsResolver",
"@type": "CombinedSettingsResolver",
"@id": "urn:solid-server-app-setup:default:ShorthandResolver",
"@type": "CombinedShorthandResolver",
"resolvers": [
{
"CombinedSettingsResolver:_resolvers_key": "urn:solid-server:default:variable:baseUrl",
"CombinedSettingsResolver:_resolvers_value": {
"CombinedShorthandResolver:_resolvers_key": "urn:solid-server:default:variable:baseUrl",
"CombinedShorthandResolver:_resolvers_value": {
"@type": "BaseUrlExtractor"
}
},
{
"CombinedSettingsResolver:_resolvers_key": "urn:solid-server:default:variable:loggingLevel",
"CombinedSettingsResolver:_resolvers_value": {
"CombinedShorthandResolver:_resolvers_key": "urn:solid-server:default:variable:loggingLevel",
"CombinedShorthandResolver:_resolvers_value": {
"@type": "KeyExtractor",
"key": "loggingLevel",
"defaultValue": "info"
}
},
{
"CombinedSettingsResolver:_resolvers_key": "urn:solid-server:default:variable:port",
"CombinedSettingsResolver:_resolvers_value": {
"CombinedShorthandResolver:_resolvers_key": "urn:solid-server:default:variable:port",
"CombinedShorthandResolver:_resolvers_value": {
"@type": "KeyExtractor",
"key": "port",
"defaultValue": 3000
}
},
{
"CombinedSettingsResolver:_resolvers_key": "urn:solid-server:default:variable:rootFilePath",
"CombinedSettingsResolver:_resolvers_value": {
"CombinedShorthandResolver:_resolvers_key": "urn:solid-server:default:variable:rootFilePath",
"CombinedShorthandResolver:_resolvers_value": {
"@type": "AssetPathExtractor",
"key": "rootFilePath",
"defaultPath": "./"
}
},
{
"CombinedSettingsResolver:_resolvers_key": "urn:solid-server:default:variable:sparqlEndpoint",
"CombinedSettingsResolver:_resolvers_value": {
"CombinedShorthandResolver:_resolvers_key": "urn:solid-server:default:variable:sparqlEndpoint",
"CombinedShorthandResolver:_resolvers_value": {
"@type": "KeyExtractor",
"key": "sparqlEndpoint"
}
},
{
"CombinedSettingsResolver:_resolvers_key": "urn:solid-server:default:variable:showStackTrace",
"CombinedSettingsResolver:_resolvers_value": {
"CombinedShorthandResolver:_resolvers_key": "urn:solid-server:default:variable:showStackTrace",
"CombinedShorthandResolver:_resolvers_value": {
"@type": "KeyExtractor",
"key": "showStackTrace",
"defaultValue": false
}
},
{
"CombinedSettingsResolver:_resolvers_key": "urn:solid-server:default:variable:podConfigJson",
"CombinedSettingsResolver:_resolvers_value": {
"CombinedShorthandResolver:_resolvers_key": "urn:solid-server:default:variable:podConfigJson",
"CombinedShorthandResolver:_resolvers_value": {
"@type": "AssetPathExtractor",
"key": "podConfigJson",
"defaultPath": "./pod-config.json"
}
},
{
"CombinedSettingsResolver:_resolvers_key": "urn:solid-server:default:variable:seededPodConfigJson",
"CombinedSettingsResolver:_resolvers_value": {
"CombinedShorthandResolver:_resolvers_key": "urn:solid-server:default:variable:seededPodConfigJson",
"CombinedShorthandResolver:_resolvers_value": {
"@type": "AssetPathExtractor",
"key": "seededPodConfigJson"
}
},
{
"CombinedSettingsResolver:_resolvers_key": "urn:solid-server:default:variable:workers",
"CombinedSettingsResolver:_resolvers_value": {
"CombinedShorthandResolver:_resolvers_key": "urn:solid-server:default:variable:workers",
"CombinedShorthandResolver:_resolvers_value": {
"@type": "KeyExtractor",
"key": "workers",
"defaultValue": 1

View File

@ -67,19 +67,19 @@
},
{
"comment": "Adds resolvers to assign the CLI values to the Components.js variables.",
"@id": "urn:solid-server-app-setup:default:SettingsResolver",
"@type": "CombinedSettingsResolver",
"@id": "urn:solid-server-app-setup:default:ShorthandResolver",
"@type": "CombinedShorthandResolver",
"resolvers": [
{
"CombinedSettingsResolver:_resolvers_key": "urn:solid-server:custom:variable:httpsKey",
"CombinedSettingsResolver:_resolvers_value": {
"CombinedShorthandResolver:_resolvers_key": "urn:solid-server:custom:variable:httpsKey",
"CombinedShorthandResolver:_resolvers_value": {
"@type": "KeyExtractor",
"key": "httpsKey"
}
},
{
"CombinedSettingsResolver:_resolvers_key": "urn:solid-server:custom:variable:httpsCert",
"CombinedSettingsResolver:_resolvers_value": {
"CombinedShorthandResolver:_resolvers_key": "urn:solid-server:custom:variable:httpsCert",
"CombinedShorthandResolver:_resolvers_value": {
"@type": "KeyExtractor",
"key": "httpsCert"
}

View File

@ -214,11 +214,11 @@ export * from './init/cli/YargsCliExtractor';
export * from './init/variables/extractors/KeyExtractor';
export * from './init/variables/extractors/AssetPathExtractor';
export * from './init/variables/extractors/BaseUrlExtractor';
export * from './init/variables/extractors/SettingsExtractor';
export * from './init/variables/extractors/ShorthandExtractor';
// Init/Variables
export * from './init/variables/CombinedSettingsResolver';
export * from './init/variables/SettingsResolver';
export * from './init/variables/CombinedShorthandResolver';
export * from './init/variables/ShorthandResolver';
// Init
export * from './init/App';

View File

@ -9,9 +9,11 @@ import { createErrorMessage, isError } from '../util/errors/ErrorUtil';
import { InternalServerError } from '../util/errors/InternalServerError';
import { resolveModulePath, resolveAssetPath } from '../util/PathUtil';
import type { App } from './App';
import type { CliExtractor } from './cli/CliExtractor';
import type { CliResolver } from './CliResolver';
import { listSingleThreadedComponents } from './cluster/SingleThreaded';
import type { CliArgv, VariableBindings } from './variables/Types';
import type { ShorthandResolver } from './variables/ShorthandResolver';
import type { CliArgv, Shorthand, VariableBindings } from './variables/Types';
const DEFAULT_CONFIG = resolveModulePath('config/default.json');
@ -33,41 +35,62 @@ export class AppRunner {
/**
* Starts the server with a given config.
* This method can be used to start the server from within another JavaScript application.
*
* Keys of the `variableBindings` object should be Components.js variables.
* E.g.: `{ 'urn:solid-server:default:variable:rootFilePath': '.data' }`.
*
* `shorthand` are CLI argument names and their corresponding values.
* E.g.: `{ rootFilePath: '.data' }`.
* Abbreviated parameter names can not be used, so `{ f: '.data' }` would not work.
*
* The values in `variableBindings` take priority over those in `shorthand`.
*
* @param loaderProperties - Components.js loader properties.
* @param configFile - Path to the server config file.
* @param variableBindings - Parameters to pass into the VariableResolver.
* @param variableBindings - Bindings of Components.js variables.
* @param shorthand - Shorthand values that need to be resolved.
*/
public async run(
loaderProperties: IComponentsManagerBuilderOptions<App>,
configFile: string,
variableBindings: VariableBindings,
variableBindings?: VariableBindings,
shorthand?: Shorthand,
): Promise<void> {
const app = await this.create(loaderProperties, configFile, variableBindings);
const app = await this.create(loaderProperties, configFile, variableBindings, shorthand);
await app.start();
}
/**
* Returns an App object, created with the given config, that can start and stop the Solid server.
*
* Keys of the `variableBindings` object should be Components.js variables.
* E.g.: `{ 'urn:solid-server:default:variable:rootFilePath': '.data' }`.
*
* `shorthand` are CLI argument names and their corresponding values.
* E.g.: `{ rootFilePath: '.data' }`.
* Abbreviated parameter names can not be used, so `{ f: '.data' }` would not work.
*
* The values in `variableBindings` take priority over those in `shorthand`.
*
* @param loaderProperties - Components.js loader properties.
* @param configFile - Path to the server config file.
* @param variableBindings - Bindings of Components.js variables.
* @param shorthand - Shorthand values that need to be resolved.
*/
public async create(
loaderProperties: IComponentsManagerBuilderOptions<App>,
configFile: string,
variableBindings: VariableBindings,
variableBindings?: VariableBindings,
shorthand?: Shorthand,
): Promise<App> {
// Create a resolver to translate (non-core) CLI parameters into values for variables
const componentsManager = await this.createComponentsManager<App>(loaderProperties, configFile);
const componentsManager = await this.createComponentsManager<any>(loaderProperties, configFile);
// Create the application using the translated variable values
return await this.createApp(componentsManager, variableBindings);
const cliResolver = await this.createCliResolver(componentsManager);
const parsedVariables = await this.resolveShorthand(cliResolver.shorthandResolver, { ...shorthand });
// Create the application using the translated variable values.
// `variableBindings` override those resolved from the `shorthand` input.
return this.createApp(componentsManager, { ...parsedVariables, ...variableBindings });
}
/**
@ -138,7 +161,7 @@ export class AppRunner {
}
// Build the CLI components and use them to generate values for the Components.js variables
const variables = await this.resolveVariables(componentsManager, argv);
const variables = await this.cliToVariables(componentsManager, argv);
// Build and start the actual server application using the generated variable values
return await this.createApp(componentsManager, variables);
@ -157,20 +180,50 @@ export class AppRunner {
}
/**
* Handles the first Components.js instantiation,
* where CLI settings and variable mappings are created.
* Handles the first Components.js instantiation.
* Uses it to extract the CLI shorthand values and use those to create variable bindings.
*/
private async resolveVariables(componentsManager: ComponentsManager<CliResolver>, argv: string[]):
private async cliToVariables(componentsManager: ComponentsManager<CliResolver>, argv: CliArgv):
Promise<VariableBindings> {
const cliResolver = await this.createCliResolver(componentsManager);
const shorthand = await this.extractShorthand(cliResolver.cliExtractor, argv);
return await this.resolveShorthand(cliResolver.shorthandResolver, shorthand);
}
/**
* Instantiates the {@link CliResolver}.
*/
private async createCliResolver(componentsManager: ComponentsManager<CliResolver>): Promise<CliResolver> {
try {
// Create a CliResolver, which combines a CliExtractor and a VariableResolver
const resolver = await componentsManager.instantiate(DEFAULT_CLI_RESOLVER, {});
// Convert CLI args to CLI bindings
const cliValues = await resolver.cliExtractor.handleSafe(argv);
// Convert CLI bindings into variable bindings
return await resolver.settingsResolver.handleSafe(cliValues);
return await componentsManager.instantiate(DEFAULT_CLI_RESOLVER, {});
} catch (error: unknown) {
this.resolveError(`Could not load the config variables`, error);
this.resolveError(`Could not create the CLI resolver`, error);
}
}
/**
* Uses the {@link CliExtractor} to convert the CLI args to a {@link Shorthand} object.
*/
private async extractShorthand(cliExtractor: CliExtractor, argv: CliArgv): Promise<Shorthand> {
try {
// Convert CLI args to CLI bindings
return await cliExtractor.handleSafe(argv);
} catch (error: unknown) {
this.resolveError(`Could not parse the CLI parameters`, error);
}
}
/**
* Uses the {@link ShorthandResolver} to convert {@link Shorthand} to {@link VariableBindings} .
*/
private async resolveShorthand(shorthandResolver: ShorthandResolver, shorthand: Shorthand):
Promise<VariableBindings> {
try {
// Convert CLI bindings into variable bindings
return await shorthandResolver.handleSafe(shorthand);
} catch (error: unknown) {
this.resolveError(`Could not resolve the shorthand values`, error);
}
}

View File

@ -1,16 +1,16 @@
import type { CliExtractor } from './cli/CliExtractor';
import type { SettingsResolver } from './variables/SettingsResolver';
import type { ShorthandResolver } from './variables/ShorthandResolver';
/**
* A class that combines a {@link CliExtractor} and a {@link SettingsResolver}.
* A class that combines a {@link CliExtractor} and a {@link ShorthandResolver}.
* Mainly exists so both such classes can be generated in a single Components.js instance.
*/
export class CliResolver {
public readonly cliExtractor: CliExtractor;
public readonly settingsResolver: SettingsResolver;
public readonly shorthandResolver: ShorthandResolver;
public constructor(cliExtractor: CliExtractor, settingsResolver: SettingsResolver) {
public constructor(cliExtractor: CliExtractor, shorthandResolver: ShorthandResolver) {
this.cliExtractor = cliExtractor;
this.settingsResolver = settingsResolver;
this.shorthandResolver = shorthandResolver;
}
}

View File

@ -1,5 +1,5 @@
import { AsyncHandler } from '../../util/handlers/AsyncHandler';
import type { CliArgv, Settings } from '../variables/Types';
import type { CliArgv, Shorthand } from '../variables/Types';
/**
* Converts the input CLI arguments into an easily parseable key/value object.
@ -15,4 +15,4 @@ import type { CliArgv, Settings } from '../variables/Types';
* - -m / \--mainModulePath
* - -l / \--loggingLevel
*/
export abstract class CliExtractor extends AsyncHandler<CliArgv, Settings> {}
export abstract class CliExtractor extends AsyncHandler<CliArgv, Shorthand> {}

View File

@ -1,14 +1,14 @@
import { createErrorMessage } from '../../util/errors/ErrorUtil';
import type { SettingsExtractor } from './extractors/SettingsExtractor';
import { SettingsResolver } from './SettingsResolver';
import type { ShorthandExtractor } from './extractors/ShorthandExtractor';
import { ShorthandResolver } from './ShorthandResolver';
/**
* Generates variable values by running a set of {@link SettingsExtractor}s on the input.
* Generates variable values by running a set of {@link ShorthandExtractor}s on the input.
*/
export class CombinedSettingsResolver extends SettingsResolver {
public readonly resolvers: Record<string, SettingsExtractor>;
export class CombinedShorthandResolver extends ShorthandResolver {
public readonly resolvers: Record<string, ShorthandExtractor>;
public constructor(resolvers: Record<string, SettingsExtractor>) {
public constructor(resolvers: Record<string, ShorthandExtractor>) {
super();
this.resolvers = resolvers;
}

View File

@ -1,9 +1,9 @@
import { AsyncHandler } from '../../util/handlers/AsyncHandler';
import type { Settings, VariableBindings } from './Types';
import type { Shorthand, VariableBindings } from './Types';
/**
* Converts a key/value object, extracted from the CLI or passed as a parameter,
* into a new key/value object where the keys are variables defined in the Components.js configuration.
* The resulting values are the values that should be assigned to those variables.
*/
export abstract class SettingsResolver extends AsyncHandler<Settings, VariableBindings> {}
export abstract class ShorthandResolver extends AsyncHandler<Shorthand, VariableBindings> {}

View File

@ -8,7 +8,7 @@ export type CliArgv = string[];
/**
* A key/value mapping of parsed command line arguments.
*/
export type Settings = Record<string, unknown>;
export type Shorthand = Record<string, unknown>;
/**
* A key/value mapping of Components.js variables.

View File

@ -1,12 +1,13 @@
import { resolveAssetPath } from '../../../util/PathUtil';
import type { Settings } from '../Types';
import { SettingsExtractor } from './SettingsExtractor';
import type { Shorthand } from '../Types';
import { ShorthandExtractor } from './ShorthandExtractor';
/**
* A {@link SettingsExtractor} that converts a path value to an absolute asset path by making use of `resolveAssetPath`.
* A {@link ShorthandExtractor} that converts a path value to an absolute asset path
* by making use of `resolveAssetPath`.
* Returns the default path in case it is defined and no path was found in the map.
*/
export class AssetPathExtractor extends SettingsExtractor {
export class AssetPathExtractor extends ShorthandExtractor {
private readonly key: string;
private readonly defaultPath?: string;
@ -16,7 +17,7 @@ export class AssetPathExtractor extends SettingsExtractor {
this.defaultPath = defaultPath;
}
public async handle(args: Settings): Promise<unknown> {
public async handle(args: Shorthand): Promise<unknown> {
const path = args[this.key] ?? this.defaultPath;
if (path) {
if (typeof path !== 'string') {

View File

@ -1,12 +1,12 @@
import { ensureTrailingSlash } from '../../../util/PathUtil';
import type { Settings } from '../Types';
import { SettingsExtractor } from './SettingsExtractor';
import type { Shorthand } from '../Types';
import { ShorthandExtractor } from './ShorthandExtractor';
/**
* A {@link SettingsExtractor} that that generates the base URL based on the input `baseUrl` value,
* A {@link ShorthandExtractor} that that generates the base URL based on the input `baseUrl` value,
* or by using the port if the first isn't provided.
*/
export class BaseUrlExtractor extends SettingsExtractor {
export class BaseUrlExtractor extends ShorthandExtractor {
private readonly defaultPort: number;
public constructor(defaultPort = 3000) {
@ -14,7 +14,7 @@ export class BaseUrlExtractor extends SettingsExtractor {
this.defaultPort = defaultPort;
}
public async handle(args: Settings): Promise<unknown> {
public async handle(args: Shorthand): Promise<unknown> {
if (typeof args.baseUrl === 'string') {
return ensureTrailingSlash(args.baseUrl);
}

View File

@ -1,11 +1,11 @@
import type { Settings } from '../Types';
import { SettingsExtractor } from './SettingsExtractor';
import type { Shorthand } from '../Types';
import { ShorthandExtractor } from './ShorthandExtractor';
/**
* A simple {@link SettingsExtractor} that extracts a single value from the input map.
* A simple {@link ShorthandExtractor} that extracts a single value from the input map.
* Returns the default value if it was defined in case no value was found in the map.
*/
export class KeyExtractor extends SettingsExtractor {
export class KeyExtractor extends ShorthandExtractor {
private readonly key: string;
private readonly defaultValue: unknown;
@ -15,7 +15,7 @@ export class KeyExtractor extends SettingsExtractor {
this.defaultValue = defaultValue;
}
public async handle(args: Settings): Promise<unknown> {
public async handle(args: Shorthand): Promise<unknown> {
return typeof args[this.key] === 'undefined' ? this.defaultValue : args[this.key];
}
}

View File

@ -1,7 +1,7 @@
import { AsyncHandler } from '../../../util/handlers/AsyncHandler';
import type { Settings } from '../Types';
import type { Shorthand } from '../Types';
/**
* A handler that computes a specific value from a given map of values.
*/
export abstract class SettingsExtractor extends AsyncHandler<Settings, unknown> {}
export abstract class ShorthandExtractor extends AsyncHandler<Shorthand, unknown> {}

View File

@ -3,7 +3,7 @@ import type { ClusterManager } from '../../../src';
import type { App } from '../../../src/init/App';
import { AppRunner } from '../../../src/init/AppRunner';
import type { CliExtractor } from '../../../src/init/cli/CliExtractor';
import type { SettingsResolver } from '../../../src/init/variables/SettingsResolver';
import type { ShorthandResolver } from '../../../src/init/variables/ShorthandResolver';
import { joinFilePath } from '../../../src/util/PathUtil';
import { flushPromises } from '../../util/Util';
@ -19,7 +19,7 @@ const defaultVariables = {
'urn:solid-server:default:variable:port': 3000,
'urn:solid-server:default:variable:loggingLevel': 'info',
};
const settingsResolver: jest.Mocked<SettingsResolver> = {
const shorthandResolver: jest.Mocked<ShorthandResolver> = {
handleSafe: jest.fn().mockResolvedValue(defaultVariables),
} as any;
@ -51,7 +51,7 @@ const app: jest.Mocked<App> = {
const manager: jest.Mocked<ComponentsManager<App>> = {
instantiate: jest.fn(async(iri: string): Promise<any> => {
switch (iri) {
case 'urn:solid-server-app-setup:default:CliResolver': return { cliExtractor, settingsResolver };
case 'urn:solid-server-app-setup:default:CliResolver': return { cliExtractor, shorthandResolver };
case 'urn:solid-server:default:App': return app;
default: throw new Error('unknown iri');
}
@ -86,13 +86,20 @@ describe('AppRunner', (): void => {
describe('create', (): void => {
it('creates an App with the provided settings.', async(): Promise<void> => {
const variables = {
'urn:solid-server:default:variable:port': 3000,
'urn:solid-server:default:variable:loggingLevel': 'info',
'urn:solid-server:default:variable:port': 4000,
'urn:solid-server:default:variable:rootFilePath': '/var/cwd/',
'urn:solid-server:default:variable:showStackTrace': false,
'urn:solid-server:default:variable:podConfigJson': '/var/cwd/pod-config.json',
'urn:solid-server:default:variable:seededPodConfigJson': '/var/cwd/seeded-pod-config.json',
};
const shorthand = {
logLevel: 'info',
};
const expectedVariables = {
...variables,
'urn:solid-server:default:variable:loggingLevel': 'info',
};
const createdApp = await new AppRunner().create(
{
mainModulePath: joinFilePath(__dirname, '../../../'),
@ -101,6 +108,7 @@ describe('AppRunner', (): void => {
},
joinFilePath(__dirname, '../../../config/default.json'),
variables,
shorthand,
);
expect(createdApp).toBe(app);
@ -113,10 +121,14 @@ describe('AppRunner', (): void => {
expect(manager.configRegistry.register).toHaveBeenCalledTimes(1);
expect(manager.configRegistry.register)
.toHaveBeenCalledWith(joinFilePath(__dirname, '/../../../config/default.json'));
expect(manager.instantiate).toHaveBeenCalledTimes(1);
expect(manager.instantiate).toHaveBeenNthCalledWith(1, 'urn:solid-server:default:App', { variables });
expect(manager.instantiate).toHaveBeenCalledTimes(2);
expect(manager.instantiate).toHaveBeenNthCalledWith(1, 'urn:solid-server-app-setup:default:CliResolver', {});
expect(manager.instantiate).toHaveBeenNthCalledWith(
2, 'urn:solid-server:default:App', { variables: expectedVariables },
);
expect(shorthandResolver.handleSafe).toHaveBeenCalledTimes(1);
expect(shorthandResolver.handleSafe).toHaveBeenLastCalledWith(shorthand);
expect(cliExtractor.handleSafe).toHaveBeenCalledTimes(0);
expect(settingsResolver.handleSafe).toHaveBeenCalledTimes(0);
expect(app.start).toHaveBeenCalledTimes(0);
expect(app.clusterManager.isSingleThreaded()).toBeFalsy();
});
@ -193,13 +205,20 @@ describe('AppRunner', (): void => {
describe('run', (): void => {
it('starts the server with provided settings.', async(): Promise<void> => {
const variables = {
'urn:solid-server:default:variable:port': 3000,
'urn:solid-server:default:variable:loggingLevel': 'info',
'urn:solid-server:default:variable:port': 4000,
'urn:solid-server:default:variable:rootFilePath': '/var/cwd/',
'urn:solid-server:default:variable:showStackTrace': false,
'urn:solid-server:default:variable:podConfigJson': '/var/cwd/pod-config.json',
'urn:solid-server:default:variable:seededPodConfigJson': '/var/cwd/seeded-pod-config.json',
};
const shorthand = {
logLevel: 'info',
};
const expectedVariables = {
...variables,
'urn:solid-server:default:variable:loggingLevel': 'info',
};
await new AppRunner().run(
{
mainModulePath: joinFilePath(__dirname, '../../../'),
@ -208,6 +227,7 @@ describe('AppRunner', (): void => {
},
joinFilePath(__dirname, '../../../config/default.json'),
variables,
shorthand,
);
expect(ComponentsManager.build).toHaveBeenCalledTimes(1);
@ -219,10 +239,14 @@ describe('AppRunner', (): void => {
expect(manager.configRegistry.register).toHaveBeenCalledTimes(1);
expect(manager.configRegistry.register)
.toHaveBeenCalledWith(joinFilePath(__dirname, '/../../../config/default.json'));
expect(manager.instantiate).toHaveBeenCalledTimes(1);
expect(manager.instantiate).toHaveBeenNthCalledWith(1, 'urn:solid-server:default:App', { variables });
expect(manager.instantiate).toHaveBeenCalledTimes(2);
expect(manager.instantiate).toHaveBeenNthCalledWith(1, 'urn:solid-server-app-setup:default:CliResolver', {});
expect(manager.instantiate).toHaveBeenNthCalledWith(
2, 'urn:solid-server:default:App', { variables: expectedVariables },
);
expect(shorthandResolver.handleSafe).toHaveBeenCalledTimes(1);
expect(shorthandResolver.handleSafe).toHaveBeenLastCalledWith(shorthand);
expect(cliExtractor.handleSafe).toHaveBeenCalledTimes(0);
expect(settingsResolver.handleSafe).toHaveBeenCalledTimes(0);
expect(app.start).toHaveBeenCalledTimes(1);
expect(app.start).toHaveBeenCalledWith();
expect(app.clusterManager.isSingleThreaded()).toBeFalsy();
@ -247,8 +271,9 @@ describe('AppRunner', (): void => {
expect(manager.instantiate).toHaveBeenNthCalledWith(1, 'urn:solid-server-app-setup:default:CliResolver', {});
expect(cliExtractor.handleSafe).toHaveBeenCalledTimes(1);
expect(cliExtractor.handleSafe).toHaveBeenCalledWith([ 'node', 'script' ]);
expect(settingsResolver.handleSafe).toHaveBeenCalledTimes(1);
expect(settingsResolver.handleSafe).toHaveBeenCalledWith(defaultParameters);
expect(shorthandResolver.handleSafe).toHaveBeenCalledTimes(1);
expect(shorthandResolver.handleSafe).toHaveBeenCalledWith(defaultParameters);
expect(manager.instantiate).toHaveBeenNthCalledWith(1, 'urn:solid-server-app-setup:default:CliResolver', {});
expect(manager.instantiate).toHaveBeenNthCalledWith(2,
'urn:solid-server:default:App',
{ variables: defaultVariables });
@ -289,8 +314,9 @@ describe('AppRunner', (): void => {
expect(manager.instantiate).toHaveBeenNthCalledWith(1, 'urn:solid-server-app-setup:default:CliResolver', {});
expect(cliExtractor.handleSafe).toHaveBeenCalledTimes(1);
expect(cliExtractor.handleSafe).toHaveBeenCalledWith(argvParameters);
expect(settingsResolver.handleSafe).toHaveBeenCalledTimes(1);
expect(settingsResolver.handleSafe).toHaveBeenCalledWith(defaultParameters);
expect(shorthandResolver.handleSafe).toHaveBeenCalledTimes(1);
expect(shorthandResolver.handleSafe).toHaveBeenCalledWith(defaultParameters);
expect(manager.instantiate).toHaveBeenNthCalledWith(1, 'urn:solid-server-app-setup:default:CliResolver', {});
expect(manager.instantiate).toHaveBeenNthCalledWith(2,
'urn:solid-server:default:App',
{ variables: defaultVariables });
@ -349,7 +375,39 @@ describe('AppRunner', (): void => {
} catch (error: unknown) {
caughtError = error as Error;
}
expect(caughtError.message).toMatch(/^Could not load the config variables/mu);
expect(caughtError.message).toMatch(/^Could not create the CLI resolver/mu);
expect(caughtError.message).toMatch(/^Cause: Fatal/mu);
expect(write).toHaveBeenCalledTimes(0);
expect(exit).toHaveBeenCalledTimes(0);
});
it('throws an error if extracting the CLI shorthand values fails.', async(): Promise<void> => {
cliExtractor.handleSafe.mockRejectedValueOnce(new Error('Fatal'));
let caughtError: Error = new Error('should disappear');
try {
await new AppRunner().createCli([ 'node', 'script' ]);
} catch (error: unknown) {
caughtError = error as Error;
}
expect(caughtError.message).toMatch(/^Could not parse the CLI parameters/mu);
expect(caughtError.message).toMatch(/^Cause: Fatal/mu);
expect(write).toHaveBeenCalledTimes(0);
expect(exit).toHaveBeenCalledTimes(0);
});
it('throws an error if resolving the shorthand values fails.', async(): Promise<void> => {
shorthandResolver.handleSafe.mockRejectedValueOnce(new Error('Fatal'));
let caughtError: Error = new Error('should disappear');
try {
await new AppRunner().createCli([ 'node', 'script' ]);
} catch (error: unknown) {
caughtError = error as Error;
}
expect(caughtError.message).toMatch(/^Could not resolve the shorthand values/mu);
expect(caughtError.message).toMatch(/^Cause: Fatal/mu);
expect(write).toHaveBeenCalledTimes(0);
@ -359,7 +417,7 @@ describe('AppRunner', (): void => {
it('throws an error if instantiating the server fails.', async(): Promise<void> => {
// We want the second call to fail
manager.instantiate
.mockResolvedValueOnce({ cliExtractor, settingsResolver })
.mockResolvedValueOnce({ cliExtractor, shorthandResolver })
.mockRejectedValueOnce(new Error('Fatal'));
let caughtError: Error = new Error('should disappear');
@ -409,8 +467,9 @@ describe('AppRunner', (): void => {
expect(manager.instantiate).toHaveBeenNthCalledWith(1, 'urn:solid-server-app-setup:default:CliResolver', {});
expect(cliExtractor.handleSafe).toHaveBeenCalledTimes(1);
expect(cliExtractor.handleSafe).toHaveBeenCalledWith([ 'node', 'script' ]);
expect(settingsResolver.handleSafe).toHaveBeenCalledTimes(1);
expect(settingsResolver.handleSafe).toHaveBeenCalledWith(defaultParameters);
expect(shorthandResolver.handleSafe).toHaveBeenCalledTimes(1);
expect(shorthandResolver.handleSafe).toHaveBeenCalledWith(defaultParameters);
expect(manager.instantiate).toHaveBeenNthCalledWith(1, 'urn:solid-server-app-setup:default:CliResolver', {});
expect(manager.instantiate).toHaveBeenNthCalledWith(2,
'urn:solid-server:default:App',
{ variables: defaultVariables });
@ -419,6 +478,45 @@ describe('AppRunner', (): void => {
expect(app.clusterManager.isSingleThreaded()).toBeFalsy();
});
it('runs the server honoring env variables.', async(): Promise<void> => {
// Set logging level to debug
const { env } = process;
const OLD_STATE = env.CSS_LOGGING_LEVEL;
env.CSS_LOGGING_LEVEL = 'debug';
await expect(new AppRunner().runCli([ 'node', 'script' ])).resolves.toBeUndefined();
expect(ComponentsManager.build).toHaveBeenCalledTimes(1);
// Check logLevel to be set to debug instead of default `info`
expect(ComponentsManager.build).toHaveBeenCalledWith({
dumpErrorState: true,
logLevel: 'info',
mainModulePath: joinFilePath(__dirname, '../../../'),
typeChecking: false,
});
expect(manager.configRegistry.register).toHaveBeenCalledTimes(1);
expect(manager.configRegistry.register)
.toHaveBeenCalledWith(joinFilePath(__dirname, '/../../../config/default.json'));
expect(manager.instantiate).toHaveBeenCalledTimes(2);
expect(manager.instantiate).toHaveBeenNthCalledWith(1, 'urn:solid-server-app-setup:default:CliResolver', {});
expect(cliExtractor.handleSafe).toHaveBeenCalledTimes(1);
expect(cliExtractor.handleSafe).toHaveBeenCalledWith([ 'node', 'script' ]);
expect(shorthandResolver.handleSafe).toHaveBeenCalledTimes(1);
expect(shorthandResolver.handleSafe).toHaveBeenCalledWith(defaultParameters);
expect(manager.instantiate).toHaveBeenNthCalledWith(1, 'urn:solid-server-app-setup:default:CliResolver', {});
expect(manager.instantiate).toHaveBeenNthCalledWith(2,
'urn:solid-server:default:App',
{ variables: defaultVariables });
expect(app.start).toHaveBeenCalledTimes(1);
expect(app.start).toHaveBeenLastCalledWith();
// Reset env
if (OLD_STATE) {
env.CSS_LOGGING_LEVEL = OLD_STATE;
} else {
delete env.CSS_LOGGING_LEVEL;
}
});
it('throws an error if the server could not start.', async(): Promise<void> => {
app.start.mockRejectedValueOnce(new Error('Fatal'));
@ -461,8 +559,9 @@ describe('AppRunner', (): void => {
expect(manager.instantiate).toHaveBeenNthCalledWith(1, 'urn:solid-server-app-setup:default:CliResolver', {});
expect(cliExtractor.handleSafe).toHaveBeenCalledTimes(1);
expect(cliExtractor.handleSafe).toHaveBeenCalledWith([ 'node', 'script' ]);
expect(settingsResolver.handleSafe).toHaveBeenCalledTimes(1);
expect(settingsResolver.handleSafe).toHaveBeenCalledWith(defaultParameters);
expect(shorthandResolver.handleSafe).toHaveBeenCalledTimes(1);
expect(shorthandResolver.handleSafe).toHaveBeenCalledWith(defaultParameters);
expect(manager.instantiate).toHaveBeenNthCalledWith(1, 'urn:solid-server-app-setup:default:CliResolver', {});
expect(manager.instantiate).toHaveBeenNthCalledWith(2,
'urn:solid-server:default:App',
{ variables: defaultVariables });

View File

@ -1,13 +1,13 @@
import type { CliExtractor } from '../../../src/init/cli/CliExtractor';
import { CliResolver } from '../../../src/init/CliResolver';
import type { SettingsResolver } from '../../../src/init/variables/SettingsResolver';
import type { ShorthandResolver } from '../../../src/init/variables/ShorthandResolver';
describe('A CliResolver', (): void => {
it('stores a CliExtractor and SettingsResolver.', async(): Promise<void> => {
const cliExtractor: CliExtractor = { canHandle: jest.fn().mockResolvedValue('CLI!') } as any;
const settingsResolver: SettingsResolver = { canHandle: jest.fn().mockResolvedValue('Settings!') } as any;
const settingsResolver: ShorthandResolver = { canHandle: jest.fn().mockResolvedValue('Settings!') } as any;
const cliResolver = new CliResolver(cliExtractor, settingsResolver);
expect(cliResolver.cliExtractor).toBe(cliExtractor);
expect(cliResolver.settingsResolver).toBe(settingsResolver);
expect(cliResolver.shorthandResolver).toBe(settingsResolver);
});
});

View File

@ -1,13 +1,13 @@
import { CombinedSettingsResolver } from '../../../../src/init/variables/CombinedSettingsResolver';
import type { SettingsExtractor } from '../../../../src/init/variables/extractors/SettingsExtractor';
import { CombinedShorthandResolver } from '../../../../src/init/variables/CombinedShorthandResolver';
import type { ShorthandExtractor } from '../../../../src/init/variables/extractors/ShorthandExtractor';
describe('A CombinedSettingsResolver', (): void => {
describe('A CombinedShorthandResolver', (): void => {
const values = { test: 'data' };
const varPort = 'urn:solid-server:default:variable:port';
const varLog = 'urn:solid-server:default:variable:loggingLevel';
let resolverPort: jest.Mocked<SettingsExtractor>;
let resolverLog: jest.Mocked<SettingsExtractor>;
let resolver: CombinedSettingsResolver;
let resolverPort: jest.Mocked<ShorthandExtractor>;
let resolverLog: jest.Mocked<ShorthandExtractor>;
let resolver: CombinedShorthandResolver;
beforeEach(async(): Promise<void> => {
resolverPort = {
@ -18,7 +18,7 @@ describe('A CombinedSettingsResolver', (): void => {
handleSafe: jest.fn().mockResolvedValue('info'),
} as any;
resolver = new CombinedSettingsResolver({
resolver = new CombinedShorthandResolver({
[varPort]: resolverPort,
[varLog]: resolverLog,
});