mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Convert errorCodes using markdown
This commit is contained in:
parent
6cf539c171
commit
f2f967ff8a
@ -28,8 +28,15 @@
|
||||
{
|
||||
"@type": "ErrorToTemplateConverter",
|
||||
"engine": { "@type": "HandlebarsTemplateEngine" },
|
||||
"templatePath": "$PACKAGE_ROOT/templates/error/error.hbs",
|
||||
"contentType": "text/html"
|
||||
"templatePath": "$PACKAGE_ROOT/templates/error/main.md",
|
||||
"descriptions": "$PACKAGE_ROOT/templates/error/descriptions/",
|
||||
"contentType": "text/markdown",
|
||||
"extension": ".md"
|
||||
},
|
||||
{
|
||||
"@type": "MarkdownToHtmlConverter",
|
||||
"engine": { "@type": "HandlebarsTemplateEngine" },
|
||||
"templatePath": "$PACKAGE_ROOT/templates/main.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
8
package-lock.json
generated
8
package-lock.json
generated
@ -4822,6 +4822,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.0.tgz",
|
||||
"integrity": "sha512-RaE0B+14ToE4l6UqdarKPnXwVDuigfFv+5j9Dze/Nqr23yyuqdNvzcZi3xB+3Agvi5R4EOgAksfv3lXX4vBt9w=="
|
||||
},
|
||||
"@types/marked": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-2.0.3.tgz",
|
||||
"integrity": "sha512-lbhSN1rht/tQ+dSWxawCzGgTfxe9DB31iLgiT1ZVT5lshpam/nyOA1m3tKHRoNPctB2ukSL22JZI5Fr+WI/zYg=="
|
||||
},
|
||||
"@types/mime": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
|
||||
@ -11427,8 +11432,7 @@
|
||||
"marked": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-2.1.3.tgz",
|
||||
"integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA=="
|
||||
},
|
||||
"media-typer": {
|
||||
"version": "0.3.0",
|
||||
|
@ -84,6 +84,7 @@
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/cors": "^2.8.10",
|
||||
"@types/end-of-stream": "^1.4.0",
|
||||
"@types/marked": "^2.0.3",
|
||||
"@types/mime-types": "^2.1.0",
|
||||
"@types/n3": "^1.10.0",
|
||||
"@types/node": "^15.12.5",
|
||||
@ -112,6 +113,7 @@
|
||||
"fetch-sparql-endpoint": "^2.0.1",
|
||||
"handlebars": "^4.7.7",
|
||||
"jose": "^3.11.6",
|
||||
"marked": "^2.1.3",
|
||||
"mime-types": "^2.1.31",
|
||||
"n3": "^1.10.0",
|
||||
"nodemailer": "^6.6.2",
|
||||
|
@ -229,6 +229,7 @@ export * from './storage/conversion/ConversionUtil';
|
||||
export * from './storage/conversion/ErrorToTemplateConverter';
|
||||
export * from './storage/conversion/ErrorToQuadConverter';
|
||||
export * from './storage/conversion/IfNeededConverter';
|
||||
export * from './storage/conversion/MarkdownToHtmlConverter';
|
||||
export * from './storage/conversion/PassthroughConverter';
|
||||
export * from './storage/conversion/QuadToRdfConverter';
|
||||
export * from './storage/conversion/RdfToQuadConverter';
|
||||
|
@ -4,25 +4,38 @@ import { BasicRepresentation } from '../../ldp/representation/BasicRepresentatio
|
||||
import type { Representation } from '../../ldp/representation/Representation';
|
||||
import type { TemplateEngine } from '../../pods/generate/TemplateEngine';
|
||||
import { INTERNAL_ERROR } from '../../util/ContentTypes';
|
||||
import { HttpError } from '../../util/errors/HttpError';
|
||||
import { InternalServerError } from '../../util/errors/InternalServerError';
|
||||
import { resolveAssetPath } from '../../util/PathUtil';
|
||||
import { joinFilePath, resolveAssetPath } from '../../util/PathUtil';
|
||||
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||
import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
||||
|
||||
/**
|
||||
* Serializes an Error by filling in the provided template.
|
||||
* Content-type is based on the constructor parameter.
|
||||
*
|
||||
* In case the input Error has an `options.errorCode` value,
|
||||
* the converter will look in the `descriptions` for a file
|
||||
* with the exact same name as that error code + `extension`.
|
||||
* The templating engine will then be applied to that file.
|
||||
* That result will be passed as an additional parameter to the main templating call,
|
||||
* using the variable `codeMessage`.
|
||||
*/
|
||||
export class ErrorToTemplateConverter extends TypedRepresentationConverter {
|
||||
private readonly engine: TemplateEngine;
|
||||
private readonly templatePath: string;
|
||||
private readonly descriptions: string;
|
||||
private readonly contentType: string;
|
||||
private readonly extension: string;
|
||||
|
||||
public constructor(engine: TemplateEngine, templatePath: string, contentType: string) {
|
||||
public constructor(engine: TemplateEngine, templatePath: string, descriptions: string, contentType: string,
|
||||
extension: string) {
|
||||
super(INTERNAL_ERROR, contentType);
|
||||
this.engine = engine;
|
||||
this.templatePath = resolveAssetPath(templatePath);
|
||||
this.descriptions = resolveAssetPath(descriptions);
|
||||
this.contentType = contentType;
|
||||
this.extension = extension;
|
||||
}
|
||||
|
||||
public async handle({ representation }: RepresentationConverterArgs): Promise<Representation> {
|
||||
@ -34,10 +47,26 @@ export class ErrorToTemplateConverter extends TypedRepresentationConverter {
|
||||
|
||||
// Render the template
|
||||
const { name, message, stack } = error;
|
||||
const variables = { name, message, stack };
|
||||
const description = await this.getErrorCodeMessage(error);
|
||||
const variables = { name, message, stack, description };
|
||||
const template = await fsPromises.readFile(this.templatePath, 'utf8');
|
||||
const html = this.engine.apply(template, variables);
|
||||
const rendered = this.engine.apply(template, variables);
|
||||
|
||||
return new BasicRepresentation(html, representation.metadata, this.contentType);
|
||||
return new BasicRepresentation(rendered, representation.metadata, this.contentType);
|
||||
}
|
||||
|
||||
private async getErrorCodeMessage(error: Error): Promise<string | undefined> {
|
||||
if (HttpError.isInstance(error) && error.options.errorCode) {
|
||||
const filePath = joinFilePath(this.descriptions, `${error.options.errorCode}${this.extension}`);
|
||||
let template: string;
|
||||
try {
|
||||
template = await fsPromises.readFile(filePath, 'utf8');
|
||||
} catch {
|
||||
// In case no template is found we still want to convert
|
||||
return;
|
||||
}
|
||||
|
||||
return this.engine.apply(template, (error.options.details ?? {}) as NodeJS.Dict<string>);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
42
src/storage/conversion/MarkdownToHtmlConverter.ts
Normal file
42
src/storage/conversion/MarkdownToHtmlConverter.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { promises as fsPromises } from 'fs';
|
||||
import marked from 'marked';
|
||||
import { BasicRepresentation } from '../../ldp/representation/BasicRepresentation';
|
||||
import type { Representation } from '../../ldp/representation/Representation';
|
||||
import type { TemplateEngine } from '../../pods/generate/TemplateEngine';
|
||||
import { TEXT_HTML, TEXT_MARKDOWN } from '../../util/ContentTypes';
|
||||
import { resolveAssetPath } from '../../util/PathUtil';
|
||||
import { readableToString } from '../../util/StreamUtil';
|
||||
import type { RepresentationConverterArgs } from './RepresentationConverter';
|
||||
import { TypedRepresentationConverter } from './TypedRepresentationConverter';
|
||||
|
||||
/**
|
||||
* Converts markdown data to HTML.
|
||||
* The generated HTML will be injected into the given template using the parameter `htmlBody`.
|
||||
* A standard markdown string will be converted to a <p> tag, so html and body tags should be part of the template.
|
||||
* In case the markdown body starts with a header (#), that value will also be used as `title` parameter.
|
||||
*/
|
||||
export class MarkdownToHtmlConverter extends TypedRepresentationConverter {
|
||||
private readonly engine: TemplateEngine;
|
||||
private readonly templatePath: string;
|
||||
|
||||
public constructor(engine: TemplateEngine, templatePath: string) {
|
||||
super(TEXT_MARKDOWN, TEXT_HTML);
|
||||
this.engine = engine;
|
||||
this.templatePath = resolveAssetPath(templatePath);
|
||||
}
|
||||
|
||||
public async handle({ representation }: RepresentationConverterArgs): Promise<Representation> {
|
||||
const markdown = await readableToString(representation.data);
|
||||
|
||||
// See if there is a title we can use
|
||||
const match = /^\s*#+\s*([^\n]+)\n/u.exec(markdown);
|
||||
const title = match?.[1];
|
||||
|
||||
const htmlBody = marked(markdown);
|
||||
|
||||
const template = await fsPromises.readFile(this.templatePath, 'utf8');
|
||||
const html = this.engine.apply(template, { htmlBody, title });
|
||||
|
||||
return new BasicRepresentation(html, representation.metadata, TEXT_HTML);
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ export const APPLICATION_OCTET_STREAM = 'application/octet-stream';
|
||||
export const APPLICATION_SPARQL_UPDATE = 'application/sparql-update';
|
||||
export const APPLICATION_X_WWW_FORM_URLENCODED = 'application/x-www-form-urlencoded';
|
||||
export const TEXT_HTML = 'text/html';
|
||||
export const TEXT_MARKDOWN = 'text/markdown';
|
||||
export const TEXT_TURTLE = 'text/turtle';
|
||||
|
||||
// Internal content types (not exposed over HTTP)
|
||||
|
@ -3,6 +3,7 @@ import { isError } from './ErrorUtil';
|
||||
export interface HttpErrorOptions {
|
||||
cause?: unknown;
|
||||
errorCode?: string;
|
||||
details?: NodeJS.Dict<unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -13,7 +13,8 @@ export abstract class BaseIdentifierStrategy implements IdentifierStrategy {
|
||||
|
||||
public getParentContainer(identifier: ResourceIdentifier): ResourceIdentifier {
|
||||
if (!this.supportsIdentifier(identifier)) {
|
||||
throw new InternalServerError(`The identifier ${identifier.path} is outside the configured identifier space.`);
|
||||
throw new InternalServerError(`The identifier ${identifier.path} is outside the configured identifier space.`,
|
||||
{ errorCode: 'E0001', details: { path: identifier.path }});
|
||||
}
|
||||
if (this.isRootContainer(identifier)) {
|
||||
throw new InternalServerError(`Cannot obtain the parent of ${identifier.path} because it is a root container.`);
|
||||
|
5
templates/error/descriptions/E0001
Normal file
5
templates/error/descriptions/E0001
Normal file
@ -0,0 +1,5 @@
|
||||
### Requests to `{{ path }}` are not supported
|
||||
The Community Solid Server received a request for `{{ path }}`, which is not configured.
|
||||
Here are some things you can try to fix this:
|
||||
- Have you started the server with the right hostname?
|
||||
- If you are running the server behind a reverse proxy, did you set up the `Forwarded` header correctly?
|
@ -1,15 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<title>{{ name }}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{ name }}</h1>
|
||||
<p>{{ message }}</p>
|
||||
{{#if stack}}
|
||||
<pre><code>{{ stack }}</code></pre>
|
||||
{{/if}}
|
||||
</body>
|
||||
</html>
|
14
templates/error/main.md
Normal file
14
templates/error/main.md
Normal file
@ -0,0 +1,14 @@
|
||||
# {{ name }}
|
||||
|
||||
{{#if description}}
|
||||
{{{ description }}}
|
||||
{{/if}}
|
||||
|
||||
## Technical details
|
||||
{{ message }}
|
||||
|
||||
{{#if stack}}
|
||||
```
|
||||
{{ stack }}
|
||||
```
|
||||
{{/if}}
|
14
templates/main.html
Normal file
14
templates/main.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
{{#if title}}
|
||||
<title>{{ title }}</title>
|
||||
{{/if}}
|
||||
</head>
|
||||
<body>
|
||||
{{! Triple braces to prevent HTML escaping }}
|
||||
{{{ htmlBody }}}
|
||||
</body>
|
||||
</html>
|
@ -4,29 +4,32 @@ import { ErrorToTemplateConverter } from '../../../../src/storage/conversion/Err
|
||||
import { BadRequestHttpError } from '../../../../src/util/errors/BadRequestHttpError';
|
||||
import { InternalServerError } from '../../../../src/util/errors/InternalServerError';
|
||||
import { readableToString } from '../../../../src/util/StreamUtil';
|
||||
import { mockFs } from '../../../util/Util';
|
||||
|
||||
const mockRead = jest.fn().mockResolvedValue('{{ template }}');
|
||||
jest.mock('fs', (): any => ({
|
||||
promises: { readFile: (...args: any[]): any => mockRead(...args) },
|
||||
}));
|
||||
jest.mock('fs');
|
||||
|
||||
describe('An ErrorToTemplateConverter', (): void => {
|
||||
let cache: { data: any };
|
||||
const identifier = { path: 'http://test.com/error' };
|
||||
const templatePath = '/templates/error.template';
|
||||
const descriptions = '/templates/codes';
|
||||
const errorCode = 'E0001';
|
||||
let engine: TemplateEngine;
|
||||
const path = '/template/error.template';
|
||||
let converter: ErrorToTemplateConverter;
|
||||
const preferences = {};
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
mockRead.mockClear();
|
||||
cache = mockFs('/templates');
|
||||
cache.data['error.template'] = '{{ template }}';
|
||||
cache.data.codes = { [`${errorCode}.html`]: '{{{ errorText }}}' };
|
||||
engine = {
|
||||
apply: jest.fn().mockReturnValue('<html>'),
|
||||
};
|
||||
|
||||
converter = new ErrorToTemplateConverter(engine, path, 'text/html');
|
||||
converter = new ErrorToTemplateConverter(engine, templatePath, descriptions, 'text/html', '.html');
|
||||
});
|
||||
|
||||
it('supports going from errors to quads.', async(): Promise<void> => {
|
||||
it('supports going from errors to the given content type.', async(): Promise<void> => {
|
||||
await expect(converter.getInputTypes()).resolves.toEqual({ 'internal/error': 1 });
|
||||
await expect(converter.getOutputTypes()).resolves.toEqual({ 'text/html': 1 });
|
||||
});
|
||||
@ -47,8 +50,6 @@ describe('An ErrorToTemplateConverter', (): void => {
|
||||
expect(result.binary).toBe(true);
|
||||
expect(result.metadata.contentType).toBe('text/html');
|
||||
await expect(readableToString(result.data)).resolves.toBe('<html>');
|
||||
expect(mockRead).toHaveBeenCalledTimes(1);
|
||||
expect(mockRead).toHaveBeenLastCalledWith(path, 'utf8');
|
||||
expect(engine.apply).toHaveBeenCalledTimes(1);
|
||||
expect(engine.apply).toHaveBeenLastCalledWith(
|
||||
'{{ template }}', { name: 'BadRequestHttpError', message: 'error text', stack: error.stack },
|
||||
@ -65,11 +66,63 @@ describe('An ErrorToTemplateConverter', (): void => {
|
||||
expect(result.binary).toBe(true);
|
||||
expect(result.metadata.contentType).toBe('text/html');
|
||||
await expect(readableToString(result.data)).resolves.toBe('<html>');
|
||||
expect(mockRead).toHaveBeenCalledTimes(1);
|
||||
expect(mockRead).toHaveBeenLastCalledWith(path, 'utf8');
|
||||
expect(engine.apply).toHaveBeenCalledTimes(1);
|
||||
expect(engine.apply).toHaveBeenLastCalledWith(
|
||||
'{{ template }}', { name: 'BadRequestHttpError', message: 'error text' },
|
||||
);
|
||||
});
|
||||
|
||||
it('adds additional information if an error code is found.', async(): Promise<void> => {
|
||||
const error = new BadRequestHttpError('error text', { errorCode, details: { key: 'val' }});
|
||||
const representation = new BasicRepresentation([ error ], 'internal/error', false);
|
||||
const prom = converter.handle({ identifier, representation, preferences });
|
||||
await expect(prom).resolves.toBeDefined();
|
||||
const result = await prom;
|
||||
expect(result.binary).toBe(true);
|
||||
expect(result.metadata.contentType).toBe('text/html');
|
||||
await expect(readableToString(result.data)).resolves.toBe('<html>');
|
||||
expect(engine.apply).toHaveBeenCalledTimes(2);
|
||||
expect(engine.apply).toHaveBeenCalledWith(
|
||||
'{{{ errorText }}}', { key: 'val' },
|
||||
);
|
||||
expect(engine.apply).toHaveBeenLastCalledWith(
|
||||
'{{ template }}',
|
||||
{ name: 'BadRequestHttpError', message: 'error text', stack: error.stack, description: '<html>' },
|
||||
);
|
||||
});
|
||||
|
||||
it('sends an empty object for additional error code parameters if none are defined.', async(): Promise<void> => {
|
||||
const error = new BadRequestHttpError('error text', { errorCode });
|
||||
const representation = new BasicRepresentation([ error ], 'internal/error', false);
|
||||
const prom = converter.handle({ identifier, representation, preferences });
|
||||
await expect(prom).resolves.toBeDefined();
|
||||
const result = await prom;
|
||||
expect(result.binary).toBe(true);
|
||||
expect(result.metadata.contentType).toBe('text/html');
|
||||
await expect(readableToString(result.data)).resolves.toBe('<html>');
|
||||
expect(engine.apply).toHaveBeenCalledTimes(2);
|
||||
expect(engine.apply).toHaveBeenCalledWith(
|
||||
'{{{ errorText }}}', { },
|
||||
);
|
||||
expect(engine.apply).toHaveBeenLastCalledWith(
|
||||
'{{ template }}',
|
||||
{ name: 'BadRequestHttpError', message: 'error text', stack: error.stack, description: '<html>' },
|
||||
);
|
||||
});
|
||||
|
||||
it('converts errors with a code as usual if no corresponding template is found.', async(): Promise<void> => {
|
||||
const error = new BadRequestHttpError('error text', { errorCode: 'invalid' });
|
||||
const representation = new BasicRepresentation([ error ], 'internal/error', false);
|
||||
const prom = converter.handle({ identifier, representation, preferences });
|
||||
await expect(prom).resolves.toBeDefined();
|
||||
const result = await prom;
|
||||
expect(result.binary).toBe(true);
|
||||
expect(result.metadata.contentType).toBe('text/html');
|
||||
await expect(readableToString(result.data)).resolves.toBe('<html>');
|
||||
expect(engine.apply).toHaveBeenCalledTimes(1);
|
||||
expect(engine.apply).toHaveBeenLastCalledWith(
|
||||
'{{ template }}',
|
||||
{ name: 'BadRequestHttpError', message: 'error text', stack: error.stack },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
61
test/unit/storage/conversion/MarkdownToHtmlConverter.test.ts
Normal file
61
test/unit/storage/conversion/MarkdownToHtmlConverter.test.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { BasicRepresentation } from '../../../../src/ldp/representation/BasicRepresentation';
|
||||
import type { TemplateEngine } from '../../../../src/pods/generate/TemplateEngine';
|
||||
import { MarkdownToHtmlConverter } from '../../../../src/storage/conversion/MarkdownToHtmlConverter';
|
||||
import { readableToString } from '../../../../src/util/StreamUtil';
|
||||
import { mockFs } from '../../../util/Util';
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
describe('A MarkdownToHtmlConverter', (): void => {
|
||||
let cache: { data: any };
|
||||
const identifier = { path: 'http://test.com/text' };
|
||||
const templatePath = '/templates/error.template';
|
||||
const preferences = {};
|
||||
let engine: TemplateEngine;
|
||||
let converter: MarkdownToHtmlConverter;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
cache = mockFs('/templates');
|
||||
cache.data['error.template'] = '{{ template }}';
|
||||
engine = {
|
||||
apply: jest.fn().mockReturnValue('<html>'),
|
||||
};
|
||||
|
||||
converter = new MarkdownToHtmlConverter(engine, templatePath);
|
||||
});
|
||||
|
||||
it('supports going from markdown to html.', async(): Promise<void> => {
|
||||
await expect(converter.getInputTypes()).resolves.toEqual({ 'text/markdown': 1 });
|
||||
await expect(converter.getOutputTypes()).resolves.toEqual({ 'text/html': 1 });
|
||||
});
|
||||
|
||||
it('converts markdown and inserts it in the template.', async(): Promise<void> => {
|
||||
const markdown = 'Text `code` more text.';
|
||||
const representation = new BasicRepresentation(markdown, 'text/markdown', true);
|
||||
const prom = converter.handle({ identifier, representation, preferences });
|
||||
await expect(prom).resolves.toBeDefined();
|
||||
const result = await prom;
|
||||
expect(result.binary).toBe(true);
|
||||
expect(result.metadata.contentType).toBe('text/html');
|
||||
await expect(readableToString(result.data)).resolves.toBe('<html>');
|
||||
expect(engine.apply).toHaveBeenCalledTimes(1);
|
||||
expect(engine.apply).toHaveBeenLastCalledWith(
|
||||
'{{ template }}', { htmlBody: '<p>Text <code>code</code> more text.</p>\n' },
|
||||
);
|
||||
});
|
||||
|
||||
it('uses the main markdown header as title if there is one.', async(): Promise<void> => {
|
||||
const markdown = '# title text\nmore text';
|
||||
const representation = new BasicRepresentation(markdown, 'text/markdown', true);
|
||||
const prom = converter.handle({ identifier, representation, preferences });
|
||||
await expect(prom).resolves.toBeDefined();
|
||||
const result = await prom;
|
||||
expect(result.binary).toBe(true);
|
||||
expect(result.metadata.contentType).toBe('text/html');
|
||||
await expect(readableToString(result.data)).resolves.toBe('<html>');
|
||||
expect(engine.apply).toHaveBeenCalledTimes(1);
|
||||
expect(engine.apply).toHaveBeenLastCalledWith(
|
||||
'{{ template }}', { htmlBody: '<h1 id="title-text">title text</h1>\n<p>more text</p>\n', title: 'title text' },
|
||||
);
|
||||
});
|
||||
});
|
@ -22,6 +22,8 @@ describe('A BaseIdentifierStrategy', (): void => {
|
||||
it('errors when attempting to get the parent of an unsupported identifier.', async(): Promise<void> => {
|
||||
expect((): any => strategy.getParentContainer({ path: '/unsupported' }))
|
||||
.toThrow('The identifier /unsupported is outside the configured identifier space.');
|
||||
expect((): any => strategy.getParentContainer({ path: '/unsupported' }))
|
||||
.toThrow(expect.objectContaining({ options: { errorCode: 'E0001', details: { path: '/unsupported' }}}));
|
||||
});
|
||||
|
||||
it('errors when attempting to get the parent of a root container.', async(): Promise<void> => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user