mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Move all metadata file knowledge to file mapper
This commit is contained in:
parent
6495d650c2
commit
57da67f9ee
@ -26,7 +26,6 @@ export class TemplatedResourcesGenerator implements ResourcesGenerator {
|
||||
private readonly templateFolder: string;
|
||||
private readonly factory: FileIdentifierMapperFactory;
|
||||
private readonly engine: TemplateEngine;
|
||||
private readonly metaExtension = '.meta';
|
||||
|
||||
/**
|
||||
* A mapper is needed to convert the template file paths to identifiers relative to the given base identifier.
|
||||
@ -91,9 +90,8 @@ export class TemplatedResourcesGenerator implements ResourcesGenerator {
|
||||
const links: Record<string, { link: ResourceLink; meta?: ResourceLink }> = { };
|
||||
for await (const link of linkGen) {
|
||||
const { path } = link.identifier;
|
||||
if (this.isMeta(path)) {
|
||||
const resourcePath = this.metaToResource(link.identifier).path;
|
||||
links[resourcePath] = Object.assign(links[resourcePath] || {}, { meta: link });
|
||||
if (link.isMetadata) {
|
||||
links[path] = Object.assign(links[path] || {}, { meta: link });
|
||||
} else {
|
||||
links[path] = Object.assign(links[path] || {}, { link });
|
||||
}
|
||||
@ -135,11 +133,10 @@ export class TemplatedResourcesGenerator implements ResourcesGenerator {
|
||||
*/
|
||||
private async generateMetadata(metaLink: ResourceLink, options: Dict<string>):
|
||||
Promise<RepresentationMetadata> {
|
||||
const identifier = this.metaToResource(metaLink.identifier);
|
||||
const metadata = new RepresentationMetadata(identifier);
|
||||
const metadata = new RepresentationMetadata(metaLink.identifier);
|
||||
|
||||
const data = await this.parseTemplate(metaLink.filePath, options);
|
||||
const parser = new Parser({ format: metaLink.contentType, baseIRI: identifier.path });
|
||||
const parser = new Parser({ format: metaLink.contentType, baseIRI: metaLink.identifier.path });
|
||||
const quads = parser.parse(data);
|
||||
metadata.addQuads(quads);
|
||||
|
||||
@ -153,18 +150,4 @@ export class TemplatedResourcesGenerator implements ResourcesGenerator {
|
||||
const raw = await fsPromises.readFile(filePath, 'utf8');
|
||||
return this.engine.apply(raw, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if the given path corresponds to a metadata file.
|
||||
*/
|
||||
private isMeta(path: string): boolean {
|
||||
return path.endsWith(this.metaExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a generated metadata identifier to the identifier of its corresponding resource.
|
||||
*/
|
||||
private metaToResource(metaIdentifier: ResourceIdentifier): ResourceIdentifier {
|
||||
return { path: metaIdentifier.path.slice(0, -this.metaExtension.length) };
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ export class RootFilePathHandler extends VariableHandler {
|
||||
|
||||
public async handle({ identifier, settings }: { identifier: ResourceIdentifier; settings: PodSettings }):
|
||||
Promise<void> {
|
||||
const path = (await this.fileMapper.mapUrlToFilePath(identifier)).filePath;
|
||||
const path = (await this.fileMapper.mapUrlToFilePath(identifier, false)).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.
|
||||
|
@ -5,7 +5,6 @@ import type { Quad } from 'rdf-js';
|
||||
import type { Representation } from '../../ldp/representation/Representation';
|
||||
import { RepresentationMetadata } from '../../ldp/representation/RepresentationMetadata';
|
||||
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
|
||||
import { ConflictHttpError } from '../../util/errors/ConflictHttpError';
|
||||
import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError';
|
||||
import { isSystemError } from '../../util/errors/SystemError';
|
||||
import { UnsupportedMediaTypeHttpError } from '../../util/errors/UnsupportedMediaTypeHttpError';
|
||||
@ -43,7 +42,7 @@ export class FileDataAccessor implements DataAccessor {
|
||||
* Will throw NotFoundHttpError if the input is a container.
|
||||
*/
|
||||
public async getData(identifier: ResourceIdentifier): Promise<Guarded<Readable>> {
|
||||
const link = await this.resourceMapper.mapUrlToFilePath(identifier);
|
||||
const link = await this.resourceMapper.mapUrlToFilePath(identifier, false);
|
||||
const stats = await this.getStats(link.filePath);
|
||||
|
||||
if (stats.isFile()) {
|
||||
@ -58,7 +57,7 @@ export class FileDataAccessor implements DataAccessor {
|
||||
* and adding file system specific metadata elements.
|
||||
*/
|
||||
public async getMetadata(identifier: ResourceIdentifier): Promise<RepresentationMetadata> {
|
||||
const link = await this.resourceMapper.mapUrlToFilePath(identifier);
|
||||
const link = await this.resourceMapper.mapUrlToFilePath(identifier, false);
|
||||
const stats = await this.getStats(link.filePath);
|
||||
if (!isContainerIdentifier(identifier) && stats.isFile()) {
|
||||
return this.getFileMetadata(link, stats);
|
||||
@ -70,7 +69,7 @@ export class FileDataAccessor implements DataAccessor {
|
||||
}
|
||||
|
||||
public async* getChildren(identifier: ResourceIdentifier): AsyncIterableIterator<RepresentationMetadata> {
|
||||
const link = await this.resourceMapper.mapUrlToFilePath(identifier);
|
||||
const link = await this.resourceMapper.mapUrlToFilePath(identifier, false);
|
||||
yield* this.getChildMetadata(link);
|
||||
}
|
||||
|
||||
@ -80,10 +79,7 @@ export class FileDataAccessor implements DataAccessor {
|
||||
*/
|
||||
public async writeDocument(identifier: ResourceIdentifier, data: Guarded<Readable>, metadata: RepresentationMetadata):
|
||||
Promise<void> {
|
||||
if (this.isMetadataPath(identifier.path)) {
|
||||
throw new ConflictHttpError('Not allowed to create files with the metadata extension.');
|
||||
}
|
||||
const link = await this.resourceMapper.mapUrlToFilePath(identifier, metadata.contentType);
|
||||
const link = await this.resourceMapper.mapUrlToFilePath(identifier, false, metadata.contentType);
|
||||
|
||||
// Check if we already have a corresponding file with a different extension
|
||||
await this.verifyExistingExtension(link);
|
||||
@ -95,7 +91,8 @@ export class FileDataAccessor implements DataAccessor {
|
||||
} catch (error: unknown) {
|
||||
// Delete the metadata if there was an error writing the file
|
||||
if (wroteMetadata) {
|
||||
await fsPromises.unlink((await this.getMetadataLink(link.identifier)).filePath);
|
||||
const metaLink = await this.resourceMapper.mapUrlToFilePath(identifier, true);
|
||||
await fsPromises.unlink(metaLink.filePath);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
@ -105,7 +102,7 @@ export class FileDataAccessor implements DataAccessor {
|
||||
* Creates corresponding folder if necessary and writes metadata to metadata file if necessary.
|
||||
*/
|
||||
public async writeContainer(identifier: ResourceIdentifier, metadata: RepresentationMetadata): Promise<void> {
|
||||
const link = await this.resourceMapper.mapUrlToFilePath(identifier);
|
||||
const link = await this.resourceMapper.mapUrlToFilePath(identifier, false);
|
||||
try {
|
||||
await fsPromises.mkdir(link.filePath, { recursive: true });
|
||||
} catch (error: unknown) {
|
||||
@ -122,11 +119,12 @@ export class FileDataAccessor implements DataAccessor {
|
||||
* Removes the corresponding file/folder (and metadata file).
|
||||
*/
|
||||
public async deleteResource(identifier: ResourceIdentifier): Promise<void> {
|
||||
const link = await this.resourceMapper.mapUrlToFilePath(identifier);
|
||||
const link = await this.resourceMapper.mapUrlToFilePath(identifier, false);
|
||||
const stats = await this.getStats(link.filePath);
|
||||
|
||||
try {
|
||||
await fsPromises.unlink((await this.getMetadataLink(link.identifier)).filePath);
|
||||
const metaLink = await this.resourceMapper.mapUrlToFilePath(identifier, true);
|
||||
await fsPromises.unlink(metaLink.filePath);
|
||||
} catch (error: unknown) {
|
||||
// Ignore if it doesn't exist
|
||||
if (!isSystemError(error) || error.code !== 'ENOENT') {
|
||||
@ -161,21 +159,6 @@ export class FileDataAccessor implements DataAccessor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates ResourceLink that corresponds to the metadata resource of the given identifier.
|
||||
*/
|
||||
private async getMetadataLink(identifier: ResourceIdentifier): Promise<ResourceLink> {
|
||||
const metaIdentifier = { path: `${identifier.path}.meta` };
|
||||
return this.resourceMapper.mapUrlToFilePath(metaIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given path is a metadata path.
|
||||
*/
|
||||
private isMetadataPath(path: string): boolean {
|
||||
return path.endsWith('.meta');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and generates all metadata relevant for the given file,
|
||||
* ingesting it into a RepresentationMetadata object.
|
||||
@ -215,7 +198,7 @@ export class FileDataAccessor implements DataAccessor {
|
||||
metadata.remove(RDF.type, LDP.terms.BasicContainer);
|
||||
metadata.removeAll(CONTENT_TYPE);
|
||||
const quads = metadata.quads();
|
||||
const metadataLink = await this.getMetadataLink(link.identifier);
|
||||
const metadataLink = await this.resourceMapper.mapUrlToFilePath(link.identifier, true);
|
||||
let wroteMetadata: boolean;
|
||||
|
||||
// Write metadata to file if there are quads remaining
|
||||
@ -263,7 +246,7 @@ export class FileDataAccessor implements DataAccessor {
|
||||
*/
|
||||
private async getRawMetadata(identifier: ResourceIdentifier): Promise<Quad[]> {
|
||||
try {
|
||||
const metadataLink = await this.getMetadataLink(identifier);
|
||||
const metadataLink = await this.resourceMapper.mapUrlToFilePath(identifier, true);
|
||||
|
||||
// Check if the metadata file exists first
|
||||
await fsPromises.lstat(metadataLink.filePath);
|
||||
@ -290,10 +273,6 @@ export class FileDataAccessor implements DataAccessor {
|
||||
// For every child in the container we want to generate specific metadata
|
||||
for await (const entry of dir) {
|
||||
const childName = entry.name;
|
||||
// Hide metadata files
|
||||
if (this.isMetadataPath(childName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ignore non-file/directory entries in the folder
|
||||
if (!entry.isFile() && !entry.isDirectory()) {
|
||||
@ -304,6 +283,11 @@ export class FileDataAccessor implements DataAccessor {
|
||||
const childLink = await this.resourceMapper
|
||||
.mapFilePathToUrl(joinFilePath(link.filePath, childName), entry.isDirectory());
|
||||
|
||||
// Hide metadata files
|
||||
if (childLink.isMetadata) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Generate metadata of this specific child
|
||||
const childStats = await fsPromises.lstat(joinFilePath(link.filePath, childName));
|
||||
const metadata = new RepresentationMetadata(childLink.identifier);
|
||||
@ -340,7 +324,7 @@ export class FileDataAccessor implements DataAccessor {
|
||||
private async verifyExistingExtension(link: ResourceLink): Promise<void> {
|
||||
try {
|
||||
// Delete the old file with the (now) wrong extension
|
||||
const oldLink = await this.resourceMapper.mapUrlToFilePath(link.identifier);
|
||||
const oldLink = await this.resourceMapper.mapUrlToFilePath(link.identifier, false);
|
||||
if (oldLink.filePath !== link.filePath) {
|
||||
await fsPromises.unlink(oldLink.filePath);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdenti
|
||||
import { getLoggerFor } from '../../logging/LogUtil';
|
||||
import { APPLICATION_OCTET_STREAM } from '../../util/ContentTypes';
|
||||
import { BadRequestHttpError } from '../../util/errors/BadRequestHttpError';
|
||||
import { ConflictHttpError } from '../../util/errors/ConflictHttpError';
|
||||
import { InternalServerError } from '../../util/errors/InternalServerError';
|
||||
import { NotFoundHttpError } from '../../util/errors/NotFoundHttpError';
|
||||
import {
|
||||
@ -33,12 +34,23 @@ export class BaseFileIdentifierMapper implements FileIdentifierMapper {
|
||||
* Determines the content type if none was provided.
|
||||
* For containers the content-type input is ignored.
|
||||
* @param identifier - The input identifier.
|
||||
* @param isMetadata - If we need the data or metadata file path.
|
||||
* @param contentType - The content-type provided with the request.
|
||||
*
|
||||
* @returns A ResourceLink with all the necessary metadata.
|
||||
*/
|
||||
public async mapUrlToFilePath(identifier: ResourceIdentifier, contentType?: string): Promise<ResourceLink> {
|
||||
const path = this.getRelativePath(identifier);
|
||||
public async mapUrlToFilePath(identifier: ResourceIdentifier, isMetadata: boolean, contentType?: string):
|
||||
Promise<ResourceLink> {
|
||||
// Technically we could allow paths ending on .meta as long as we make sure there is never a mixup.
|
||||
// But this can lead to potential issues.
|
||||
// This also immediately stops users that expect they can update metadata like this.
|
||||
if (this.isMetadataPath(identifier.path)) {
|
||||
throw new ConflictHttpError('Not allowed to create files with the metadata extension.');
|
||||
}
|
||||
let path = this.getRelativePath(identifier);
|
||||
if (isMetadata) {
|
||||
path += '.meta';
|
||||
}
|
||||
this.validateRelativePath(path, identifier);
|
||||
|
||||
const filePath = this.getAbsolutePath(path);
|
||||
@ -57,7 +69,7 @@ export class BaseFileIdentifierMapper implements FileIdentifierMapper {
|
||||
*/
|
||||
protected async mapUrlToContainerPath(identifier: ResourceIdentifier, filePath: string): Promise<ResourceLink> {
|
||||
this.logger.debug(`URL ${identifier.path} points to the container ${filePath}`);
|
||||
return { identifier, filePath };
|
||||
return { identifier, filePath, isMetadata: this.isMetadataPath(filePath) };
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,7 +87,7 @@ export class BaseFileIdentifierMapper implements FileIdentifierMapper {
|
||||
Promise<ResourceLink> {
|
||||
contentType = await this.getContentTypeFromUrl(identifier, contentType);
|
||||
this.logger.debug(`The path for ${identifier.path} is ${filePath}`);
|
||||
return { identifier, filePath, contentType };
|
||||
return { identifier, filePath, contentType, isMetadata: this.isMetadataPath(filePath) };
|
||||
}
|
||||
|
||||
/**
|
||||
@ -113,7 +125,11 @@ export class BaseFileIdentifierMapper implements FileIdentifierMapper {
|
||||
this.logger.debug(`Document ${filePath} maps to URL ${url}`);
|
||||
contentType = await this.getContentTypeFromPath(filePath);
|
||||
}
|
||||
return { identifier: { path: url }, filePath, contentType };
|
||||
const isMetadata = this.isMetadataPath(filePath);
|
||||
if (isMetadata) {
|
||||
url = url.slice(0, -'.meta'.length);
|
||||
}
|
||||
return { identifier: { path: url }, filePath, contentType, isMetadata };
|
||||
}
|
||||
|
||||
/**
|
||||
@ -194,4 +210,11 @@ export class BaseFileIdentifierMapper implements FileIdentifierMapper {
|
||||
throw new BadRequestHttpError('Disallowed /.. segment in URL');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given path is a metadata path.
|
||||
*/
|
||||
protected isMetadataPath(path: string): boolean {
|
||||
return path.endsWith('.meta');
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,10 @@ export interface ResourceLink {
|
||||
* Content-type for a document (not defined for containers).
|
||||
*/
|
||||
contentType?: string;
|
||||
/**
|
||||
* If the resource is a metadata file.
|
||||
*/
|
||||
isMetadata: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -33,11 +37,13 @@ export interface FileIdentifierMapper {
|
||||
* If there is no corresponding file a file path will be generated.
|
||||
* For containers the content-type input gets ignored.
|
||||
* @param identifier - The input identifier.
|
||||
* @param isMetadata - If we are mapping the metadata of the resource instead of its data.
|
||||
* @param contentType - The (optional) content-type of the resource.
|
||||
*
|
||||
* @returns A ResourceLink with all the necessary metadata.
|
||||
*/
|
||||
mapUrlToFilePath: (identifier: ResourceIdentifier, contentType?: string) => Promise<ResourceLink>;
|
||||
mapUrlToFilePath: (identifier: ResourceIdentifier, isMetadata: boolean, contentType?: string) =>
|
||||
Promise<ResourceLink>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -18,11 +18,16 @@ class DummyFactory implements FileIdentifierMapperFactory {
|
||||
const trimRoot = trimTrailingSlashes(rootFilePath);
|
||||
return {
|
||||
async mapFilePathToUrl(filePath: string, isContainer: boolean): Promise<ResourceLink> {
|
||||
const path = `${trimBase}${filePath.slice(trimRoot.length)}`;
|
||||
let path = `${trimBase}${filePath.slice(trimRoot.length)}`;
|
||||
const isMetadata = filePath.endsWith('.meta');
|
||||
if (isMetadata) {
|
||||
path = path.slice(0, -'.meta'.length);
|
||||
}
|
||||
return {
|
||||
identifier: { path: isContainer ? ensureTrailingSlash(path) : path },
|
||||
filePath,
|
||||
contentType: isContainer ? undefined : 'text/turtle',
|
||||
isMetadata,
|
||||
};
|
||||
},
|
||||
} as any;
|
||||
|
@ -23,6 +23,7 @@ describe('A RootFilePathHandler', (): void => {
|
||||
mapUrlToFilePath: async(id): Promise<ResourceLink> => ({
|
||||
identifier: id,
|
||||
filePath: joinFilePath(rootFilePath, id.path.slice(baseUrl.length)),
|
||||
isMetadata: false,
|
||||
}),
|
||||
mapFilePathToUrl: jest.fn(),
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { BaseFileIdentifierMapper } from '../../../../src/storage/mapping/BaseFileIdentifierMapper';
|
||||
import { BadRequestHttpError } from '../../../../src/util/errors/BadRequestHttpError';
|
||||
import { ConflictHttpError } from '../../../../src/util/errors/ConflictHttpError';
|
||||
import { NotFoundHttpError } from '../../../../src/util/errors/NotFoundHttpError';
|
||||
import { trimTrailingSlashes } from '../../../../src/util/PathUtil';
|
||||
|
||||
@ -12,51 +13,71 @@ describe('An BaseFileIdentifierMapper', (): void => {
|
||||
|
||||
describe('mapUrlToFilePath', (): void => {
|
||||
it('throws 404 if the input path does not contain the base.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: 'invalid' })).rejects.toThrow(NotFoundHttpError);
|
||||
await expect(mapper.mapUrlToFilePath({ path: 'invalid' }, false)).rejects.toThrow(NotFoundHttpError);
|
||||
});
|
||||
|
||||
it('throws 404 if the relative path does not start with a slash.', async(): Promise<void> => {
|
||||
const result = mapper.mapUrlToFilePath({ path: `${trimTrailingSlashes(base)}test` });
|
||||
const result = mapper.mapUrlToFilePath({ path: `${trimTrailingSlashes(base)}test` }, false);
|
||||
await expect(result).rejects.toThrow(BadRequestHttpError);
|
||||
await expect(result).rejects.toThrow('URL needs a / after the base');
|
||||
});
|
||||
|
||||
it('throws 400 if the input path contains relative parts.', async(): Promise<void> => {
|
||||
const result = mapper.mapUrlToFilePath({ path: `${base}test/../test2` });
|
||||
const result = mapper.mapUrlToFilePath({ path: `${base}test/../test2` }, false);
|
||||
await expect(result).rejects.toThrow(BadRequestHttpError);
|
||||
await expect(result).rejects.toThrow('Disallowed /.. segment in URL');
|
||||
});
|
||||
|
||||
it('returns the corresponding file path for container identifiers.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}container/` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}container/` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}container/` },
|
||||
filePath: `${rootFilepath}container/`,
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the default content-type.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test` },
|
||||
filePath: `${rootFilepath}test`,
|
||||
contentType: 'application/octet-stream',
|
||||
isMetadata: false,
|
||||
});
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test.ttl` },
|
||||
filePath: `${rootFilepath}test.ttl`,
|
||||
contentType: 'application/octet-stream',
|
||||
isMetadata: false,
|
||||
});
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test.txt` },
|
||||
filePath: `${rootFilepath}test.txt`,
|
||||
contentType: 'application/octet-stream',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('generates a file path if supported content-type was provided.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` }, 'text/turtle')).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` }, false, 'text/turtle')).resolves.toEqual({
|
||||
identifier: { path: `${base}test.ttl` },
|
||||
filePath: `${rootFilepath}test.ttl`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors on metadata identifiers.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.meta` }, true)).rejects.toThrow(ConflictHttpError);
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.meta` }, true))
|
||||
.rejects.toThrow('Not allowed to create files with the metadata extension.');
|
||||
});
|
||||
|
||||
it('generates correct metadata file paths.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt` }, true)).resolves.toEqual({
|
||||
identifier: { path: `${base}test.txt` },
|
||||
filePath: `${rootFilepath}test.txt.meta`,
|
||||
contentType: 'application/octet-stream',
|
||||
isMetadata: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -70,6 +91,7 @@ describe('An BaseFileIdentifierMapper', (): void => {
|
||||
await expect(mapper.mapFilePathToUrl(`${rootFilepath}container/`, true)).resolves.toEqual({
|
||||
identifier: { path: `${base}container/` },
|
||||
filePath: `${rootFilepath}container/`,
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
@ -78,16 +100,28 @@ describe('An BaseFileIdentifierMapper', (): void => {
|
||||
identifier: { path: `${base}test` },
|
||||
filePath: `${rootFilepath}test`,
|
||||
contentType: 'application/octet-stream',
|
||||
isMetadata: false,
|
||||
});
|
||||
await expect(mapper.mapFilePathToUrl(`${rootFilepath}test.ttl`, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test.ttl` },
|
||||
filePath: `${rootFilepath}test.ttl`,
|
||||
contentType: 'application/octet-stream',
|
||||
isMetadata: false,
|
||||
});
|
||||
await expect(mapper.mapFilePathToUrl(`${rootFilepath}test.txt`, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test.txt` },
|
||||
filePath: `${rootFilepath}test.txt`,
|
||||
contentType: 'application/octet-stream',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('identifies metadata files.', async(): Promise<void> => {
|
||||
await expect(mapper.mapFilePathToUrl(`${rootFilepath}test.meta`, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test` },
|
||||
filePath: `${rootFilepath}test.meta`,
|
||||
contentType: 'application/octet-stream',
|
||||
isMetadata: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -26,30 +26,31 @@ describe('An ExtensionBasedMapper', (): void => {
|
||||
|
||||
describe('mapUrlToFilePath', (): void => {
|
||||
it('throws 404 if the input path does not contain the base.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: 'invalid' })).rejects.toThrow(NotFoundHttpError);
|
||||
await expect(mapper.mapUrlToFilePath({ path: 'invalid' }, false)).rejects.toThrow(NotFoundHttpError);
|
||||
});
|
||||
|
||||
it('throws 404 if the relative path does not start with a slash.', async(): Promise<void> => {
|
||||
const result = mapper.mapUrlToFilePath({ path: `${trimTrailingSlashes(base)}test` });
|
||||
const result = mapper.mapUrlToFilePath({ path: `${trimTrailingSlashes(base)}test` }, false);
|
||||
await expect(result).rejects.toThrow(BadRequestHttpError);
|
||||
await expect(result).rejects.toThrow('URL needs a / after the base');
|
||||
});
|
||||
|
||||
it('throws 400 if the input path contains relative parts.', async(): Promise<void> => {
|
||||
const result = mapper.mapUrlToFilePath({ path: `${base}test/../test2` });
|
||||
const result = mapper.mapUrlToFilePath({ path: `${base}test/../test2` }, false);
|
||||
await expect(result).rejects.toThrow(BadRequestHttpError);
|
||||
await expect(result).rejects.toThrow('Disallowed /.. segment in URL');
|
||||
});
|
||||
|
||||
it('returns the corresponding file path for container identifiers.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}container/` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}container/` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}container/` },
|
||||
filePath: `${rootFilepath}container/`,
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects URLs that end with "$.{extension}".', async(): Promise<void> => {
|
||||
const result = mapper.mapUrlToFilePath({ path: `${base}test$.txt` });
|
||||
const result = mapper.mapUrlToFilePath({ path: `${base}test$.txt` }, false);
|
||||
await expect(result).rejects.toThrow(NotImplementedHttpError);
|
||||
await expect(result).rejects.toThrow('Identifiers cannot contain a dollar sign before their extension');
|
||||
});
|
||||
@ -58,58 +59,74 @@ describe('An ExtensionBasedMapper', (): void => {
|
||||
fsPromises.readdir.mockImplementation((): void => {
|
||||
throw new Error('does not exist');
|
||||
});
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}no/test.txt` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}no/test.txt` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}no/test.txt` },
|
||||
filePath: `${rootFilepath}no/test.txt`,
|
||||
contentType: 'text/plain',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('determines content-type by extension when looking for a file that does not exist.', async(): Promise<void> => {
|
||||
fsPromises.readdir.mockReturnValue([ 'test.ttl' ]);
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test.txt` },
|
||||
filePath: `${rootFilepath}test.txt`,
|
||||
contentType: 'text/plain',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('determines the content-type based on the extension.', async(): Promise<void> => {
|
||||
fsPromises.readdir.mockReturnValue([ 'test.txt' ]);
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test.txt` },
|
||||
filePath: `${rootFilepath}test.txt`,
|
||||
contentType: 'text/plain',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('determines the content-type correctly for metadata files.', async(): Promise<void> => {
|
||||
fsPromises.readdir.mockReturnValue([ 'test.meta' ]);
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test` }, true)).resolves.toEqual({
|
||||
identifier: { path: `${base}test` },
|
||||
filePath: `${rootFilepath}test.meta`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('matches even if the content-type does not match the extension.', async(): Promise<void> => {
|
||||
fsPromises.readdir.mockReturnValue([ 'test.txt$.ttl' ]);
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test.txt` },
|
||||
filePath: `${rootFilepath}test.txt$.ttl`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('generates a file path if the content-type was provided.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt` }, 'text/plain')).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt` }, false, 'text/plain')).resolves.toEqual({
|
||||
identifier: { path: `${base}test.txt` },
|
||||
filePath: `${rootFilepath}test.txt`,
|
||||
contentType: 'text/plain',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('adds an extension if the given extension does not match the given content-type.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt` }, 'text/turtle')).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt` }, false, 'text/turtle')).resolves.toEqual({
|
||||
identifier: { path: `${base}test.txt` },
|
||||
filePath: `${rootFilepath}test.txt$.ttl`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('throws 501 if the given content-type is not recognized.', async(): Promise<void> => {
|
||||
const result = mapper.mapUrlToFilePath({ path: `${base}test.txt` }, 'fake/data');
|
||||
const result = mapper.mapUrlToFilePath({ path: `${base}test.txt` }, false, 'fake/data');
|
||||
await expect(result).rejects.toThrow(NotImplementedHttpError);
|
||||
await expect(result).rejects.toThrow('Unsupported content type fake/data');
|
||||
});
|
||||
@ -124,6 +141,7 @@ describe('An ExtensionBasedMapper', (): void => {
|
||||
await expect(mapper.mapFilePathToUrl(`${rootFilepath}container/`, true)).resolves.toEqual({
|
||||
identifier: { path: `${base}container/` },
|
||||
filePath: `${rootFilepath}container/`,
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
@ -132,6 +150,16 @@ describe('An ExtensionBasedMapper', (): void => {
|
||||
identifier: { path: `${base}test.txt` },
|
||||
filePath: `${rootFilepath}test.txt`,
|
||||
contentType: 'text/plain',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a generated identifier for metadata files.', async(): Promise<void> => {
|
||||
await expect(mapper.mapFilePathToUrl(`${rootFilepath}test.meta`, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test` },
|
||||
filePath: `${rootFilepath}test.meta`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: true,
|
||||
});
|
||||
});
|
||||
|
||||
@ -140,6 +168,7 @@ describe('An ExtensionBasedMapper', (): void => {
|
||||
identifier: { path: `${base}test.txt` },
|
||||
filePath: `${rootFilepath}test.txt$.ttl`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
@ -148,6 +177,7 @@ describe('An ExtensionBasedMapper', (): void => {
|
||||
identifier: { path: `${base}test` },
|
||||
filePath: `${rootFilepath}test`,
|
||||
contentType: 'application/octet-stream',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -13,56 +13,61 @@ describe('An FixedContentTypeMapper', (): void => {
|
||||
|
||||
describe('mapUrlToFilePath', (): void => {
|
||||
it('throws 404 if the input path does not contain the base.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: 'invalid' })).rejects.toThrow(NotFoundHttpError);
|
||||
await expect(mapper.mapUrlToFilePath({ path: 'invalid' }, false)).rejects.toThrow(NotFoundHttpError);
|
||||
});
|
||||
|
||||
it('throws 404 if the relative path does not start with a slash.', async(): Promise<void> => {
|
||||
const result = mapper.mapUrlToFilePath({ path: `${trimTrailingSlashes(base)}test` });
|
||||
const result = mapper.mapUrlToFilePath({ path: `${trimTrailingSlashes(base)}test` }, false);
|
||||
await expect(result).rejects.toThrow(BadRequestHttpError);
|
||||
await expect(result).rejects.toThrow('URL needs a / after the base');
|
||||
});
|
||||
|
||||
it('throws 400 if the input path contains relative parts.', async(): Promise<void> => {
|
||||
const result = mapper.mapUrlToFilePath({ path: `${base}test/../test2` });
|
||||
const result = mapper.mapUrlToFilePath({ path: `${base}test/../test2` }, false);
|
||||
await expect(result).rejects.toThrow(BadRequestHttpError);
|
||||
await expect(result).rejects.toThrow('Disallowed /.. segment in URL');
|
||||
});
|
||||
|
||||
it('returns the corresponding file path for container identifiers.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}container/` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}container/` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}container/` },
|
||||
filePath: `${rootFilepath}container/`,
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('always returns the configured content-type.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test` },
|
||||
filePath: `${rootFilepath}test`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test.ttl` },
|
||||
filePath: `${rootFilepath}test.ttl`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test.txt` },
|
||||
filePath: `${rootFilepath}test.txt`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('generates a file path if supported content-type was provided.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` }, 'text/turtle')).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` }, false, 'text/turtle')).resolves.toEqual({
|
||||
identifier: { path: `${base}test.ttl` },
|
||||
filePath: `${rootFilepath}test.ttl`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('throws 400 if the given content-type is not supported.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` }, 'application/n-quads')).rejects
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` }, false, 'application/n-quads')).rejects
|
||||
.toThrow(
|
||||
new BadRequestHttpError(`Unsupported content type application/n-quads, only text/turtle is allowed`),
|
||||
);
|
||||
@ -78,6 +83,7 @@ describe('An FixedContentTypeMapper', (): void => {
|
||||
await expect(mapper.mapFilePathToUrl(`${rootFilepath}container/`, true)).resolves.toEqual({
|
||||
identifier: { path: `${base}container/` },
|
||||
filePath: `${rootFilepath}container/`,
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
@ -86,16 +92,19 @@ describe('An FixedContentTypeMapper', (): void => {
|
||||
identifier: { path: `${base}test` },
|
||||
filePath: `${rootFilepath}test`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
await expect(mapper.mapFilePathToUrl(`${rootFilepath}test.ttl`, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test.ttl` },
|
||||
filePath: `${rootFilepath}test.ttl`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
await expect(mapper.mapFilePathToUrl(`${rootFilepath}test.txt`, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test.txt` },
|
||||
filePath: `${rootFilepath}test.txt`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -107,40 +116,45 @@ describe('An FixedContentTypeMapper', (): void => {
|
||||
|
||||
describe('mapUrlToFilePath', (): void => {
|
||||
it('returns the corresponding file path for container identifiers.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}container/` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}container/` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}container/` },
|
||||
filePath: `${rootFilepath}container/`,
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('always returns the configured content-type.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test` },
|
||||
filePath: `${rootFilepath}test.ttl`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test.ttl` },
|
||||
filePath: `${rootFilepath}test.ttl.ttl`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test.txt` },
|
||||
filePath: `${rootFilepath}test.txt.ttl`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('generates a file path if supported content-type was provided.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` }, 'text/turtle')).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` }, false, 'text/turtle')).resolves.toEqual({
|
||||
identifier: { path: `${base}test.ttl` },
|
||||
filePath: `${rootFilepath}test.ttl.ttl`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('throws 400 if the given content-type is not supported.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` }, 'application/n-quads')).rejects
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` }, false, 'application/n-quads')).rejects
|
||||
.toThrow(
|
||||
new BadRequestHttpError(`Unsupported content type application/n-quads, only text/turtle is allowed`),
|
||||
);
|
||||
@ -152,6 +166,7 @@ describe('An FixedContentTypeMapper', (): void => {
|
||||
await expect(mapper.mapFilePathToUrl(`${rootFilepath}container/`, true)).resolves.toEqual({
|
||||
identifier: { path: `${base}container/` },
|
||||
filePath: `${rootFilepath}container/`,
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
@ -160,6 +175,7 @@ describe('An FixedContentTypeMapper', (): void => {
|
||||
identifier: { path: `${base}test` },
|
||||
filePath: `${rootFilepath}test.ttl`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
@ -176,42 +192,46 @@ describe('An FixedContentTypeMapper', (): void => {
|
||||
|
||||
describe('mapUrlToFilePath', (): void => {
|
||||
it('returns the corresponding file path for container identifiers.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}container/` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}container/` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}container/` },
|
||||
filePath: `${rootFilepath}container/`,
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('always returns the configured content-type.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test.ttl` },
|
||||
filePath: `${rootFilepath}test`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt.ttl` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.txt.ttl` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test.txt.ttl` },
|
||||
filePath: `${rootFilepath}test.txt`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('generates a file path if supported content-type was provided.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` }, 'text/turtle')).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` }, false, 'text/turtle')).resolves.toEqual({
|
||||
identifier: { path: `${base}test.ttl` },
|
||||
filePath: `${rootFilepath}test`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('throws 404 if the url does not end with the suffix.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.nq` }, 'text/turtle')).rejects
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.nq` }, false, 'text/turtle')).rejects
|
||||
.toThrow(NotFoundHttpError);
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test` }, 'text/turtle')).rejects
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test` }, false, 'text/turtle')).rejects
|
||||
.toThrow(NotFoundHttpError);
|
||||
});
|
||||
|
||||
it('throws 400 if the given content-type is not supported.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` }, 'application/n-quads')).rejects
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` }, false, 'application/n-quads')).rejects
|
||||
.toThrow(
|
||||
new BadRequestHttpError(`Unsupported content type application/n-quads, only text/turtle is allowed`),
|
||||
);
|
||||
@ -223,6 +243,7 @@ describe('An FixedContentTypeMapper', (): void => {
|
||||
await expect(mapper.mapFilePathToUrl(`${rootFilepath}container/`, true)).resolves.toEqual({
|
||||
identifier: { path: `${base}container/` },
|
||||
filePath: `${rootFilepath}container/`,
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
@ -231,16 +252,19 @@ describe('An FixedContentTypeMapper', (): void => {
|
||||
identifier: { path: `${base}test.ttl` },
|
||||
filePath: `${rootFilepath}test`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
await expect(mapper.mapFilePathToUrl(`${rootFilepath}test.ttl`, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test.ttl.ttl` },
|
||||
filePath: `${rootFilepath}test.ttl`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
await expect(mapper.mapFilePathToUrl(`${rootFilepath}test.txt`, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test.txt.ttl` },
|
||||
filePath: `${rootFilepath}test.txt`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -252,24 +276,26 @@ describe('An FixedContentTypeMapper', (): void => {
|
||||
|
||||
describe('mapUrlToFilePath', (): void => {
|
||||
it('returns the corresponding file path for container identifiers.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}container/` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}container/` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}container/` },
|
||||
filePath: `${rootFilepath}container/`,
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('always returns the configured content-type.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` })).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.ttl` }, false)).resolves.toEqual({
|
||||
identifier: { path: `${base}test.ttl` },
|
||||
filePath: `${rootFilepath}test.nq`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('throws 404 if the url does not end with the suffix.', async(): Promise<void> => {
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.nq` }, 'text/turtle')).rejects
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test.nq` }, false, 'text/turtle')).rejects
|
||||
.toThrow(NotFoundHttpError);
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test` }, 'text/turtle')).rejects
|
||||
await expect(mapper.mapUrlToFilePath({ path: `${base}test` }, false, 'text/turtle')).rejects
|
||||
.toThrow(NotFoundHttpError);
|
||||
});
|
||||
});
|
||||
@ -279,6 +305,7 @@ describe('An FixedContentTypeMapper', (): void => {
|
||||
await expect(mapper.mapFilePathToUrl(`${rootFilepath}container/`, true)).resolves.toEqual({
|
||||
identifier: { path: `${base}container/` },
|
||||
filePath: `${rootFilepath}container/`,
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
@ -287,6 +314,7 @@ describe('An FixedContentTypeMapper', (): void => {
|
||||
identifier: { path: `${base}test.ttl` },
|
||||
filePath: `${rootFilepath}test.nq`,
|
||||
contentType: 'text/turtle',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -15,39 +15,42 @@ describe('A SubdomainExtensionBasedMapper', (): void => {
|
||||
describe('mapUrlToFilePath', (): void => {
|
||||
it('converts file paths to identifiers with a subdomain.', async(): Promise<void> => {
|
||||
const identifier = { path: `${getSubdomain('alice')}test.txt` };
|
||||
await expect(mapper.mapUrlToFilePath(identifier, 'text/plain')).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath(identifier, false, 'text/plain')).resolves.toEqual({
|
||||
identifier,
|
||||
filePath: `${rootFilepath}alice/test.txt`,
|
||||
contentType: 'text/plain',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('adds the default subdomain to the file path for root identifiers.', async(): Promise<void> => {
|
||||
const identifier = { path: `${base}test.txt` };
|
||||
await expect(mapper.mapUrlToFilePath(identifier, 'text/plain')).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath(identifier, false, 'text/plain')).resolves.toEqual({
|
||||
identifier,
|
||||
filePath: `${rootFilepath}www/test.txt`,
|
||||
contentType: 'text/plain',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('decodes punycode when generating a file path.', async(): Promise<void> => {
|
||||
const identifier = { path: `${getSubdomain('xn--c1yn36f')}t%20est.txt` };
|
||||
await expect(mapper.mapUrlToFilePath(identifier, 'text/plain')).resolves.toEqual({
|
||||
await expect(mapper.mapUrlToFilePath(identifier, false, 'text/plain')).resolves.toEqual({
|
||||
identifier,
|
||||
filePath: `${rootFilepath}點看/t est.txt`,
|
||||
contentType: 'text/plain',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('errors if the path is invalid.', async(): Promise<void> => {
|
||||
const identifier = { path: `veryinvalidpath` };
|
||||
await expect(mapper.mapUrlToFilePath(identifier, 'text/plain')).rejects.toThrow(NotFoundHttpError);
|
||||
await expect(mapper.mapUrlToFilePath(identifier, false, 'text/plain')).rejects.toThrow(NotFoundHttpError);
|
||||
});
|
||||
|
||||
it('errors if the subdomain matches the default one.', async(): Promise<void> => {
|
||||
const identifier = { path: `${getSubdomain('www')}test.txt` };
|
||||
await expect(mapper.mapUrlToFilePath(identifier, 'text/plain')).rejects.toThrow(ForbiddenHttpError);
|
||||
await expect(mapper.mapUrlToFilePath(identifier, false, 'text/plain')).rejects.toThrow(ForbiddenHttpError);
|
||||
});
|
||||
});
|
||||
|
||||
@ -57,6 +60,7 @@ describe('A SubdomainExtensionBasedMapper', (): void => {
|
||||
identifier: { path: `${getSubdomain('alice')}test.txt` },
|
||||
filePath: `${rootFilepath}alice/test.txt`,
|
||||
contentType: 'text/plain',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
@ -64,6 +68,7 @@ describe('A SubdomainExtensionBasedMapper', (): void => {
|
||||
await expect(mapper.mapFilePathToUrl(`${rootFilepath}alice/test.txt`, true)).resolves.toEqual({
|
||||
identifier: { path: `${getSubdomain('alice')}test.txt/` },
|
||||
filePath: `${rootFilepath}alice/test.txt`,
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
@ -72,6 +77,7 @@ describe('A SubdomainExtensionBasedMapper', (): void => {
|
||||
identifier: { path: `${base}test.txt` },
|
||||
filePath: `${rootFilepath}www/test.txt`,
|
||||
contentType: 'text/plain',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
@ -80,6 +86,7 @@ describe('A SubdomainExtensionBasedMapper', (): void => {
|
||||
identifier: { path: `${getSubdomain('xn--c1yn36f')}t%20est.txt` },
|
||||
filePath: `${rootFilepath}點看/t est.txt`,
|
||||
contentType: 'text/plain',
|
||||
isMetadata: false,
|
||||
});
|
||||
});
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user