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
26 changed files with 172 additions and 183 deletions

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);