feat: Respect root path for static assets and HTML links

* feat: rootpath for static assets and links1

* fix: static asset handler respects root path

* fix: use rootPath for links

* tests: fix the tests after adding consuctor params

* feat: change matchregex instead of stored URLs

* feat: baseUrl for handlebar engine and templates

* feat: full baseUrl passed to templates

* test: fix integration tests + templates

* chore: implement requested changes

* docs: Describe TemplateEngine interface changes

Co-authored-by: Joachim Van Herwegen <joachimvh@gmail.com>
This commit is contained in:
Jasper Vaneessen 2022-05-24 10:20:41 +02:00 committed by GitHub
parent 771d138037
commit 2814e72b34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 105 additions and 43 deletions

View File

@ -42,6 +42,7 @@ These changes are relevant if you wrote custom modules for the server that depen
- `YargsCliExtractor` was changed to now take as input an array of parameter objects. - `YargsCliExtractor` was changed to now take as input an array of parameter objects.
- `RedirectAllHttpHandler` was removed and fully replaced by `RedirectingHttpHandler`. - `RedirectAllHttpHandler` was removed and fully replaced by `RedirectingHttpHandler`.
- `SingleThreadedResourceLocker` has been renamed to `MemoryResourceLocker`. - `SingleThreadedResourceLocker` has been renamed to `MemoryResourceLocker`.
- Both `TemplateEngine` implementations now take a `baseUrl` parameter as input.
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. 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

@ -17,7 +17,10 @@
"@type": "TemplatedResourcesGenerator", "@type": "TemplatedResourcesGenerator",
"templateFolder": "@css:templates/root/prefilled", "templateFolder": "@css:templates/root/prefilled",
"factory": { "@type": "ExtensionBasedMapperFactory" }, "factory": { "@type": "ExtensionBasedMapperFactory" },
"templateEngine": { "@type": "HandlebarsTemplateEngine" } "templateEngine": {
"@type": "HandlebarsTemplateEngine",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
}
}, },
"args_storageKey": "rootInitialized", "args_storageKey": "rootInitialized",
"args_storage": { "@id": "urn:solid-server:default:SetupStorage" } "args_storage": { "@id": "urn:solid-server:default:SetupStorage" }

View File

@ -17,7 +17,10 @@
"@type": "TemplatedResourcesGenerator", "@type": "TemplatedResourcesGenerator",
"templateFolder": "@css:templates/root/empty", "templateFolder": "@css:templates/root/empty",
"factory": { "@type": "ExtensionBasedMapperFactory" }, "factory": { "@type": "ExtensionBasedMapperFactory" },
"templateEngine": { "@type": "HandlebarsTemplateEngine" } "templateEngine": {
"@type": "HandlebarsTemplateEngine",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
}
}, },
"args_storageKey": "rootInitialized", "args_storageKey": "rootInitialized",
"args_storage": { "@id": "urn:solid-server:default:SetupStorage" } "args_storage": { "@id": "urn:solid-server:default:SetupStorage" }

View File

@ -27,12 +27,14 @@
{ {
"comment": "Renders the main setup template.", "comment": "Renders the main setup template.",
"@type": "EjsTemplateEngine", "@type": "EjsTemplateEngine",
"template": "@css:templates/setup/index.html.ejs" "template": "@css:templates/setup/index.html.ejs",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
}, },
{ {
"comment": "Will embed the result of the first engine into the main HTML template.", "comment": "Will embed the result of the first engine into the main HTML template.",
"@type": "EjsTemplateEngine", "@type": "EjsTemplateEngine",
"template": "@css:templates/main.html.ejs" "template": "@css:templates/main.html.ejs",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
} }
] ]
} }
@ -60,7 +62,10 @@
"@type": "TemplatedResourcesGenerator", "@type": "TemplatedResourcesGenerator",
"templateFolder": "@css:templates/root/empty", "templateFolder": "@css:templates/root/empty",
"factory": { "@type": "ExtensionBasedMapperFactory" }, "factory": { "@type": "ExtensionBasedMapperFactory" },
"templateEngine": { "@type": "HandlebarsTemplateEngine" } "templateEngine": {
"@type": "HandlebarsTemplateEngine",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
}
}, },
"args_storageKey": "rootInitialized", "args_storageKey": "rootInitialized",
"args_storage": { "@id": "urn:solid-server:default:SetupStorage" } "args_storage": { "@id": "urn:solid-server:default:SetupStorage" }

View File

@ -6,6 +6,7 @@
"@id": "urn:solid-server:default:StaticAssetHandler", "@id": "urn:solid-server:default:StaticAssetHandler",
"@type": "StaticAssetHandler", "@type": "StaticAssetHandler",
"options_expires": 86400, "options_expires": 86400,
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
"assets": [ "assets": [
{ {
"StaticAssetHandler:_assets_key": "/favicon.ico", "StaticAssetHandler:_assets_key": "/favicon.ico",

View File

@ -17,7 +17,10 @@
"@type": "TemplatedResourcesGenerator", "@type": "TemplatedResourcesGenerator",
"templateFolder": "@css:templates/root/empty", "templateFolder": "@css:templates/root/empty",
"factory": { "@type": "ExtensionBasedMapperFactory" }, "factory": { "@type": "ExtensionBasedMapperFactory" },
"templateEngine": { "@type": "HandlebarsTemplateEngine" } "templateEngine": {
"@type": "HandlebarsTemplateEngine",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
}
}, },
"args_storageKey": "idpContainerInitialized", "args_storageKey": "idpContainerInitialized",
"args_storage": { "@id": "urn:solid-server:default:SetupStorage" } "args_storage": { "@id": "urn:solid-server:default:SetupStorage" }

View File

@ -17,7 +17,10 @@
"@type": "TemplatedResourcesGenerator", "@type": "TemplatedResourcesGenerator",
"templateFolder": "@css:templates/root/empty", "templateFolder": "@css:templates/root/empty",
"factory": { "@type": "ExtensionBasedMapperFactory" }, "factory": { "@type": "ExtensionBasedMapperFactory" },
"templateEngine": { "@type": "HandlebarsTemplateEngine" } "templateEngine": {
"@type": "HandlebarsTemplateEngine",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
}
}, },
"args_storageKey": "wellKnownContainerInitialized", "args_storageKey": "wellKnownContainerInitialized",
"args_storage": { "@id": "urn:solid-server:default:SetupStorage" } "args_storage": { "@id": "urn:solid-server:default:SetupStorage" }

View File

@ -17,7 +17,8 @@
"args_accountStore": { "@id": "urn:solid-server:auth:password:AccountStore" }, "args_accountStore": { "@id": "urn:solid-server:auth:password:AccountStore" },
"args_templateEngine": { "args_templateEngine": {
"@type": "EjsTemplateEngine", "@type": "EjsTemplateEngine",
"template": "@css:templates/identity/email-password/reset-password-email.html.ejs" "template": "@css:templates/identity/email-password/reset-password-email.html.ejs",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
}, },
"args_emailSender": { "@id": "urn:solid-server:default:EmailSender" }, "args_emailSender": { "@id": "urn:solid-server:default:EmailSender" },
"args_resetRoute": { "@id": "urn:solid-server:auth:password:ResetPasswordRoute" } "args_resetRoute": { "@id": "urn:solid-server:auth:password:ResetPasswordRoute" }

View File

@ -12,12 +12,14 @@
"engines": [ "engines": [
{ {
"comment": "Will be called with specific templates to generate HTML snippets.", "comment": "Will be called with specific templates to generate HTML snippets.",
"@type": "EjsTemplateEngine" "@type": "EjsTemplateEngine",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
}, },
{ {
"comment": "Will embed the result of the first engine into the main HTML template.", "comment": "Will embed the result of the first engine into the main HTML template.",
"@type": "EjsTemplateEngine", "@type": "EjsTemplateEngine",
"template": "@css:templates/main.html.ejs" "template": "@css:templates/main.html.ejs",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
} }
] ]
}, },

View File

@ -16,6 +16,7 @@
} }
] ]
}, },
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" },
"configStorage": { "@id": "urn:solid-server:default:PodConfigurationStorage" } "configStorage": { "@id": "urn:solid-server:default:PodConfigurationStorage" }
} }
] ]

View File

@ -10,7 +10,8 @@
"@type": "ExtensionBasedMapperFactory" "@type": "ExtensionBasedMapperFactory"
}, },
"templateEngine": { "templateEngine": {
"@type": "HandlebarsTemplateEngine" "@type": "HandlebarsTemplateEngine",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
} }
} }
] ]

View File

@ -12,12 +12,14 @@
"engines": [ "engines": [
{ {
"comment": "Will be called with specific templates to generate HTML snippets.", "comment": "Will be called with specific templates to generate HTML snippets.",
"@type": "EjsTemplateEngine" "@type": "EjsTemplateEngine",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
}, },
{ {
"comment": "Will embed the result of the first engine into the main HTML template.", "comment": "Will embed the result of the first engine into the main HTML template.",
"@type": "EjsTemplateEngine", "@type": "EjsTemplateEngine",
"template": "@css:templates/main.html.ejs" "template": "@css:templates/main.html.ejs",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
} }
] ]
} }

View File

@ -13,7 +13,10 @@
"comment": "Converts an error into a Markdown description of its details.", "comment": "Converts an error into a Markdown description of its details.",
"@id": "urn:solid-server:default:ErrorToTemplateConverter", "@id": "urn:solid-server:default:ErrorToTemplateConverter",
"@type": "ErrorToTemplateConverter", "@type": "ErrorToTemplateConverter",
"templateEngine": { "@type": "HandlebarsTemplateEngine" } "templateEngine": {
"@type": "HandlebarsTemplateEngine",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
}
} }
] ]
} }

View File

@ -7,7 +7,8 @@
"@type": "MarkdownToHtmlConverter", "@type": "MarkdownToHtmlConverter",
"templateEngine": { "templateEngine": {
"@type": "EjsTemplateEngine", "@type": "EjsTemplateEngine",
"template": "@css:templates/main.html.ejs" "template": "@css:templates/main.html.ejs",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
} }
}, },
{ {
@ -16,7 +17,8 @@
"@type": "ContainerToTemplateConverter", "@type": "ContainerToTemplateConverter",
"templateEngine": { "templateEngine": {
"@type": "HandlebarsTemplateEngine", "@type": "HandlebarsTemplateEngine",
"template": "@css:templates/container.md.hbs" "template": "@css:templates/container.md.hbs",
"baseUrl": { "@id": "urn:solid-server:default:variable:baseUrl" }
}, },
"contentType": "text/markdown", "contentType": "text/markdown",
"identifierStrategy": { "@id": "urn:solid-server:default:IdentifierStrategy" } "identifierStrategy": { "@id": "urn:solid-server:default:IdentifierStrategy" }

View File

@ -31,6 +31,7 @@ export class TemplatedPodGenerator implements PodGenerator {
private readonly variableHandler: VariableHandler; private readonly variableHandler: VariableHandler;
private readonly configStorage: KeyValueStorage<string, unknown>; private readonly configStorage: KeyValueStorage<string, unknown>;
private readonly configTemplatePath: string; private readonly configTemplatePath: string;
private readonly baseUrl: string;
/** /**
* @param storeFactory - Factory used for Components.js instantiation. * @param storeFactory - Factory used for Components.js instantiation.
@ -39,10 +40,11 @@ export class TemplatedPodGenerator implements PodGenerator {
* @param configTemplatePath - Where to find the configuration templates. * @param configTemplatePath - Where to find the configuration templates.
*/ */
public constructor(storeFactory: ComponentsJsFactory, variableHandler: VariableHandler, public constructor(storeFactory: ComponentsJsFactory, variableHandler: VariableHandler,
configStorage: KeyValueStorage<string, unknown>, configTemplatePath?: string) { configStorage: KeyValueStorage<string, unknown>, baseUrl: string, configTemplatePath?: string) {
this.storeFactory = storeFactory; this.storeFactory = storeFactory;
this.variableHandler = variableHandler; this.variableHandler = variableHandler;
this.configStorage = configStorage; this.configStorage = configStorage;
this.baseUrl = baseUrl;
this.configTemplatePath = configTemplatePath ?? DEFAULT_CONFIG_PATH; this.configTemplatePath = configTemplatePath ?? DEFAULT_CONFIG_PATH;
} }
@ -75,7 +77,11 @@ export class TemplatedPodGenerator implements PodGenerator {
variables[TEMPLATE_VARIABLE.templateConfig] = joinFilePath(this.configTemplatePath, settings.template); variables[TEMPLATE_VARIABLE.templateConfig] = joinFilePath(this.configTemplatePath, settings.template);
const store: ResourceStore = const store: ResourceStore =
await this.storeFactory.generate(variables[TEMPLATE_VARIABLE.templateConfig]!, TEMPLATE.ResourceStore, variables); await this.storeFactory.generate(
variables[TEMPLATE_VARIABLE.templateConfig]!,
TEMPLATE.ResourceStore,
{ ...variables, 'urn:solid-server:default:variable:baseUrl': this.baseUrl },
);
this.logger.debug(`Generating store ${identifier.path} with variables ${JSON.stringify(variables)}`); this.logger.debug(`Generating store ${identifier.path} with variables ${JSON.stringify(variables)}`);
// Store the variables permanently // Store the variables permanently

View File

@ -5,7 +5,7 @@ import { getLoggerFor } from '../../logging/LogUtil';
import { APPLICATION_OCTET_STREAM } from '../../util/ContentTypes'; import { APPLICATION_OCTET_STREAM } from '../../util/ContentTypes';
import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError'; import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError';
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError'; import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
import { joinFilePath, resolveAssetPath } from '../../util/PathUtil'; import { ensureTrailingSlash, joinFilePath, resolveAssetPath, trimLeadingSlashes } from '../../util/PathUtil';
import { pipeSafely } from '../../util/StreamUtil'; import { pipeSafely } from '../../util/StreamUtil';
import type { HttpHandlerInput } from '../HttpHandler'; import type { HttpHandlerInput } from '../HttpHandler';
import { HttpHandler } from '../HttpHandler'; import { HttpHandler } from '../HttpHandler';
@ -29,22 +29,24 @@ export class StaticAssetHandler extends HttpHandler {
* where URL paths ending in a slash are interpreted as entire folders. * where URL paths ending in a slash are interpreted as entire folders.
* @param options - Cache expiration time in seconds. * @param options - Cache expiration time in seconds.
*/ */
public constructor(assets: Record<string, string>, options: { expires?: number } = {}) { public constructor(assets: Record<string, string>, baseUrl: string, options: { expires?: number } = {}) {
super(); super();
this.mappings = {}; this.mappings = {};
const rootPath = ensureTrailingSlash(new URL(baseUrl).pathname);
for (const [ url, path ] of Object.entries(assets)) { for (const [ url, path ] of Object.entries(assets)) {
this.mappings[url] = resolveAssetPath(path); this.mappings[trimLeadingSlashes(url)] = resolveAssetPath(path);
} }
this.pathMatcher = this.createPathMatcher(assets); this.pathMatcher = this.createPathMatcher(rootPath);
this.expires = Number.isInteger(options.expires) ? Math.max(0, options.expires!) : 0; this.expires = Number.isInteger(options.expires) ? Math.max(0, options.expires!) : 0;
} }
/** /**
* Creates a regular expression that matches the URL paths. * Creates a regular expression that matches the URL paths.
*/ */
private createPathMatcher(assets: Record<string, string>): RegExp { private createPathMatcher(rootPath: string): RegExp {
// Sort longest paths first to ensure the longest match has priority // Sort longest paths first to ensure the longest match has priority
const paths = Object.keys(assets) const paths = Object.keys(this.mappings)
.sort((pathA, pathB): number => pathB.length - pathA.length); .sort((pathA, pathB): number => pathB.length - pathA.length);
// Collect regular expressions for files and folders separately // Collect regular expressions for files and folders separately
@ -55,7 +57,7 @@ export class StaticAssetHandler extends HttpHandler {
} }
// Either match an exact document or a file within a folder (stripping the query string) // Either match an exact document or a file within a folder (stripping the query string)
return new RegExp(`^(?:(${files.join('|')})|(${folders.join('|')})([^?]+))(?:\\?.*)?$`, 'u'); return new RegExp(`^${rootPath}(?:(${files.join('|')})|(${folders.join('|')})([^?]+))(?:\\?.*)?$`, 'u');
} }
/** /**

View File

@ -97,6 +97,17 @@ export function ensureLeadingSlash(path: string): string {
return path.replace(/^\/*/u, '/'); return path.replace(/^\/*/u, '/');
} }
/**
* Makes sure the input path has no slashes at the beginning.
*
* @param path - Path to check.
*
* @returns The potentially changed path.
*/
export function trimLeadingSlashes(path: string): string {
return path.replace(/^\/+/u, '');
}
/** /**
* Extracts the extension (without dot) from a path. * Extracts the extension (without dot) from a path.
* Custom function since `path.extname` does not work on all cases (e.g. ".acl") * Custom function since `path.extname` does not work on all cases (e.g. ".acl")

View File

@ -11,13 +11,17 @@ import Dict = NodeJS.Dict;
*/ */
export class EjsTemplateEngine<T extends Dict<any> = Dict<any>> implements TemplateEngine<T> { export class EjsTemplateEngine<T extends Dict<any> = Dict<any>> implements TemplateEngine<T> {
private readonly applyTemplate: Promise<TemplateFunction>; private readonly applyTemplate: Promise<TemplateFunction>;
private readonly baseUrl: string;
/** /**
* @param baseUrl - Base URL of the server.
* @param template - The default template @range {json} * @param template - The default template @range {json}
*/ */
public constructor(template?: Template) { public constructor(baseUrl: string, template?: Template) {
// EJS requires the `filename` parameter to be able to include partial templates // EJS requires the `filename` parameter to be able to include partial templates
const filename = getTemplateFilePath(template); const filename = getTemplateFilePath(template);
this.baseUrl = baseUrl;
this.applyTemplate = readTemplate(template) this.applyTemplate = readTemplate(template)
.then((templateString: string): TemplateFunction => compile(templateString, { filename })); .then((templateString: string): TemplateFunction => compile(templateString, { filename }));
} }
@ -25,7 +29,7 @@ export class EjsTemplateEngine<T extends Dict<any> = Dict<any>> implements Templ
public async render(contents: T): Promise<string>; public async render(contents: T): Promise<string>;
public async render<TCustom = T>(contents: TCustom, template: Template): Promise<string>; public async render<TCustom = T>(contents: TCustom, template: Template): Promise<string>;
public async render<TCustom = T>(contents: TCustom, template?: Template): Promise<string> { public async render<TCustom = T>(contents: TCustom, template?: Template): Promise<string> {
const options = { ...contents, filename: getTemplateFilePath(template) }; const options = { ...contents, filename: getTemplateFilePath(template), baseUrl: this.baseUrl };
return template ? render(await readTemplate(template), options) : (await this.applyTemplate)(options); return template ? render(await readTemplate(template), options) : (await this.applyTemplate)(options);
} }
} }

View File

@ -11,11 +11,14 @@ import Dict = NodeJS.Dict;
*/ */
export class HandlebarsTemplateEngine<T extends Dict<any> = Dict<any>> implements TemplateEngine<T> { export class HandlebarsTemplateEngine<T extends Dict<any> = Dict<any>> implements TemplateEngine<T> {
private readonly applyTemplate: Promise<TemplateDelegate>; private readonly applyTemplate: Promise<TemplateDelegate>;
private readonly baseUrl: string;
/** /**
* @params baseUrl - Base URL of the server.
* @param template - The default template @range {json} * @param template - The default template @range {json}
*/ */
public constructor(template?: Template) { public constructor(baseUrl: string, template?: Template) {
this.baseUrl = baseUrl;
this.applyTemplate = readTemplate(template) this.applyTemplate = readTemplate(template)
.then((templateString: string): TemplateDelegate => compile(templateString)); .then((templateString: string): TemplateDelegate => compile(templateString));
} }
@ -24,6 +27,6 @@ export class HandlebarsTemplateEngine<T extends Dict<any> = Dict<any>> implement
public async render<TCustom = T>(contents: TCustom, template: Template): Promise<string>; public async render<TCustom = T>(contents: TCustom, template: Template): Promise<string>;
public async render<TCustom = T>(contents: TCustom, template?: Template): Promise<string> { public async render<TCustom = T>(contents: TCustom, template?: Template): Promise<string> {
const applyTemplate = template ? compile(await readTemplate(template)) : await this.applyTemplate; const applyTemplate = template ? compile(await readTemplate(template)) : await this.applyTemplate;
return applyTemplate(contents); return applyTemplate({ ...contents, baseUrl: this.baseUrl });
} }
} }

View File

@ -3,7 +3,8 @@
"import": [ "import": [
"css:config/util/auxiliary/acl.json", "css:config/util/auxiliary/acl.json",
"css:config/util/index/default.json", "css:config/util/index/default.json",
"css:config/util/representation-conversion/default.json" "css:config/util/representation-conversion/default.json",
"css:config/util/variables/default.json"
], ],
"@graph": [ "@graph": [
{ {

View File

@ -4,12 +4,12 @@
<meta charset="utf-8"/> <meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/> <meta name="viewport" content="width=device-width, initial-scale=1"/>
<title><%= extractTitle(htmlBody) %></title> <title><%= extractTitle(htmlBody) %></title>
<link rel="stylesheet" href="/.well-known/css/styles/main.css" type="text/css"> <link rel="stylesheet" href="<%= baseUrl -%>.well-known/css/styles/main.css" type="text/css">
<script type="text/javascript" src="/.well-known/css/scripts/util.js"></script> <script type="text/javascript" src="<%= baseUrl -%>.well-known/css/scripts/util.js"></script>
</head> </head>
<body> <body>
<header> <header>
<a href="/"><img src="/.well-known/css/images/solid.svg" alt="[Solid logo]" /></a> <a href="<%= baseUrl %>"><img src="<%= baseUrl -%>.well-known/css/images/solid.svg" alt="[Solid logo]" /></a>
<h1>Community Solid Server</h1> <h1>Community Solid Server</h1>
</header> </header>
<main> <main>

View File

@ -13,6 +13,7 @@ describe('A TemplatedPodGenerator', (): void => {
const template = 'config-template.json'; const template = 'config-template.json';
const templatePath = `${configTemplatePath}${template}`; const templatePath = `${configTemplatePath}${template}`;
const identifier = { path: 'http://test.com/alice/' }; const identifier = { path: 'http://test.com/alice/' };
const baseUrl = 'http://test.com';
let settings: PodSettings; let settings: PodSettings;
let storeFactory: ComponentsJsFactory; let storeFactory: ComponentsJsFactory;
let variableHandler: VariableHandler; let variableHandler: VariableHandler;
@ -32,7 +33,7 @@ describe('A TemplatedPodGenerator', (): void => {
configStorage = new Map<string, unknown>() as any; configStorage = new Map<string, unknown>() as any;
generator = new TemplatedPodGenerator(storeFactory, variableHandler, configStorage, configTemplatePath); generator = new TemplatedPodGenerator(storeFactory, variableHandler, configStorage, baseUrl, configTemplatePath);
}); });
it('only supports settings with a template.', async(): Promise<void> => { it('only supports settings with a template.', async(): Promise<void> => {
@ -45,9 +46,11 @@ describe('A TemplatedPodGenerator', (): void => {
expect(variableHandler.handleSafe).toHaveBeenCalledTimes(1); expect(variableHandler.handleSafe).toHaveBeenCalledTimes(1);
expect(variableHandler.handleSafe).toHaveBeenLastCalledWith({ identifier, settings }); expect(variableHandler.handleSafe).toHaveBeenLastCalledWith({ identifier, settings });
expect(storeFactory.generate).toHaveBeenCalledTimes(1); expect(storeFactory.generate).toHaveBeenCalledTimes(1);
expect(storeFactory.generate).toHaveBeenLastCalledWith( expect(storeFactory.generate)
templatePath, TEMPLATE.ResourceStore, { [TEMPLATE_VARIABLE.templateConfig]: templatePath }, .toHaveBeenLastCalledWith(templatePath, TEMPLATE.ResourceStore, {
); [TEMPLATE_VARIABLE.templateConfig]: templatePath,
'urn:solid-server:default:variable:baseUrl': baseUrl,
});
expect(configStorage.get(identifier.path)).toEqual({ [TEMPLATE_VARIABLE.templateConfig]: templatePath }); expect(configStorage.get(identifier.path)).toEqual({ [TEMPLATE_VARIABLE.templateConfig]: templatePath });
}); });
@ -72,13 +75,14 @@ describe('A TemplatedPodGenerator', (): void => {
}); });
it('uses a default template folder if none is provided.', async(): Promise<void> => { it('uses a default template folder if none is provided.', async(): Promise<void> => {
generator = new TemplatedPodGenerator(storeFactory, variableHandler, configStorage); generator = new TemplatedPodGenerator(storeFactory, variableHandler, configStorage, baseUrl);
const defaultPath = joinFilePath(__dirname, '../../../../templates/config/', template); const defaultPath = joinFilePath(__dirname, '../../../../templates/config/', template);
await expect(generator.generate(identifier, settings)).resolves.toBe('store'); await expect(generator.generate(identifier, settings)).resolves.toBe('store');
expect(storeFactory.generate) expect(storeFactory.generate)
.toHaveBeenLastCalledWith(defaultPath, TEMPLATE.ResourceStore, { .toHaveBeenLastCalledWith(defaultPath, TEMPLATE.ResourceStore, {
[TEMPLATE_VARIABLE.templateConfig]: defaultPath, [TEMPLATE_VARIABLE.templateConfig]: defaultPath,
'urn:solid-server:default:variable:baseUrl': baseUrl,
}); });
}); });
}); });

View File

@ -48,7 +48,7 @@ async function genToArray<T>(iterable: AsyncIterable<T>): Promise<T[]> {
describe('A TemplatedResourcesGenerator', (): void => { describe('A TemplatedResourcesGenerator', (): void => {
const rootFilePath = '/templates/pod'; const rootFilePath = '/templates/pod';
// Using handlebars engine since it's smaller than any possible dummy // Using handlebars engine since it's smaller than any possible dummy
const generator = new TemplatedResourcesGenerator(rootFilePath, new DummyFactory(), new HandlebarsTemplateEngine()); const generator = new TemplatedResourcesGenerator(rootFilePath, new DummyFactory(), new HandlebarsTemplateEngine('http://test.com/'));
let cache: { data: any }; let cache: { data: any };
const template = '<{{webId}}> a <http://xmlns.com/foaf/0.1/Person>.'; const template = '<{{webId}}> a <http://xmlns.com/foaf/0.1/Person>.';
const location = { path: 'http://test.com/alice/' }; const location = { path: 'http://test.com/alice/' };

View File

@ -20,7 +20,7 @@ describe('A StaticAssetHandler', (): void => {
'/foo/bar/folder1/': '/assets/folders/1/', '/foo/bar/folder1/': '/assets/folders/1/',
'/foo/bar/folder2/': '/assets/folders/2', '/foo/bar/folder2/': '/assets/folders/2',
'/foo/bar/folder2/subfolder/': '/assets/folders/3', '/foo/bar/folder2/subfolder/': '/assets/folders/3',
}); }, 'http://localhost:3000');
afterEach(jest.clearAllMocks); afterEach(jest.clearAllMocks);
@ -217,7 +217,7 @@ describe('A StaticAssetHandler', (): void => {
jest.spyOn(Date, 'now').mockReturnValue(0); jest.spyOn(Date, 'now').mockReturnValue(0);
const cachedHandler = new StaticAssetHandler({ const cachedHandler = new StaticAssetHandler({
'/foo/bar/style': '/assets/styles/bar.css', '/foo/bar/style': '/assets/styles/bar.css',
}, { }, 'http://localhost:3000', {
expires: 86400, expires: 86400,
}); });
const request = { method: 'GET', url: '/foo/bar/style' }; const request = { method: 'GET', url: '/foo/bar/style' };

View File

@ -11,7 +11,7 @@ describe('A EjsTemplateEngine', (): void => {
let templateEngine: EjsTemplateEngine; let templateEngine: EjsTemplateEngine;
beforeEach((): void => { beforeEach((): void => {
templateEngine = new EjsTemplateEngine(defaultTemplate); templateEngine = new EjsTemplateEngine('http://localhost:3000', defaultTemplate);
}); });
it('uses the default template when no template was passed.', async(): Promise<void> => { it('uses the default template when no template was passed.', async(): Promise<void> => {

View File

@ -10,7 +10,7 @@ describe('A HandlebarsTemplateEngine', (): void => {
let templateEngine: HandlebarsTemplateEngine; let templateEngine: HandlebarsTemplateEngine;
beforeEach((): void => { beforeEach((): void => {
templateEngine = new HandlebarsTemplateEngine(template); templateEngine = new HandlebarsTemplateEngine('http://localhost:3000/', template);
}); });
it('uses the default template when no template was passed.', async(): Promise<void> => { it('uses the default template when no template was passed.', async(): Promise<void> => {