feat: Only accept NamedNodes as predicates for metadata

* refactor: move toCachedNamedNode (private)

* chore: only NamedNodes predicates in removes

* feat: enforce NamedNode predicates in most cases

* feat: getAll only accepts NamedNodes

* feat: toCachedNamedNode only accepts string arg

* tests: use NamedNodes for getAll calls

* test: remove unnecessary string check for coverage

* tests: fix NamedNodes in new tests after rebase

* feat: metadatawriters store NamedNodes

* refactor: toCachedNamedNode as utility function

* fix: double write of linkRelMap

* test: use the CONTENT_TYPE constant
This commit is contained in:
Jasper Vaneessen 2022-04-15 09:53:39 +02:00 committed by GitHub
parent db906ae872
commit 668d0a331f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 172 additions and 183 deletions

View File

@ -1,4 +1,5 @@
import { DataFactory } from 'n3';
import type { NamedNode } from 'rdf-js';
import { SOLID_META } from '../../util/Vocabularies';
import type { RepresentationMetadata } from '../representation/RepresentationMetadata';
import type { AuxiliaryIdentifierStrategy } from './AuxiliaryIdentifierStrategy';
@ -11,12 +12,12 @@ import { MetadataGenerator } from './MetadataGenerator';
* In case the input is metadata of an auxiliary resource no metadata will be added
*/
export class LinkMetadataGenerator extends MetadataGenerator {
private readonly link: string;
private readonly link: NamedNode;
private readonly identifierStrategy: AuxiliaryIdentifierStrategy;
public constructor(link: string, identifierStrategy: AuxiliaryIdentifierStrategy) {
super();
this.link = link;
this.link = DataFactory.namedNode(link);
this.identifierStrategy = identifierStrategy;
}

View File

@ -19,7 +19,7 @@ export class SlugParser extends MetadataParser {
throw new BadRequestHttpError('Request has multiple Slug headers');
}
this.logger.debug(`Request Slug is '${slug}'.`);
input.metadata.set(SOLID_HTTP.slug, slug);
input.metadata.set(SOLID_HTTP.terms.slug, slug);
}
}
}

View File

@ -1,3 +1,5 @@
import type { NamedNode } from 'n3';
import { DataFactory } from 'n3';
import { getLoggerFor } from '../../../logging/LogUtil';
import type { HttpResponse } from '../../../server/HttpResponse';
import { addHeader } from '../../../util/HeaderUtil';
@ -9,19 +11,23 @@ import { MetadataWriter } from './MetadataWriter';
* The values of the objects will be put in a Link header with the corresponding "rel" value.
*/
export class LinkRelMetadataWriter extends MetadataWriter {
private readonly linkRelMap: Record<string, string>;
private readonly linkRelMap: Map<NamedNode, string>;
protected readonly logger = getLoggerFor(this);
public constructor(linkRelMap: Record<string, string>) {
super();
this.linkRelMap = linkRelMap;
this.linkRelMap = new Map<NamedNode, string>();
for (const [ key, value ] of Object.entries(linkRelMap)) {
this.linkRelMap.set(DataFactory.namedNode(key), value);
}
}
public async handle(input: { response: HttpResponse; metadata: RepresentationMetadata }): Promise<void> {
const keys = Object.keys(this.linkRelMap);
this.logger.debug(`Available link relations: ${keys.length}`);
for (const key of keys) {
const values = input.metadata.getAll(key).map((term): string => `<${term.value}>; rel="${this.linkRelMap[key]}"`);
this.logger.debug(`Available link relations: ${this.linkRelMap.size}`);
for (const [ predicate, relValue ] of this.linkRelMap) {
const values = input.metadata.getAll(predicate)
.map((term): string => `<${term.value}>; rel="${relValue}"`);
if (values.length > 0) {
this.logger.debug(`Adding Link header ${values}`);
addHeader(input.response, 'Link', values);

View File

@ -1,3 +1,5 @@
import type { NamedNode } from 'n3';
import { DataFactory } from 'n3';
import type { HttpResponse } from '../../../server/HttpResponse';
import { addHeader } from '../../../util/HeaderUtil';
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
@ -8,11 +10,15 @@ import { MetadataWriter } from './MetadataWriter';
* The header value(s) will be the same as the corresponding object value(s).
*/
export class MappedMetadataWriter extends MetadataWriter {
private readonly headerMap: [string, string][];
private readonly headerMap: Map<NamedNode, string>;
public constructor(headerMap: Record<string, string>) {
super();
this.headerMap = Object.entries(headerMap);
this.headerMap = new Map<NamedNode, string>();
for (const [ key, value ] of Object.entries(headerMap)) {
this.headerMap.set(DataFactory.namedNode(key), value);
}
}
public async handle(input: { response: HttpResponse; metadata: RepresentationMetadata }): Promise<void> {

View File

@ -4,7 +4,7 @@ import { getLoggerFor } from '../../logging/LogUtil';
import { InternalServerError } from '../../util/errors/InternalServerError';
import type { ContentType } from '../../util/HeaderUtil';
import { parseContentType } from '../../util/HeaderUtil';
import { toNamedTerm, toObjectTerm, toCachedNamedNode, isTerm, toLiteral } from '../../util/TermUtil';
import { toNamedTerm, toObjectTerm, isTerm, toLiteral } from '../../util/TermUtil';
import { CONTENT_TYPE_TERM, CONTENT_LENGTH_TERM, XSD, SOLID_META, RDFS } from '../../util/Vocabularies';
import type { ResourceIdentifier } from './ResourceIdentifier';
import { isResourceIdentifier } from './ResourceIdentifier';
@ -21,6 +21,22 @@ export function isRepresentationMetadata(object: any): object is RepresentationM
return typeof object?.setMetadata === 'function';
}
// Caches named node conversions
const cachedNamedNodes: Record<string, NamedNode> = {};
/**
* Converts the incoming name (URI or shorthand) to a named node.
* The generated terms get cached to reduce the number of created nodes,
* so only use this for internal constants!
* @param name - Predicate to potentially transform.
*/
function toCachedNamedNode(name: string): NamedNode {
if (!(name in cachedNamedNodes)) {
cachedNamedNodes[name] = DataFactory.namedNode(name);
}
return cachedNamedNodes[name];
}
/**
* Stores the metadata triples and provides methods for easy access.
* Most functions return the metadata object to allow for chaining.
@ -116,7 +132,7 @@ export class RepresentationMetadata {
*/
public quads(
subject: NamedNode | BlankNode | string | null = null,
predicate: NamedNode | string | null = null,
predicate: NamedNode | null = null,
object: NamedNode | BlankNode | Literal | string | null = null,
graph: MetadataGraph | null = null,
): Quad[] {
@ -167,12 +183,12 @@ export class RepresentationMetadata {
*/
public addQuad(
subject: NamedNode | BlankNode | string,
predicate: NamedNode | string,
predicate: NamedNode,
object: NamedNode | BlankNode | Literal | string,
graph?: MetadataGraph,
): this {
this.store.addQuad(toNamedTerm(subject),
toCachedNamedNode(predicate),
predicate,
toObjectTerm(object, true),
graph ? toNamedTerm(graph) : undefined);
return this;
@ -194,12 +210,12 @@ export class RepresentationMetadata {
*/
public removeQuad(
subject: NamedNode | BlankNode | string,
predicate: NamedNode | string,
predicate: NamedNode,
object: NamedNode | BlankNode | Literal | string,
graph?: MetadataGraph,
): this {
const quads = this.quads(toNamedTerm(subject),
toCachedNamedNode(predicate),
predicate,
toObjectTerm(object, true),
graph ? toNamedTerm(graph) : undefined);
return this.removeQuads(quads);
@ -219,7 +235,7 @@ export class RepresentationMetadata {
* @param object - Value(s) to add.
* @param graph - Optional graph of where to add the values to.
*/
public add(predicate: NamedNode | string, object: MetadataValue, graph?: MetadataGraph): this {
public add(predicate: NamedNode, object: MetadataValue, graph?: MetadataGraph): this {
return this.forQuads(predicate, object, (pred, obj): any => this.addQuad(this.id, pred, obj, graph));
}
@ -229,7 +245,7 @@ export class RepresentationMetadata {
* @param object - Value(s) to remove.
* @param graph - Optional graph of where to remove the values from.
*/
public remove(predicate: NamedNode | string, object: MetadataValue, graph?: MetadataGraph): this {
public remove(predicate: NamedNode, object: MetadataValue, graph?: MetadataGraph): this {
return this.forQuads(predicate, object, (pred, obj): any => this.removeQuad(this.id, pred, obj, graph));
}
@ -237,12 +253,11 @@ export class RepresentationMetadata {
* Helper function to simplify add/remove
* Runs the given function on all predicate/object pairs, but only converts the predicate to a named node once.
*/
private forQuads(predicate: NamedNode | string, object: MetadataValue,
private forQuads(predicate: NamedNode, object: MetadataValue,
forFn: (pred: NamedNode, obj: NamedNode | Literal) => void): this {
const predicateNode = toCachedNamedNode(predicate);
const objects = Array.isArray(object) ? object : [ object ];
for (const obj of objects) {
forFn(predicateNode, toObjectTerm(obj, true));
forFn(predicate, toObjectTerm(obj, true));
}
return this;
}
@ -252,8 +267,8 @@ export class RepresentationMetadata {
* @param predicate - Predicate to remove.
* @param graph - Optional graph where to remove from.
*/
public removeAll(predicate: NamedNode | string, graph?: MetadataGraph): this {
this.removeQuads(this.store.getQuads(this.id, toCachedNamedNode(predicate), null, graph ?? null));
public removeAll(predicate: NamedNode, graph?: MetadataGraph): this {
this.removeQuads(this.store.getQuads(this.id, predicate, null, graph ?? null));
return this;
}
@ -278,8 +293,8 @@ export class RepresentationMetadata {
*
* @returns An array with all matches.
*/
public getAll(predicate: NamedNode | string, graph?: MetadataGraph): Term[] {
return this.store.getQuads(this.id, toCachedNamedNode(predicate), null, graph ?? null)
public getAll(predicate: NamedNode, graph?: MetadataGraph): Term[] {
return this.store.getQuads(this.id, predicate, null, graph ?? null)
.map((quad): Term => quad.object);
}
@ -292,15 +307,15 @@ export class RepresentationMetadata {
*
* @returns The corresponding value. Undefined if there is no match
*/
public get(predicate: NamedNode | string, graph?: MetadataGraph): Term | undefined {
public get(predicate: NamedNode, graph?: MetadataGraph): Term | undefined {
const terms = this.getAll(predicate, graph);
if (terms.length === 0) {
return;
}
if (terms.length > 1) {
this.logger.error(`Multiple results for ${typeof predicate === 'string' ? predicate : predicate.value}`);
this.logger.error(`Multiple results for ${predicate.value}`);
throw new InternalServerError(
`Multiple results for ${typeof predicate === 'string' ? predicate : predicate.value}`,
`Multiple results for ${predicate.value}`,
);
}
return terms[0];
@ -313,7 +328,7 @@ export class RepresentationMetadata {
* @param object - Value(s) to set.
* @param graph - Optional graph where the triple should be stored.
*/
public set(predicate: NamedNode | string, object?: MetadataValue, graph?: MetadataGraph): this {
public set(predicate: NamedNode, object?: MetadataValue, graph?: MetadataGraph): this {
this.removeAll(predicate, graph);
if (object) {
this.add(predicate, object, graph);

View File

@ -29,7 +29,6 @@ import {
import { parseQuads } from '../util/QuadUtil';
import { addResourceMetadata, updateModifiedDate } from '../util/ResourceUtil';
import {
CONTENT_TYPE,
DC,
SOLID_HTTP,
LDP,
@ -39,6 +38,7 @@ import {
XSD,
SOLID_META,
PREFERRED_PREFIX_TERM,
CONTENT_TYPE_TERM,
} from '../util/Vocabularies';
import type { DataAccessor } from './accessors/DataAccessor';
import type { Conditions } from './Conditions';
@ -435,7 +435,7 @@ export class DataAccessorBasedStore implements ResourceStore {
}
// Input content type doesn't matter anymore
representation.metadata.removeAll(CONTENT_TYPE);
representation.metadata.removeAll(CONTENT_TYPE_TERM);
// Container data is stored in the metadata
representation.metadata.addQuads(quads);
@ -516,8 +516,8 @@ export class DataAccessorBasedStore implements ResourceStore {
Promise<ResourceIdentifier> {
// Get all values needed for naming the resource
const isContainer = this.isContainerType(metadata);
const slug = metadata.get(SOLID_HTTP.slug)?.value;
metadata.removeAll(SOLID_HTTP.slug);
const slug = metadata.get(SOLID_HTTP.terms.slug)?.value;
metadata.removeAll(SOLID_HTTP.terms.slug);
let newID: ResourceIdentifier = this.createURI(container, isContainer, slug);
@ -544,7 +544,7 @@ export class DataAccessorBasedStore implements ResourceStore {
* @param metadata - Metadata of the (new) resource.
*/
protected isContainerType(metadata: RepresentationMetadata): boolean {
return this.hasContainerType(metadata.getAll(RDF.type));
return this.hasContainerType(metadata.getAll(RDF.terms.type));
}
/**
@ -558,7 +558,7 @@ export class DataAccessorBasedStore implements ResourceStore {
* Verifies if this is the metadata of a root storage container.
*/
protected isRootStorage(metadata: RepresentationMetadata): boolean {
return metadata.getAll(RDF.type).some((term): boolean => term.value === PIM.Storage);
return metadata.getAll(RDF.terms.type).some((term): boolean => term.value === PIM.Storage);
}
/**

View File

@ -16,7 +16,7 @@ import { joinFilePath, isContainerIdentifier } from '../../util/PathUtil';
import { parseQuads, serializeQuads } from '../../util/QuadUtil';
import { addResourceMetadata, updateModifiedDate } from '../../util/ResourceUtil';
import { toLiteral, toNamedTerm } from '../../util/TermUtil';
import { CONTENT_TYPE, DC, IANA, LDP, POSIX, RDF, SOLID_META, XSD } from '../../util/Vocabularies';
import { CONTENT_TYPE_TERM, DC, IANA, LDP, POSIX, RDF, SOLID_META, XSD } from '../../util/Vocabularies';
import type { FileIdentifierMapper, ResourceLink } from '../mapping/FileIdentifierMapper';
import type { DataAccessor } from './DataAccessor';
@ -174,7 +174,7 @@ export class FileDataAccessor implements DataAccessor {
private async getFileMetadata(link: ResourceLink, stats: Stats):
Promise<RepresentationMetadata> {
return (await this.getBaseMetadata(link, stats, false))
.set(CONTENT_TYPE, link.contentType);
.set(CONTENT_TYPE_TERM, link.contentType);
}
/**
@ -202,7 +202,7 @@ export class FileDataAccessor implements DataAccessor {
metadata.remove(RDF.terms.type, LDP.terms.Container);
metadata.remove(RDF.terms.type, LDP.terms.BasicContainer);
metadata.removeAll(DC.terms.modified);
metadata.removeAll(CONTENT_TYPE);
metadata.removeAll(CONTENT_TYPE_TERM);
const quads = metadata.quads();
const metadataLink = await this.resourceMapper.mapUrlToFilePath(link.identifier, true);
let wroteMetadata: boolean;

View File

@ -27,7 +27,7 @@ import { guardStream } from '../../util/GuardedStream';
import type { Guarded } from '../../util/GuardedStream';
import type { IdentifierStrategy } from '../../util/identifiers/IdentifierStrategy';
import { isContainerIdentifier } from '../../util/PathUtil';
import { CONTENT_TYPE, LDP } from '../../util/Vocabularies';
import { LDP, CONTENT_TYPE_TERM } from '../../util/Vocabularies';
import type { DataAccessor } from './DataAccessor';
const { defaultGraph, namedNode, quad, variable } = DataFactory;
@ -132,7 +132,7 @@ export class SparqlDataAccessor implements DataAccessor {
}
// Not relevant since all content is triples
metadata.removeAll(CONTENT_TYPE);
metadata.removeAll(CONTENT_TYPE_TERM);
return this.sendSparqlUpdate(this.sparqlInsert(name, metadata, parent, triples));
}

View File

@ -57,7 +57,7 @@ export class PodQuotaStrategy extends QuotaStrategy {
throw error;
}
const hasPimStorageMetadata = metadata!.getAll(RDF.type)
const hasPimStorageMetadata = metadata!.getAll(RDF.terms.type)
.some((term): boolean => term.value === PIM.Storage);
return hasPimStorageMetadata ? identifier : this.searchPimStorage(parent);

View File

@ -1,35 +1,8 @@
import { DataFactory } from 'n3';
import type { NamedNode, Literal, Term } from 'rdf-js';
import { CONTENT_TYPE_TERM } from './Vocabularies';
const { namedNode, literal } = DataFactory;
// Shorthands for commonly used predicates
const shorthands: Record<string, NamedNode> = {
contentType: CONTENT_TYPE_TERM,
};
// Caches named node conversions
const cachedNamedNodes: Record<string, NamedNode> = {
...shorthands,
};
/**
* Converts the incoming name (URI or shorthand) to a named node.
* The generated terms get cached to reduce the number of created nodes,
* so only use this for internal constants!
* @param name - Predicate to potentially transform.
*/
export function toCachedNamedNode(name: NamedNode | string): NamedNode {
if (typeof name !== 'string') {
return name;
}
if (!(name in cachedNamedNodes)) {
cachedNamedNodes[name] = namedNode(name);
}
return cachedNamedNodes[name];
}
/**
* @param input - Checks if this is a {@link Term}.
*/

View File

@ -45,7 +45,7 @@ describe('A LockingResourceStore', (): void => {
// Initialize store
const metadata = new RepresentationMetadata({ path: base }, TEXT_TURTLE);
metadata.add(RDF.type, PIM.terms.Storage);
metadata.add(RDF.terms.type, PIM.terms.Storage);
await source.setRepresentation({ path: base }, new BasicRepresentation([], metadata));
locker = new EqualReadWriteLocker(new SingleThreadedResourceLocker());

View File

@ -1,3 +1,4 @@
import { DataFactory } from 'n3';
import type { AuxiliaryIdentifierStrategy } from '../../../../src/http/auxiliary/AuxiliaryIdentifierStrategy';
import { LinkMetadataGenerator } from '../../../../src/http/auxiliary/LinkMetadataGenerator';
import { RepresentationMetadata } from '../../../../src/http/representation/RepresentationMetadata';
@ -35,7 +36,7 @@ describe('A LinkMetadataGenerator', (): void => {
const metadata = new RepresentationMetadata(subjectId);
await expect(generator.handle(metadata)).resolves.toBeUndefined();
expect(metadata.quads()).toHaveLength(1);
expect(metadata.get(link)?.value).toBe(auxiliaryId.path);
expect(metadata.getAll(link, SOLID_META.terms.ResponseMetadata)).toHaveLength(1);
expect(metadata.get(DataFactory.namedNode(link))?.value).toBe(auxiliaryId.path);
expect(metadata.getAll(DataFactory.namedNode(link), SOLID_META.terms.ResponseMetadata)).toHaveLength(1);
});
});

View File

@ -22,14 +22,14 @@ describe('A LinkParser', (): void => {
request.headers.link = '<http://test.com/type>;rel="type"';
await expect(parser.handle({ request, metadata })).resolves.toBeUndefined();
expect(metadata.quads()).toHaveLength(1);
expect(metadata.get(RDF.type)?.value).toBe('http://test.com/type');
expect(metadata.get(RDF.terms.type)?.value).toBe('http://test.com/type');
});
it('supports multiple link headers.', async(): Promise<void> => {
request.headers.link = [ '<http://test.com/typeA>;rel="type"', '<http://test.com/typeB>;rel=type' ];
await expect(parser.handle({ request, metadata })).resolves.toBeUndefined();
expect(metadata.quads()).toHaveLength(2);
expect(metadata.getAll(RDF.type).map((term): any => term.value))
expect(metadata.getAll(RDF.terms.type).map((term): any => term.value))
.toEqual([ 'http://test.com/typeA', 'http://test.com/typeB' ]);
});
@ -37,7 +37,7 @@ describe('A LinkParser', (): void => {
request.headers.link = '<http://test.com/typeA>;rel="type" , <http://test.com/typeB>;rel=type';
await expect(parser.handle({ request, metadata })).resolves.toBeUndefined();
expect(metadata.quads()).toHaveLength(2);
expect(metadata.getAll(RDF.type).map((term): any => term.value))
expect(metadata.getAll(RDF.terms.type).map((term): any => term.value))
.toEqual([ 'http://test.com/typeA', 'http://test.com/typeB' ]);
});

View File

@ -30,6 +30,6 @@ describe('A SlugParser', (): void => {
request.headers.slug = 'slugA';
await expect(parser.handle({ request, metadata })).resolves.toBeUndefined();
expect(metadata.quads()).toHaveLength(1);
expect(metadata.get(SOLID_HTTP.slug)?.value).toBe('slugA');
expect(metadata.get(SOLID_HTTP.terms.slug)?.value).toBe('slugA');
});
});

View File

@ -42,7 +42,7 @@ describe('A PatchOperationHandler', (): void => {
expect(store.modifyResource).toHaveBeenCalledTimes(1);
expect(store.modifyResource).toHaveBeenLastCalledWith(operation.target, body, conditions);
expect(result.statusCode).toBe(201);
expect(result.metadata?.get(SOLID_HTTP.location)?.value).toBe(operation.target.path);
expect(result.metadata?.get(SOLID_HTTP.terms.location)?.value).toBe(operation.target.path);
expect(result.data).toBeUndefined();
});

View File

@ -41,7 +41,7 @@ describe('A PostOperationHandler', (): void => {
const result = await handler.handle({ operation });
expect(result.statusCode).toBe(201);
expect(result.metadata).toBeInstanceOf(RepresentationMetadata);
expect(result.metadata?.get(SOLID_HTTP.location)?.value).toBe('newPath');
expect(result.metadata?.get(SOLID_HTTP.terms.location)?.value).toBe('newPath');
expect(result.data).toBeUndefined();
expect(store.addResource).toHaveBeenCalledTimes(1);
expect(store.addResource).toHaveBeenLastCalledWith(operation.target, body, conditions);

View File

@ -41,7 +41,7 @@ describe('A PutOperationHandler', (): void => {
expect(store.setRepresentation).toHaveBeenCalledTimes(1);
expect(store.setRepresentation).toHaveBeenLastCalledWith(operation.target, body, conditions);
expect(result.statusCode).toBe(201);
expect(result.metadata?.get(SOLID_HTTP.location)?.value).toBe(operation.target.path);
expect(result.metadata?.get(SOLID_HTTP.terms.location)?.value).toBe(operation.target.path);
expect(result.data).toBeUndefined();
});

View File

@ -3,7 +3,7 @@ import type { BlankNode } from 'n3';
import { DataFactory } from 'n3';
import type { NamedNode, Quad } from 'rdf-js';
import { RepresentationMetadata } from '../../../../src/http/representation/RepresentationMetadata';
import { CONTENT_TYPE, SOLID_META, RDFS } from '../../../../src/util/Vocabularies';
import { CONTENT_TYPE_TERM, SOLID_META, RDFS } from '../../../../src/util/Vocabularies';
const { defaultGraph, literal, namedNode, quad } = DataFactory;
// Helper functions to filter quads
@ -82,14 +82,14 @@ describe('A RepresentationMetadata', (): void => {
it('takes overrides for specific predicates.', async(): Promise<void> => {
metadata = new RepresentationMetadata({ predVal: 'objVal' });
expect(metadata.get('predVal')).toEqualRdfTerm(literal('objVal'));
expect(metadata.get(namedNode('predVal'))).toEqualRdfTerm(literal('objVal'));
metadata = new RepresentationMetadata({ predVal: literal('objVal') });
expect(metadata.get('predVal')).toEqualRdfTerm(literal('objVal'));
expect(metadata.get(namedNode('predVal'))).toEqualRdfTerm(literal('objVal'));
metadata = new RepresentationMetadata({ predVal: [ 'objVal1', literal('objVal2') ], predVal2: 'objVal3' });
expect(metadata.getAll('predVal')).toEqualRdfTermArray([ literal('objVal1'), literal('objVal2') ]);
expect(metadata.get('predVal2')).toEqualRdfTerm(literal('objVal3'));
expect(metadata.getAll(namedNode('predVal'))).toEqualRdfTermArray([ literal('objVal1'), literal('objVal2') ]);
expect(metadata.get(namedNode('predVal2'))).toEqualRdfTerm(literal('objVal3'));
});
it('can combine overrides with an identifier.', async(): Promise<void> => {
@ -153,7 +153,7 @@ describe('A RepresentationMetadata', (): void => {
// `setMetadata` should have the same result as the following
const expectedMetadata = new RepresentationMetadata(identifier).addQuads(inputQuads);
expectedMetadata.identifier = namedNode('otherId');
expectedMetadata.add('test:pred', 'objVal');
expectedMetadata.add(namedNode('test:pred'), 'objVal');
expect(metadata.identifier).toEqual(other.identifier);
expect(metadata.quads()).toBeRdfIsomorphic(expectedMetadata.quads());
@ -161,13 +161,13 @@ describe('A RepresentationMetadata', (): void => {
it('can add a quad.', async(): Promise<void> => {
const newQuad = quad(namedNode('random'), namedNode('new'), literal('triple'));
metadata.addQuad('random', 'new', 'triple');
metadata.addQuad('random', namedNode('new'), 'triple');
expect(metadata.quads()).toBeRdfIsomorphic([ ...inputQuads, newQuad ]);
});
it('can add a quad with a graph.', async(): Promise<void> => {
const newQuad = quad(namedNode('random'), namedNode('new'), literal('triple'), namedNode('graph'));
metadata.addQuad('random', 'new', 'triple', 'graph');
metadata.addQuad('random', namedNode('new'), 'triple', 'graph');
expect(metadata.quads()).toBeRdfIsomorphic([ ...inputQuads, newQuad ]);
});
@ -186,7 +186,7 @@ describe('A RepresentationMetadata', (): void => {
});
it('removes all matching triples if graph is undefined.', async(): Promise<void> => {
metadata.removeQuad(identifier, 'has', 'data');
metadata.removeQuad(identifier, namedNode('has'), 'data');
expect(metadata.quads()).toHaveLength(inputQuads.length - 2);
expect(metadata.quads()).toBeRdfIsomorphic(removeQuads(inputQuads, identifier.value, 'has', 'data'));
});
@ -277,7 +277,6 @@ describe('A RepresentationMetadata', (): void => {
it('errors if there are multiple values when getting a value.', async(): Promise<void> => {
expect((): any => metadata.get(namedNode('has'))).toThrow(Error);
expect((): any => metadata.get('has')).toThrow(Error);
});
it('can set the value of a predicate.', async(): Promise<void> => {
@ -293,15 +292,15 @@ describe('A RepresentationMetadata', (): void => {
it('has a shorthand for content-type.', async(): Promise<void> => {
expect(metadata.contentType).toBeUndefined();
metadata.contentType = 'a/b';
expect(metadata.get(CONTENT_TYPE)).toEqualRdfTerm(literal('a/b'));
expect(metadata.get(CONTENT_TYPE_TERM)).toEqualRdfTerm(literal('a/b'));
expect(metadata.contentType).toBe('a/b');
metadata.contentType = undefined;
expect(metadata.contentType).toBeUndefined();
});
it('errors if a shorthand has multiple values.', async(): Promise<void> => {
metadata.add(CONTENT_TYPE, 'a/b');
metadata.add(CONTENT_TYPE, 'c/d');
metadata.add(CONTENT_TYPE_TERM, 'a/b');
metadata.add(CONTENT_TYPE_TERM, 'c/d');
expect((): any => metadata.contentType).toThrow();
});

View File

@ -1,3 +1,4 @@
import { DataFactory } from 'n3';
import type { ResourceIdentifier } from '../../../../src/http/representation/ResourceIdentifier';
import { TemplatedResourcesGenerator } from '../../../../src/pods/generate/TemplatedResourcesGenerator';
import type {
@ -10,6 +11,8 @@ import { readableToString } from '../../../../src/util/StreamUtil';
import { HandlebarsTemplateEngine } from '../../../../src/util/templates/HandlebarsTemplateEngine';
import { mockFs } from '../../../util/Util';
const { namedNode } = DataFactory;
jest.mock('fs');
class DummyFactory implements FileIdentifierMapperFactory {
@ -115,7 +118,7 @@ describe('A TemplatedResourcesGenerator', (): void => {
const rootMetadata = result[0].representation.metadata;
expect(rootMetadata.identifier.value).toBe(location.path);
expect(rootMetadata.quads()).toHaveLength(2);
expect(rootMetadata.get('pre:has')?.value).toBe('metadata');
expect(rootMetadata.get(namedNode('pre:has'))?.value).toBe('metadata');
expect(rootMetadata.contentType).toBe('text/turtle');
// Container has no metadata triples besides content-type
@ -128,7 +131,7 @@ describe('A TemplatedResourcesGenerator', (): void => {
const docMetadata = result[2].representation.metadata;
expect(docMetadata.identifier.value).toBe(`${location.path}container/template`);
expect(docMetadata.quads()).toHaveLength(2);
expect(docMetadata.get('pre:has')?.value).toBe('metadata');
expect(docMetadata.get(namedNode('pre:has'))?.value).toBe('metadata');
expect(docMetadata.contentType).toBe('text/turtle');
});
});

View File

@ -39,7 +39,7 @@ describe('PodQuotaStrategy', (): void => {
async(identifier: ResourceIdentifier): Promise<RepresentationMetadata> => {
const res = new RepresentationMetadata();
if (identifier.path === `${base}nested/`) {
res.add(RDF.type, PIM.Storage);
res.add(RDF.terms.type, PIM.Storage);
}
return res;
},

View File

@ -181,7 +181,8 @@ describe('A DataAccessorBasedStore', (): void => {
expect(result).toMatchObject({ binary: true });
expect(await arrayifyStream(result.data)).toEqual([ resourceData ]);
expect(result.metadata.contentType).toBe('text/plain');
expect(result.metadata.get('AUXILIARY')?.value).toBe(auxiliaryStrategy.getAuxiliaryIdentifier(resourceID).path);
expect(result.metadata.get(namedNode('AUXILIARY'))?.value)
.toBe(auxiliaryStrategy.getAuxiliaryIdentifier(resourceID).path);
});
it('will return a data stream that matches the metadata for containers.', async(): Promise<void> => {
@ -196,7 +197,8 @@ describe('A DataAccessorBasedStore', (): void => {
expect(result).toMatchObject({ binary: false });
expect(await arrayifyStream(result.data)).toBeRdfIsomorphic(metaMirror.quads());
expect(result.metadata.contentType).toEqual(INTERNAL_QUADS);
expect(result.metadata.get('AUXILIARY')?.value).toBe(auxiliaryStrategy.getAuxiliaryIdentifier(resourceID).path);
expect(result.metadata.get(namedNode('AUXILIARY'))?.value)
.toBe(auxiliaryStrategy.getAuxiliaryIdentifier(resourceID).path);
});
it('will remove containment triples referencing auxiliary resources.', async(): Promise<void> => {
@ -255,13 +257,13 @@ describe('A DataAccessorBasedStore', (): void => {
it('errors when trying to create a container with non-RDF data.', async(): Promise<void> => {
const resourceID = { path: root };
representation.metadata.add(RDF.type, LDP.terms.Container);
representation.metadata.add(RDF.terms.type, LDP.terms.Container);
await expect(store.addResource(resourceID, representation)).rejects.toThrow(BadRequestHttpError);
});
it('can write resources.', async(): Promise<void> => {
const resourceID = { path: root };
representation.metadata.removeAll(RDF.type);
representation.metadata.removeAll(RDF.terms.type);
const result = await store.addResource(resourceID, representation);
expect(result).toEqual({
path: expect.stringMatching(new RegExp(`^${root}[^/]+$`, 'u')),
@ -272,7 +274,7 @@ describe('A DataAccessorBasedStore', (): void => {
it('can write containers.', async(): Promise<void> => {
const resourceID = { path: root };
representation.metadata.add(RDF.type, LDP.terms.Container);
representation.metadata.add(RDF.terms.type, LDP.terms.Container);
representation.metadata.contentType = 'text/turtle';
representation.data = guardedStreamFrom([ '<> a <http://test.com/coolContainer>.' ]);
const result = await store.addResource(resourceID, representation);
@ -291,8 +293,8 @@ describe('A DataAccessorBasedStore', (): void => {
it('creates a URI based on the incoming slug.', async(): Promise<void> => {
const resourceID = { path: root };
representation.metadata.removeAll(RDF.type);
representation.metadata.add(SOLID_HTTP.slug, 'newName');
representation.metadata.removeAll(RDF.terms.type);
representation.metadata.add(SOLID_HTTP.terms.slug, 'newName');
const result = await store.addResource(resourceID, representation);
expect(result).toEqual({
path: `${root}newName`,
@ -301,8 +303,8 @@ describe('A DataAccessorBasedStore', (): void => {
it('errors on a slug ending on / without Link rel:type Container header.', async(): Promise<void> => {
const resourceID = { path: root };
representation.metadata.removeAll(RDF.type);
representation.metadata.add(SOLID_HTTP.slug, 'noContainer/');
representation.metadata.removeAll(RDF.terms.type);
representation.metadata.add(SOLID_HTTP.terms.slug, 'noContainer/');
representation.data = guardedStreamFrom([ `` ]);
const result = store.addResource(resourceID, representation);
@ -314,9 +316,9 @@ describe('A DataAccessorBasedStore', (): void => {
it('creates a URI when the incoming slug does not end with /, ' +
'but has a Link rel:type Container header.', async(): Promise<void> => {
const resourceID = { path: root };
representation.metadata.removeAll(RDF.type);
representation.metadata.add(RDF.type, LDP.terms.Container);
representation.metadata.add(SOLID_HTTP.slug, 'newContainer');
representation.metadata.removeAll(RDF.terms.type);
representation.metadata.add(RDF.terms.type, LDP.terms.Container);
representation.metadata.add(SOLID_HTTP.terms.slug, 'newContainer');
representation.data = guardedStreamFrom([ `` ]);
const result = await store.addResource(resourceID, representation);
expect(result).toEqual({
@ -326,7 +328,7 @@ describe('A DataAccessorBasedStore', (): void => {
it('generates a new URI if adding the slug would create an existing URI.', async(): Promise<void> => {
const resourceID = { path: root };
representation.metadata.add(SOLID_HTTP.slug, 'newName');
representation.metadata.add(SOLID_HTTP.terms.slug, 'newName');
accessor.data[`${root}newName`] = representation;
const result = await store.addResource(resourceID, representation);
expect(result).not.toEqual({
@ -339,17 +341,17 @@ describe('A DataAccessorBasedStore', (): void => {
it('generates http://test.com/%26%26 when slug is &%26.', async(): Promise<void> => {
const resourceID = { path: root };
representation.metadata.removeAll(RDF.type);
representation.metadata.add(SOLID_HTTP.slug, '&%26');
representation.metadata.removeAll(RDF.terms.type);
representation.metadata.add(SOLID_HTTP.terms.slug, '&%26');
const result = await store.addResource(resourceID, representation);
expect(result).toEqual({ path: `${root}%26%26` });
});
it('errors if the slug contains a slash.', async(): Promise<void> => {
const resourceID = { path: root };
representation.metadata.removeAll(RDF.type);
representation.metadata.removeAll(RDF.terms.type);
representation.data = guardedStreamFrom([ `` ]);
representation.metadata.add(SOLID_HTTP.slug, 'sla/sh/es');
representation.metadata.add(SOLID_HTTP.terms.slug, 'sla/sh/es');
const result = store.addResource(resourceID, representation);
await expect(result).rejects.toThrow(BadRequestHttpError);
await expect(result).rejects.toThrow('Slugs should not contain slashes');
@ -357,8 +359,8 @@ describe('A DataAccessorBasedStore', (): void => {
it('errors if the slug would cause an auxiliary resource URI to be generated.', async(): Promise<void> => {
const resourceID = { path: root };
representation.metadata.removeAll(RDF.type);
representation.metadata.add(SOLID_HTTP.slug, 'test.dummy');
representation.metadata.removeAll(RDF.terms.type);
representation.metadata.add(SOLID_HTTP.terms.slug, 'test.dummy');
const result = store.addResource(resourceID, representation);
await expect(result).rejects.toThrow(ForbiddenHttpError);
await expect(result).rejects.toThrow('Slug bodies that would result in an auxiliary resource are forbidden');
@ -402,7 +404,7 @@ describe('A DataAccessorBasedStore', (): void => {
const mock = jest.spyOn(accessor, 'getMetadata');
const resourceID = { path: `${root}` };
representation.metadata.removeAll(RDF.type);
representation.metadata.removeAll(RDF.terms.type);
representation.metadata.contentType = 'text/turtle';
representation.data = guardedStreamFrom([ `<${root}> a <coolContainer>.` ]);
@ -416,7 +418,7 @@ describe('A DataAccessorBasedStore', (): void => {
it('will error if path does not end in slash and does not match its resource type.', async(): Promise<void> => {
const resourceID = { path: `${root}resource` };
representation.metadata.add(RDF.type, LDP.terms.Container);
representation.metadata.add(RDF.terms.type, LDP.terms.Container);
await expect(store.setRepresentation(resourceID, representation)).rejects.toThrow(
new BadRequestHttpError('Containers should have a `/` at the end of their path, resources should not.'),
);
@ -424,7 +426,7 @@ describe('A DataAccessorBasedStore', (): void => {
it('errors when trying to create a container with non-RDF data.', async(): Promise<void> => {
const resourceID = { path: `${root}container/` };
representation.metadata.add(RDF.type, LDP.terms.Container);
representation.metadata.add(RDF.terms.type, LDP.terms.Container);
await expect(store.setRepresentation(resourceID, representation)).rejects.toThrow(BadRequestHttpError);
});
@ -450,7 +452,7 @@ describe('A DataAccessorBasedStore', (): void => {
const resourceID = { path: `${root}container/` };
// Generate based on URI
representation.metadata.removeAll(RDF.type);
representation.metadata.removeAll(RDF.terms.type);
representation.metadata.contentType = 'text/turtle';
representation.data = guardedStreamFrom([ `<${root}resource/> a <coolContainer>.` ]);
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
@ -488,15 +490,15 @@ describe('A DataAccessorBasedStore', (): void => {
it('does not write generated metadata.', async(): Promise<void> => {
const resourceID = { path: `${root}resource` };
representation.metadata.add('notGen', 'value');
representation.metadata.add('gen', 'value', SOLID_META.terms.ResponseMetadata);
representation.metadata.add(namedNode('notGen'), 'value');
representation.metadata.add(namedNode('gen'), 'value', SOLID_META.terms.ResponseMetadata);
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
{ path: root },
{ path: `${root}resource` },
]);
await expect(arrayifyStream(accessor.data[resourceID.path].data)).resolves.toEqual([ resourceData ]);
expect(accessor.data[resourceID.path].metadata.get('notGen')?.value).toBe('value');
expect(accessor.data[resourceID.path].metadata.get('gen')).toBeUndefined();
expect(accessor.data[resourceID.path].metadata.get(namedNode('notGen'))?.value).toBe('value');
expect(accessor.data[resourceID.path].metadata.get(namedNode('gen'))).toBeUndefined();
});
it('can write resources even if root does not exist.', async(): Promise<void> => {
@ -514,7 +516,7 @@ describe('A DataAccessorBasedStore', (): void => {
const resourceID = { path: `${root}container/` };
// Generate based on URI
representation.metadata.removeAll(RDF.type);
representation.metadata.removeAll(RDF.terms.type);
representation.metadata.contentType = 'internal/quads';
representation.data = guardedStreamFrom(
[ quad(namedNode(`${root}resource/`), namedNode('a'), namedNode('coolContainer')) ],
@ -529,7 +531,7 @@ describe('A DataAccessorBasedStore', (): void => {
it('errors when trying to create a container with containment triples.', async(): Promise<void> => {
const resourceID = { path: `${root}container/` };
representation.metadata.add(RDF.type, LDP.terms.Container);
representation.metadata.add(RDF.terms.type, LDP.terms.Container);
representation.metadata.contentType = 'text/turtle';
representation.metadata.identifier = DataFactory.namedNode(`${root}resource/`);
representation.data = guardedStreamFrom(
@ -548,9 +550,9 @@ describe('A DataAccessorBasedStore', (): void => {
{ path: `${root}a/b/resource` },
]);
await expect(arrayifyStream(accessor.data[resourceID.path].data)).resolves.toEqual([ resourceData ]);
expect(accessor.data[`${root}a/`].metadata.getAll(RDF.type).map((type): string => type.value))
expect(accessor.data[`${root}a/`].metadata.getAll(RDF.terms.type).map((type): string => type.value))
.toContain(LDP.Container);
expect(accessor.data[`${root}a/b/`].metadata.getAll(RDF.type).map((type): string => type.value))
expect(accessor.data[`${root}a/b/`].metadata.getAll(RDF.terms.type).map((type): string => type.value))
.toContain(LDP.Container);
});
@ -568,7 +570,7 @@ describe('A DataAccessorBasedStore', (): void => {
const resourceID = { path: `${root}` };
// Generate based on URI
representation.metadata.removeAll(RDF.type);
representation.metadata.removeAll(RDF.terms.type);
representation.metadata.contentType = 'text/turtle';
representation.data = guardedStreamFrom([]);
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
@ -620,7 +622,7 @@ describe('A DataAccessorBasedStore', (): void => {
});
it('will error when deleting a root storage container.', async(): Promise<void> => {
representation.metadata.add(RDF.type, PIM.terms.Storage);
representation.metadata.add(RDF.terms.type, PIM.terms.Storage);
accessor.data[`${root}container/`] = representation;
const result = store.deleteResource({ path: `${root}container/` });
await expect(result).rejects.toThrow(MethodNotAllowedHttpError);
@ -629,7 +631,7 @@ describe('A DataAccessorBasedStore', (): void => {
it('will error when deleting an auxiliary of a root storage container if not allowed.', async(): Promise<void> => {
const storageMetadata = new RepresentationMetadata(representation.metadata);
storageMetadata.add(RDF.type, PIM.terms.Storage);
storageMetadata.add(RDF.terms.type, PIM.terms.Storage);
accessor.data[`${root}container/`] = new BasicRepresentation(representation.data, storageMetadata);
accessor.data[`${root}container/.dummy`] = representation;
auxiliaryStrategy.isRequiredInRoot = jest.fn().mockReturnValue(true);

View File

@ -18,6 +18,8 @@ import { toLiteral } from '../../../../src/util/TermUtil';
import { CONTENT_TYPE, DC, LDP, POSIX, RDF, SOLID_META, XSD } from '../../../../src/util/Vocabularies';
import { mockFs } from '../../../util/Util';
const { namedNode } = DataFactory;
jest.mock('fs');
const rootFilePath = 'uploads';
@ -104,10 +106,11 @@ describe('A FileDataAccessor', (): void => {
metadata = await accessor.getMetadata({ path: `${base}resource.ttl` });
expect(metadata.identifier.value).toBe(`${base}resource.ttl`);
expect(metadata.contentType).toBe('text/turtle');
expect(metadata.get(RDF.type)?.value).toBe(LDP.Resource);
expect(metadata.get(POSIX.size)).toEqualRdfTerm(toLiteral('data'.length, XSD.terms.integer));
expect(metadata.get(DC.modified)).toEqualRdfTerm(toLiteral(now.toISOString(), XSD.terms.dateTime));
expect(metadata.get(POSIX.mtime)).toEqualRdfTerm(toLiteral(Math.floor(now.getTime() / 1000), XSD.terms.integer));
expect(metadata.get(RDF.terms.type)?.value).toBe(LDP.Resource);
expect(metadata.get(POSIX.terms.size)).toEqualRdfTerm(toLiteral('data'.length, XSD.terms.integer));
expect(metadata.get(DC.terms.modified)).toEqualRdfTerm(toLiteral(now.toISOString(), XSD.terms.dateTime));
expect(metadata.get(POSIX.terms.mtime)).toEqualRdfTerm(toLiteral(Math.floor(now.getTime() / 1000),
XSD.terms.integer));
// `dc:modified` is in the default graph
expect(metadata.quads(null, null, null, SOLID_META.terms.ResponseMetadata)).toHaveLength(2);
});
@ -115,8 +118,8 @@ describe('A FileDataAccessor', (): void => {
it('does not generate size metadata for a container.', async(): Promise<void> => {
cache.data = { container: {}};
metadata = await accessor.getMetadata({ path: `${base}container/` });
expect(metadata.get(POSIX.size)).toBeUndefined();
expect(metadata.get(DC.modified)).toEqualRdfTerm(toLiteral(now.toISOString(), XSD.terms.dateTime));
expect(metadata.get(POSIX.terms.size)).toBeUndefined();
expect(metadata.get(DC.terms.modified)).toEqualRdfTerm(toLiteral(now.toISOString(), XSD.terms.dateTime));
});
it('generates the metadata for a container.', async(): Promise<void> => {
@ -130,12 +133,13 @@ describe('A FileDataAccessor', (): void => {
};
metadata = await accessor.getMetadata({ path: `${base}container/` });
expect(metadata.identifier.value).toBe(`${base}container/`);
expect(metadata.getAll(RDF.type)).toEqualRdfTermArray(
expect(metadata.getAll(RDF.terms.type)).toEqualRdfTermArray(
[ LDP.terms.Container, LDP.terms.BasicContainer, LDP.terms.Resource ],
);
expect(metadata.get(POSIX.size)).toBeUndefined();
expect(metadata.get(DC.modified)).toEqualRdfTerm(toLiteral(now.toISOString(), XSD.terms.dateTime));
expect(metadata.get(POSIX.mtime)).toEqualRdfTerm(toLiteral(Math.floor(now.getTime() / 1000), XSD.terms.integer));
expect(metadata.get(POSIX.terms.size)).toBeUndefined();
expect(metadata.get(DC.terms.modified)).toEqualRdfTerm(toLiteral(now.toISOString(), XSD.terms.dateTime));
expect(metadata.get(POSIX.terms.mtime)).toEqualRdfTerm(toLiteral(Math.floor(now.getTime() / 1000),
XSD.terms.integer));
// `dc:modified` is in the default graph
expect(metadata.quads(null, null, null, SOLID_META.terms.ResponseMetadata)).toHaveLength(1);
});
@ -169,7 +173,7 @@ describe('A FileDataAccessor', (): void => {
// Containers
for (const child of children.filter(({ identifier }): boolean => identifier.value.endsWith('/'))) {
const types = child.getAll(RDF.type).map((term): string => term.value);
const types = child.getAll(RDF.terms.type).map((term): string => term.value);
expect(types).toContain(LDP.Resource);
expect(types).toContain(LDP.Container);
expect(types).toContain(LDP.BasicContainer);
@ -177,7 +181,7 @@ describe('A FileDataAccessor', (): void => {
// Documents
for (const child of children.filter(({ identifier }): boolean => !identifier.value.endsWith('/'))) {
const types = child.getAll(RDF.type).map((term): string => term.value);
const types = child.getAll(RDF.terms.type).map((term): string => term.value);
expect(types).toContain(LDP.Resource);
expect(types).toContain('http://www.w3.org/ns/iana/media-types/application/octet-stream#Resource');
expect(types).not.toContain(LDP.Container);
@ -186,8 +190,8 @@ describe('A FileDataAccessor', (): void => {
// All resources
for (const child of children) {
expect(child.get(DC.modified)).toEqualRdfTerm(toLiteral(now.toISOString(), XSD.terms.dateTime));
expect(child.get(POSIX.mtime)).toEqualRdfTerm(toLiteral(Math.floor(now.getTime() / 1000),
expect(child.get(DC.terms.modified)).toEqualRdfTerm(toLiteral(now.toISOString(), XSD.terms.dateTime));
expect(child.get(POSIX.terms.mtime)).toEqualRdfTerm(toLiteral(Math.floor(now.getTime() / 1000),
XSD.terms.integer));
// `dc:modified` is in the default graph
expect(child.quads(null, null, null, SOLID_META.terms.ResponseMetadata))
@ -228,8 +232,8 @@ describe('A FileDataAccessor', (): void => {
`${base}container/resource2`,
]));
const types1 = children[0].getAll(RDF.type).map((term): string => term.value);
const types2 = children[1].getAll(RDF.type).map((term): string => term.value);
const types1 = children[0].getAll(RDF.terms.type).map((term): string => term.value);
const types2 = children[1].getAll(RDF.terms.type).map((term): string => term.value);
expect(types1).toContain('http://www.w3.org/ns/iana/media-types/application/octet-stream#Resource');
for (const type of types2) {
@ -279,7 +283,7 @@ describe('A FileDataAccessor', (): void => {
});
it('does not write metadata that is stored by the file system.', async(): Promise<void> => {
metadata.add(RDF.type, LDP.terms.Resource);
metadata.add(RDF.terms.type, LDP.terms.Resource);
await expect(accessor.writeDocument({ path: `${base}resource` }, data, metadata)).resolves.toBeUndefined();
expect(cache.data.resource).toBe('data');
expect(cache.data['resource.meta']).toBeUndefined();
@ -315,7 +319,7 @@ describe('A FileDataAccessor', (): void => {
data.emit('error', new Error('error'));
return null;
};
metadata.add('likes', 'apples');
metadata.add(namedNode('likes'), 'apples');
await expect(accessor.writeDocument({ path: `${base}resource` }, data, metadata))
.rejects.toThrow('error');
expect(cache.data['resource.meta']).toBeUndefined();
@ -325,7 +329,7 @@ describe('A FileDataAccessor', (): void => {
cache.data = { 'resource$.ttl': '<this> <is> <data>.', 'resource.meta': '<this> <is> <metadata>.' };
metadata.identifier = DataFactory.namedNode(`${base}resource`);
metadata.contentType = 'text/plain';
metadata.add('new', 'metadata');
metadata.add(namedNode('new'), 'metadata');
await expect(accessor.writeDocument({ path: `${base}resource` }, data, metadata))
.resolves.toBeUndefined();
expect(cache.data).toEqual({
@ -337,7 +341,7 @@ describe('A FileDataAccessor', (): void => {
it('does not try to update the content-type if there is no original file.', async(): Promise<void> => {
metadata.identifier = DataFactory.namedNode(`${base}resource.txt`);
metadata.contentType = 'text/turtle';
metadata.add('new', 'metadata');
metadata.add(namedNode('new'), 'metadata');
await expect(accessor.writeDocument({ path: `${base}resource.txt` }, data, metadata))
.resolves.toBeUndefined();
expect(cache.data).toEqual({

View File

@ -133,14 +133,14 @@ describe('An InMemoryDataAccessor', (): void => {
)).resolves.toBeUndefined();
const newMetadata = new RepresentationMetadata(inputMetadata);
newMetadata.add(RDF.type, LDP.terms.BasicContainer);
newMetadata.add(RDF.terms.type, LDP.terms.BasicContainer);
await expect(accessor.writeContainer(identifier, newMetadata)).resolves.toBeUndefined();
metadata = await accessor.getMetadata(identifier);
expect(metadata.identifier.value).toBe(`${base}container/`);
const quads = metadata.quads();
expect(quads).toHaveLength(2);
expect(metadata.getAll(RDF.type).map((term): string => term.value))
expect(metadata.getAll(RDF.terms.type).map((term): string => term.value))
.toEqual([ LDP.Container, LDP.BasicContainer ]);
const children = [];
@ -168,7 +168,7 @@ describe('An InMemoryDataAccessor', (): void => {
expect(metadata.identifier.value).toBe(`${base}`);
const quads = metadata.quads();
expect(quads).toHaveLength(1);
expect(metadata.getAll(RDF.type)).toHaveLength(1);
expect(metadata.getAll(RDF.terms.type)).toHaveLength(1);
const children = [];
for await (const child of accessor.getChildren(identifier)) {

View File

@ -63,7 +63,7 @@ describe('A ConstantConverter', (): void => {
it('does not support representations that are already in the right format.', async(): Promise<void> => {
const preferences = { type: { 'text/html': 1 }};
const metadata = new RepresentationMetadata({ contentType: 'text/html' });
const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'text/html' });
const representation = { metadata } as any;
const args = { identifier, representation, preferences };
@ -101,7 +101,7 @@ describe('A ConstantConverter', (): void => {
it('replaces the representation of a supported request.', async(): Promise<void> => {
const preferences = { type: { 'text/html': 1 }};
const metadata = new RepresentationMetadata({ contentType: 'text/turtle' });
const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'text/turtle' });
const representation = { metadata, data: { destroy: jest.fn() }} as any;
const args = { identifier, representation, preferences };

View File

@ -2,6 +2,7 @@ import 'jest-rdf';
import { RepresentationMetadata } from '../../../../src/http/representation/RepresentationMetadata';
import { ContentTypeReplacer } from '../../../../src/storage/conversion/ContentTypeReplacer';
import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError';
import { CONTENT_TYPE } from '../../../../src/util/Vocabularies';
const binary = true;
const data = { data: true };
@ -21,7 +22,7 @@ describe('A ContentTypeReplacer', (): void => {
});
it('throws on an unsupported input type.', async(): Promise<void> => {
const metadata = new RepresentationMetadata({ contentType: 'text/plain' });
const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'text/plain' });
const representation = { metadata };
const preferences = { type: { 'application/json': 1 }};
@ -31,7 +32,7 @@ describe('A ContentTypeReplacer', (): void => {
});
it('throws on an unsupported output type.', async(): Promise<void> => {
const metadata = new RepresentationMetadata({ contentType: 'application/n-triples' });
const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'application/n-triples' });
const representation = { metadata };
const preferences = { type: { 'application/json': 1 }};
@ -51,7 +52,7 @@ describe('A ContentTypeReplacer', (): void => {
});
it('replaces a supported content type when no preferences are given.', async(): Promise<void> => {
const metadata = new RepresentationMetadata({ contentType: 'application/n-triples' });
const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'application/n-triples' });
const representation = { binary, data, metadata };
const preferences = {};
@ -62,7 +63,7 @@ describe('A ContentTypeReplacer', (): void => {
});
it('replaces a supported content type when preferences are given.', async(): Promise<void> => {
const metadata = new RepresentationMetadata({ contentType: 'application/n-triples' });
const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'application/n-triples' });
const representation = { binary, data, metadata };
const preferences = { type: { 'application/n-quads': 1 }};
@ -73,7 +74,7 @@ describe('A ContentTypeReplacer', (): void => {
});
it('replaces a supported wildcard type.', async(): Promise<void> => {
const metadata = new RepresentationMetadata({ contentType: 'text/plain' });
const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'text/plain' });
const representation = { binary, data, metadata };
const preferences = { type: { 'application/octet-stream': 1 }};
@ -84,7 +85,7 @@ describe('A ContentTypeReplacer', (): void => {
});
it('picks the most preferred content type.', async(): Promise<void> => {
const metadata = new RepresentationMetadata({ contentType: 'application/n-triples' });
const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'application/n-triples' });
const representation = { binary, data, metadata };
const preferences = { type: {
'text/turtle': 0.5,

View File

@ -1,14 +1,13 @@
import 'jest-rdf';
import { DataFactory } from 'n3';
import {
toCachedNamedNode,
toNamedTerm,
toPredicateTerm,
toObjectTerm,
toLiteral,
isTerm,
} from '../../../src/util/TermUtil';
import { CONTENT_TYPE_TERM, XSD } from '../../../src/util/Vocabularies';
import { XSD } from '../../../src/util/Vocabularies';
const { literal, namedNode } = DataFactory;
describe('TermUtil', (): void => {
@ -22,27 +21,6 @@ describe('TermUtil', (): void => {
});
});
describe('toCachedNamedNode function', (): void => {
it('returns the input if it was a named node.', async(): Promise<void> => {
const term = namedNode('name');
expect(toCachedNamedNode(term)).toBe(term);
});
it('returns a named node when a string is used.', async(): Promise<void> => {
expect(toCachedNamedNode('name')).toEqualRdfTerm(namedNode('name'));
});
it('caches generated named nodes.', async(): Promise<void> => {
const result = toCachedNamedNode('name');
expect(result).toEqualRdfTerm(namedNode('name'));
expect(toCachedNamedNode('name')).toBe(result);
});
it('supports URI shorthands.', async(): Promise<void> => {
expect(toCachedNamedNode('contentType')).toEqualRdfTerm(CONTENT_TYPE_TERM);
});
});
describe('toSubjectTerm function', (): void => {
it('returns the input if it was a term.', async(): Promise<void> => {
const nn = namedNode('name');