mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Add implementation for dynamically instantiating pods
This commit is contained in:
parent
88d008e36f
commit
b160121176
10
src/index.ts
10
src/index.ts
@ -108,7 +108,16 @@ export * from './logging/LogUtil';
|
||||
export * from './logging/VoidLoggerFactory';
|
||||
export * from './logging/WinstonLoggerFactory';
|
||||
|
||||
// Pods/Generate/Variables
|
||||
export * from './pods/generate/variables/BaseUrlHandler';
|
||||
export * from './pods/generate/variables/RootFilePathHandler';
|
||||
export * from './pods/generate/variables/VariableHandler';
|
||||
export * from './pods/generate/variables/Variables';
|
||||
export * from './pods/generate/variables/VariableSetter';
|
||||
|
||||
// Pods/Generate
|
||||
export * from './pods/generate/BaseComponentsJsFactory';
|
||||
export * from './pods/generate/ComponentsJsFactory';
|
||||
export * from './pods/generate/GenerateUtil';
|
||||
export * from './pods/generate/HandlebarsTemplateEngine';
|
||||
export * from './pods/generate/IdentifierGenerator';
|
||||
@ -116,6 +125,7 @@ export * from './pods/generate/PodGenerator';
|
||||
export * from './pods/generate/ResourcesGenerator';
|
||||
export * from './pods/generate/SubdomainIdentifierGenerator';
|
||||
export * from './pods/generate/SuffixIdentifierGenerator';
|
||||
export * from './pods/generate/TemplatedPodGenerator';
|
||||
export * from './pods/generate/TemplateEngine';
|
||||
export * from './pods/generate/TemplatedResourcesGenerator';
|
||||
|
||||
|
43
src/pods/generate/BaseComponentsJsFactory.ts
Normal file
43
src/pods/generate/BaseComponentsJsFactory.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import type { IComponentsManagerBuilderOptions, LogLevel } from 'componentsjs';
|
||||
import { ComponentsManager } from 'componentsjs';
|
||||
import { joinFilePath } from '../../util/PathUtil';
|
||||
import type { ComponentsJsFactory } from './ComponentsJsFactory';
|
||||
|
||||
/**
|
||||
* Can be used to instantiate objects using Components.js.
|
||||
* Default main module path is the root folder of the project.
|
||||
* For every generate call a new manager will be made,
|
||||
* but moduleState will be stored in between calls.
|
||||
*/
|
||||
export class BaseComponentsJsFactory implements ComponentsJsFactory {
|
||||
private readonly options: IComponentsManagerBuilderOptions<any>;
|
||||
|
||||
public constructor(relativeModulePath = '../../../', logLevel = 'error') {
|
||||
this.options = {
|
||||
mainModulePath: joinFilePath(__dirname, relativeModulePath),
|
||||
logLevel: logLevel as LogLevel,
|
||||
dumpErrorState: false,
|
||||
};
|
||||
}
|
||||
|
||||
private async buildManager(): Promise<ComponentsManager<any>> {
|
||||
const manager = await ComponentsManager.build(this.options);
|
||||
this.options.moduleState = manager.moduleState;
|
||||
return manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls Components.js to instantiate a new object.
|
||||
* @param configPath - Location of the config to instantiate.
|
||||
* @param componentIri - Iri of the object in the config that will be the result.
|
||||
* @param variables - Variables to send to Components.js
|
||||
*
|
||||
* @returns The resulting object, corresponding to the given component IRI.
|
||||
*/
|
||||
public async generate<T>(configPath: string, componentIri: string, variables: Record<string, any>):
|
||||
Promise<T> {
|
||||
const manager = await this.buildManager();
|
||||
await manager.configRegistry.register(configPath);
|
||||
return await manager.instantiate(componentIri, { variables });
|
||||
}
|
||||
}
|
14
src/pods/generate/ComponentsJsFactory.ts
Normal file
14
src/pods/generate/ComponentsJsFactory.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Used for instantiating new object using Components.js configurations.
|
||||
*/
|
||||
export interface ComponentsJsFactory {
|
||||
/**
|
||||
* Instantiates a new object using Components.js.
|
||||
* @param configPath - Location of the config to instantiate.
|
||||
* @param componentIri - Iri of the object in the config that will be the result.
|
||||
* @param variables - Variables to send to Components.js
|
||||
*
|
||||
* @returns The resulting object, corresponding to the given component IRI.
|
||||
*/
|
||||
generate: <T>(configPath: string, componentIri: string, variables: Record<string, any>) => Promise<T>;
|
||||
}
|
86
src/pods/generate/TemplatedPodGenerator.ts
Normal file
86
src/pods/generate/TemplatedPodGenerator.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
|
||||
import { getLoggerFor } from '../../logging/LogUtil';
|
||||
import type { KeyValueStorage } from '../../storage/keyvalue/KeyValueStorage';
|
||||
import type { ResourceStore } from '../../storage/ResourceStore';
|
||||
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
|
||||
import { ConflictHttpError } from '../../util/errors/ConflictHttpError';
|
||||
import { joinFilePath } from '../../util/PathUtil';
|
||||
import type { PodSettings } from '../settings/PodSettings';
|
||||
import type { ComponentsJsFactory } from './ComponentsJsFactory';
|
||||
import type { PodGenerator } from './PodGenerator';
|
||||
import type { VariableHandler } from './variables/VariableHandler';
|
||||
import { isValidVariable, TEMPLATE, TEMPLATE_VARIABLE } from './variables/Variables';
|
||||
|
||||
const DEFAULT_CONFIG_PATH = joinFilePath(__dirname, '../../../config/templates');
|
||||
|
||||
/**
|
||||
* Creates a new ResourceStore when creating a pod based on a Components.js configuration.
|
||||
*
|
||||
* Part of the dynamic pod creation.
|
||||
* 1. It calls a VariableHandler to add necessary variable values.
|
||||
* E.g. setting the base url variable for components.js to the pod identifier.
|
||||
* 2. It filters/cleans the input agent values using {@link VariableHandler}s
|
||||
* 3. It calls a ComponentsJsFactory with the variables and template location to instantiate a new ResourceStore.
|
||||
* 4. It stores these values in the configuration storage, which is used as a permanent storage for pod configurations.
|
||||
*
|
||||
* @see {@link ConfigPodManager}, {@link ConfigPodInitializer}, {@link BaseUrlRouterRule}
|
||||
*/
|
||||
export class TemplatedPodGenerator implements PodGenerator {
|
||||
protected readonly logger = getLoggerFor(this);
|
||||
private readonly storeFactory: ComponentsJsFactory;
|
||||
private readonly variableHandler: VariableHandler;
|
||||
private readonly configStorage: KeyValueStorage<string, unknown>;
|
||||
private readonly configTemplatePath: string;
|
||||
|
||||
/**
|
||||
* @param storeFactory - Factory used for Components.js instantiation.
|
||||
* @param variableHandler - Handler used for setting variable values.
|
||||
* @param configStorage - Where to store the configuration values to instantiate the store for this pod.
|
||||
* @param configTemplatePath - Where to find the configuration templates.
|
||||
*/
|
||||
public constructor(storeFactory: ComponentsJsFactory, variableHandler: VariableHandler,
|
||||
configStorage: KeyValueStorage<string, unknown>, configTemplatePath?: string) {
|
||||
this.storeFactory = storeFactory;
|
||||
this.variableHandler = variableHandler;
|
||||
this.configStorage = configStorage;
|
||||
this.configTemplatePath = configTemplatePath ?? DEFAULT_CONFIG_PATH;
|
||||
}
|
||||
|
||||
public async generate(identifier: ResourceIdentifier, settings: PodSettings): Promise<ResourceStore> {
|
||||
if (!settings.template) {
|
||||
throw new BadRequestHttpError('Settings require template field.');
|
||||
}
|
||||
|
||||
if (await this.configStorage.has(identifier.path)) {
|
||||
this.logger.warn(`There already is a pod at ${identifier.path}`);
|
||||
throw new ConflictHttpError(`There already is a pod at ${identifier.path}`);
|
||||
}
|
||||
|
||||
await this.variableHandler.handleSafe({ identifier, settings });
|
||||
|
||||
// Filter out irrelevant data in the agent
|
||||
const variables: NodeJS.Dict<string> = {};
|
||||
for (const key of Object.keys(settings)) {
|
||||
if (isValidVariable(key)) {
|
||||
variables[key] = settings[key];
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent unsafe template names
|
||||
if (!/^[a-zA-Z0-9.-]+$/u.test(settings.template)) {
|
||||
this.logger.warn(`Invalid template name ${settings.template}`);
|
||||
throw new BadRequestHttpError(`Invalid template name ${settings.template}`);
|
||||
}
|
||||
// Storing the template in the variables so it also gets stored in the config for later re-use
|
||||
variables[TEMPLATE_VARIABLE.templateConfig] = joinFilePath(this.configTemplatePath, settings.template);
|
||||
|
||||
const store: ResourceStore =
|
||||
await this.storeFactory.generate(variables[TEMPLATE_VARIABLE.templateConfig]!, TEMPLATE.ResourceStore, variables);
|
||||
this.logger.debug(`Generating store ${identifier.path} with variables ${JSON.stringify(variables)}`);
|
||||
|
||||
// Store the variables permanently
|
||||
await this.configStorage.set(identifier.path, variables);
|
||||
|
||||
return store;
|
||||
}
|
||||
}
|
16
src/pods/generate/variables/BaseUrlHandler.ts
Normal file
16
src/pods/generate/variables/BaseUrlHandler.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type { ResourceIdentifier } from '../../../ldp/representation/ResourceIdentifier';
|
||||
import type { PodSettings } from '../../settings/PodSettings';
|
||||
import { VariableHandler } from './VariableHandler';
|
||||
import { TEMPLATE_VARIABLE } from './Variables';
|
||||
|
||||
/**
|
||||
* Adds the pod identifier as base url variable to the agent.
|
||||
* This allows for config templates that require a value for TEMPLATE_BASE_URL_URN,
|
||||
* which should equal the pod identifier.
|
||||
*/
|
||||
export class BaseUrlHandler extends VariableHandler {
|
||||
public async handle({ identifier, settings }: { identifier: ResourceIdentifier; settings: PodSettings }):
|
||||
Promise<void> {
|
||||
settings[TEMPLATE_VARIABLE.baseUrl] = identifier.path;
|
||||
}
|
||||
}
|
37
src/pods/generate/variables/RootFilePathHandler.ts
Normal file
37
src/pods/generate/variables/RootFilePathHandler.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { promises as fsPromises } from 'fs';
|
||||
import type { ResourceIdentifier } from '../../../ldp/representation/ResourceIdentifier';
|
||||
import type { FileIdentifierMapper } from '../../../storage/mapping/FileIdentifierMapper';
|
||||
import { ConflictHttpError } from '../../../util/errors/ConflictHttpError';
|
||||
import { isSystemError } from '../../../util/errors/SystemError';
|
||||
import type { PodSettings } from '../../settings/PodSettings';
|
||||
import { VariableHandler } from './VariableHandler';
|
||||
import { TEMPLATE_VARIABLE } from './Variables';
|
||||
|
||||
/**
|
||||
* Uses a FileIdentifierMapper to generate a root file path variable based on the identifier.
|
||||
* Will throw an error if the resulting file path already exists.
|
||||
*/
|
||||
export class RootFilePathHandler extends VariableHandler {
|
||||
private readonly fileMapper: FileIdentifierMapper;
|
||||
|
||||
public constructor(fileMapper: FileIdentifierMapper) {
|
||||
super();
|
||||
this.fileMapper = fileMapper;
|
||||
}
|
||||
|
||||
public async handle({ identifier, settings }: { identifier: ResourceIdentifier; settings: PodSettings }):
|
||||
Promise<void> {
|
||||
const path = (await this.fileMapper.mapUrlToFilePath(identifier)).filePath;
|
||||
try {
|
||||
// Even though we check if it already exists, there is still a potential race condition
|
||||
// in between this check and the store being created.
|
||||
await fsPromises.access(path);
|
||||
throw new ConflictHttpError(`There already is a folder that corresponds to ${identifier.path}`);
|
||||
} catch (error: unknown) {
|
||||
if (!(isSystemError(error) && error.code === 'ENOENT')) {
|
||||
throw error;
|
||||
}
|
||||
settings[TEMPLATE_VARIABLE.rootFilePath] = path;
|
||||
}
|
||||
}
|
||||
}
|
11
src/pods/generate/variables/VariableHandler.ts
Normal file
11
src/pods/generate/variables/VariableHandler.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import type { ResourceIdentifier } from '../../../ldp/representation/ResourceIdentifier';
|
||||
import { AsyncHandler } from '../../../util/handlers/AsyncHandler';
|
||||
import type { PodSettings } from '../../settings/PodSettings';
|
||||
|
||||
/**
|
||||
* Updates the variables stored in the given agent.
|
||||
* Can be used to set variables that are required for the Components.js instantiation
|
||||
* but which should not be provided by the request.
|
||||
* E.g.: The exact file path (when required) should be determined by the server to prevent abuse.
|
||||
*/
|
||||
export abstract class VariableHandler extends AsyncHandler<{ identifier: ResourceIdentifier; settings: PodSettings }> {}
|
25
src/pods/generate/variables/VariableSetter.ts
Normal file
25
src/pods/generate/variables/VariableSetter.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import type { PodSettings } from '../../settings/PodSettings';
|
||||
import { VariableHandler } from './VariableHandler';
|
||||
|
||||
/**
|
||||
* A VariableHandler that will set the given variable to the given value,
|
||||
* unless there already is a value for the variable and override is false.
|
||||
*/
|
||||
export class VariableSetter extends VariableHandler {
|
||||
private readonly variable: string;
|
||||
private readonly value: string;
|
||||
private readonly override: boolean;
|
||||
|
||||
public constructor(variable: string, value: string, override = false) {
|
||||
super();
|
||||
this.variable = variable;
|
||||
this.value = value;
|
||||
this.override = override;
|
||||
}
|
||||
|
||||
public async handle({ settings }: { settings: PodSettings }): Promise<void> {
|
||||
if (this.override || !settings[this.variable]) {
|
||||
settings[this.variable] = this.value;
|
||||
}
|
||||
}
|
||||
}
|
20
src/pods/generate/variables/Variables.ts
Normal file
20
src/pods/generate/variables/Variables.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { createUriAndTermNamespace } from '../../../util/Vocabularies';
|
||||
|
||||
export const TEMPLATE = createUriAndTermNamespace('urn:solid-server:template:',
|
||||
'ResourceStore');
|
||||
|
||||
// Variables used for configuration templates
|
||||
// This is not an exclusive list
|
||||
export const TEMPLATE_VARIABLE = createUriAndTermNamespace(`${TEMPLATE.namespace}variable:`,
|
||||
'baseUrl',
|
||||
'rootFilePath',
|
||||
'sparqlEndpoint',
|
||||
'templateConfig');
|
||||
|
||||
/**
|
||||
* Checks if the given variable is one that is supported.
|
||||
* This can be used to weed out irrelevant parameters in an object.
|
||||
*/
|
||||
export function isValidVariable(variable: string): boolean {
|
||||
return variable.startsWith(TEMPLATE_VARIABLE.namespace);
|
||||
}
|
39
test/unit/pods/generate/BaseComponentsJsFactory.test.ts
Normal file
39
test/unit/pods/generate/BaseComponentsJsFactory.test.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import type { ComponentsManager } from 'componentsjs';
|
||||
import { BaseComponentsJsFactory } from '../../../../src/pods/generate/BaseComponentsJsFactory';
|
||||
|
||||
const manager: jest.Mocked<ComponentsManager<any>> = {
|
||||
instantiate: jest.fn(async(): Promise<any> => 'store!'),
|
||||
configRegistry: {
|
||||
register: jest.fn(),
|
||||
},
|
||||
} as any;
|
||||
|
||||
jest.mock('componentsjs', (): any => ({
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
ComponentsManager: {
|
||||
build: jest.fn(async(): Promise<ComponentsManager<any>> => manager),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('A BaseComponentsJsFactory', (): void => {
|
||||
let factory: BaseComponentsJsFactory;
|
||||
const configPath = 'config!';
|
||||
const componentIri = 'componentIri!';
|
||||
const variables = {
|
||||
aa: 'b',
|
||||
cc: 'd',
|
||||
};
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
jest.clearAllMocks();
|
||||
factory = new BaseComponentsJsFactory();
|
||||
});
|
||||
|
||||
it('calls Components.js with the given values.', async(): Promise<void> => {
|
||||
await expect(factory.generate(configPath, componentIri, variables)).resolves.toBe('store!');
|
||||
expect(manager.configRegistry.register).toHaveBeenCalledTimes(1);
|
||||
expect(manager.configRegistry.register).toHaveBeenLastCalledWith(configPath);
|
||||
expect(manager.instantiate).toHaveBeenCalledTimes(1);
|
||||
expect(manager.instantiate).toHaveBeenLastCalledWith(componentIri, { variables });
|
||||
});
|
||||
});
|
84
test/unit/pods/generate/TemplatedPodGenerator.test.ts
Normal file
84
test/unit/pods/generate/TemplatedPodGenerator.test.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import type { ComponentsJsFactory } from '../../../../src/pods/generate/ComponentsJsFactory';
|
||||
import { TemplatedPodGenerator } from '../../../../src/pods/generate/TemplatedPodGenerator';
|
||||
import type { VariableHandler } from '../../../../src/pods/generate/variables/VariableHandler';
|
||||
import { TEMPLATE, TEMPLATE_VARIABLE } from '../../../../src/pods/generate/variables/Variables';
|
||||
import type { PodSettings } from '../../../../src/pods/settings/PodSettings';
|
||||
import type { KeyValueStorage } from '../../../../src/storage/keyvalue/KeyValueStorage';
|
||||
import { BadRequestHttpError } from '../../../../src/util/errors/BadRequestHttpError';
|
||||
import { ConflictHttpError } from '../../../../src/util/errors/ConflictHttpError';
|
||||
import { joinFilePath } from '../../../../src/util/PathUtil';
|
||||
|
||||
describe('A TemplatedPodGenerator', (): void => {
|
||||
const configTemplatePath = 'templates/config/';
|
||||
const template = 'config-template.json';
|
||||
const templatePath = `${configTemplatePath}${template}`;
|
||||
const identifier = { path: 'http://test.com/alice/' };
|
||||
let settings: PodSettings;
|
||||
let storeFactory: ComponentsJsFactory;
|
||||
let variableHandler: VariableHandler;
|
||||
let configStorage: KeyValueStorage<string, unknown>;
|
||||
let generator: TemplatedPodGenerator;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
settings = { template } as any;
|
||||
|
||||
storeFactory = {
|
||||
generate: jest.fn().mockResolvedValue('store'),
|
||||
} as any;
|
||||
|
||||
variableHandler = {
|
||||
handleSafe: jest.fn(),
|
||||
} as any;
|
||||
|
||||
configStorage = new Map<string, unknown>() as any;
|
||||
|
||||
generator = new TemplatedPodGenerator(storeFactory, variableHandler, configStorage, configTemplatePath);
|
||||
});
|
||||
|
||||
it('only supports settings with a template.', async(): Promise<void> => {
|
||||
(settings as any).template = undefined;
|
||||
await expect(generator.generate(identifier, settings)).rejects.toThrow(BadRequestHttpError);
|
||||
});
|
||||
|
||||
it('generates a store and stores relevant variables.', async(): Promise<void> => {
|
||||
await expect(generator.generate(identifier, settings)).resolves.toBe('store');
|
||||
expect(variableHandler.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(variableHandler.handleSafe).toHaveBeenLastCalledWith({ identifier, settings });
|
||||
expect(storeFactory.generate).toHaveBeenCalledTimes(1);
|
||||
expect(storeFactory.generate).toHaveBeenLastCalledWith(
|
||||
templatePath, TEMPLATE.ResourceStore, { [TEMPLATE_VARIABLE.templateConfig]: templatePath },
|
||||
);
|
||||
expect(configStorage.get(identifier.path)).toEqual({ [TEMPLATE_VARIABLE.templateConfig]: templatePath });
|
||||
});
|
||||
|
||||
it('rejects identifiers that already have a config.', async(): Promise<void> => {
|
||||
await configStorage.set(identifier.path, {});
|
||||
await expect(generator.generate(identifier, settings)).rejects.toThrow(ConflictHttpError);
|
||||
});
|
||||
|
||||
it('rejects invalid config template names.', async(): Promise<void> => {
|
||||
settings.template = '../../secret-file.json';
|
||||
await expect(generator.generate(identifier, settings)).rejects.toThrow(BadRequestHttpError);
|
||||
});
|
||||
|
||||
it('only stores relevant variables from an agent object.', async(): Promise<void> => {
|
||||
settings[TEMPLATE_VARIABLE.rootFilePath] = 'correctFilePath';
|
||||
settings.login = 'should not be stored';
|
||||
await expect(generator.generate(identifier, settings)).resolves.toBe('store');
|
||||
expect(configStorage.get(identifier.path)).toEqual({
|
||||
[TEMPLATE_VARIABLE.templateConfig]: templatePath,
|
||||
[TEMPLATE_VARIABLE.rootFilePath]: 'correctFilePath',
|
||||
});
|
||||
});
|
||||
|
||||
it('uses a default template folder if none is provided.', async(): Promise<void> => {
|
||||
generator = new TemplatedPodGenerator(storeFactory, variableHandler, configStorage);
|
||||
const defaultPath = joinFilePath(__dirname, '../../../../config/templates/', template);
|
||||
|
||||
await expect(generator.generate(identifier, settings)).resolves.toBe('store');
|
||||
expect(storeFactory.generate)
|
||||
.toHaveBeenLastCalledWith(defaultPath, TEMPLATE.ResourceStore, {
|
||||
[TEMPLATE_VARIABLE.templateConfig]: defaultPath,
|
||||
});
|
||||
});
|
||||
});
|
14
test/unit/pods/generate/variables/BaseUrlHandler.test.ts
Normal file
14
test/unit/pods/generate/variables/BaseUrlHandler.test.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { BaseUrlHandler } from '../../../../../src/pods/generate/variables/BaseUrlHandler';
|
||||
import { TEMPLATE_VARIABLE } from '../../../../../src/pods/generate/variables/Variables';
|
||||
import type { PodSettings } from '../../../../../src/pods/settings/PodSettings';
|
||||
|
||||
describe('A BaseUrlHandler', (): void => {
|
||||
const handler = new BaseUrlHandler();
|
||||
|
||||
it('adds the identifier as base URL variable.', async(): Promise<void> => {
|
||||
const identifier = { path: 'http://test.com/foo' };
|
||||
const settings = {} as PodSettings;
|
||||
await expect(handler.handle({ identifier, settings })).resolves.toBeUndefined();
|
||||
expect(settings[TEMPLATE_VARIABLE.baseUrl]).toBe(identifier.path);
|
||||
});
|
||||
});
|
@ -0,0 +1,45 @@
|
||||
import fs from 'fs';
|
||||
import { RootFilePathHandler } from '../../../../../src/pods/generate/variables/RootFilePathHandler';
|
||||
import { TEMPLATE_VARIABLE } from '../../../../../src/pods/generate/variables/Variables';
|
||||
import type { PodSettings } from '../../../../../src/pods/settings/PodSettings';
|
||||
import type { ResourceLink } from '../../../../../src/storage/mapping/FileIdentifierMapper';
|
||||
import { ConflictHttpError } from '../../../../../src/util/errors/ConflictHttpError';
|
||||
import { joinFilePath } from '../../../../../src/util/PathUtil';
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
describe('A RootFilePathHandler', (): void => {
|
||||
const rootFilePath = 'files/';
|
||||
const baseUrl = 'http://test.com/';
|
||||
let handler: RootFilePathHandler;
|
||||
const identifier = { path: 'http://test.com/alice/' };
|
||||
let settings: PodSettings;
|
||||
let fsPromises: Record<string, jest.Mock>;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
settings = {} as any;
|
||||
|
||||
handler = new RootFilePathHandler({
|
||||
mapUrlToFilePath: async(id): Promise<ResourceLink> => ({
|
||||
identifier: id,
|
||||
filePath: joinFilePath(rootFilePath, id.path.slice(baseUrl.length)),
|
||||
}),
|
||||
mapFilePathToUrl: jest.fn(),
|
||||
});
|
||||
|
||||
fs.promises = {
|
||||
access: jest.fn(),
|
||||
} as any;
|
||||
fsPromises = fs.promises as any;
|
||||
});
|
||||
|
||||
it('errors if the target folder already exists.', async(): Promise<void> => {
|
||||
await expect(handler.handle({ identifier, settings })).rejects.toThrow(ConflictHttpError);
|
||||
});
|
||||
|
||||
it('adds the new file path as variable.', async(): Promise<void> => {
|
||||
fsPromises.access.mockRejectedValue({ code: 'ENOENT', syscall: 'access' });
|
||||
await expect(handler.handle({ identifier, settings })).resolves.toBeUndefined();
|
||||
expect(settings[TEMPLATE_VARIABLE.rootFilePath]).toBe(joinFilePath(rootFilePath, 'alice/'));
|
||||
});
|
||||
});
|
33
test/unit/pods/generate/variables/VariableSetter.test.ts
Normal file
33
test/unit/pods/generate/variables/VariableSetter.test.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { VariableSetter } from '../../../../../src/pods/generate/variables/VariableSetter';
|
||||
import type { PodSettings } from '../../../../../src/pods/settings/PodSettings';
|
||||
|
||||
describe('A VariableSetter', (): void => {
|
||||
const variable = 'variable';
|
||||
const value = 'http://test.com/sparql';
|
||||
let settings: PodSettings;
|
||||
let handler: VariableSetter;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
settings = {} as any;
|
||||
});
|
||||
|
||||
it('does nothing if there already is a sparql endpoint value and override is false.', async(): Promise<void> => {
|
||||
handler = new VariableSetter(variable, value);
|
||||
settings[variable] = 'sparql-endpoint';
|
||||
await expect(handler.handle({ settings })).resolves.toBeUndefined();
|
||||
expect(settings[variable]).toBe('sparql-endpoint');
|
||||
});
|
||||
|
||||
it('adds adds the value to the variable if there is none.', async(): Promise<void> => {
|
||||
handler = new VariableSetter(variable, value);
|
||||
await expect(handler.handle({ settings })).resolves.toBeUndefined();
|
||||
expect(settings[variable]).toBe(value);
|
||||
});
|
||||
|
||||
it('always sets the value if override is true.', async(): Promise<void> => {
|
||||
handler = new VariableSetter(variable, value, true);
|
||||
settings[variable] = 'sparql-endpoint';
|
||||
await expect(handler.handle({ settings })).resolves.toBeUndefined();
|
||||
expect(settings[variable]).toBe(value);
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user