mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Rework ResourceStore to return extra info
* feat: change return types in ResourceStore.ts
* feat: change return types in BaseResourceStore.ts
* feat: change return types in LockingResourceStore.ts
* feat: change return types in RoutingResourceStore.ts
* feat: change return types in MonitoringStore.ts
* feat: change return types in PassthroughStore.ts
* feat: change return types in ReadOnlyStore.ts
* feat: change return types in PatchHandler.ts
* feat: change return types in PatchingStore.ts
* feat: change return types in RepresentationPatchHandler.ts
* feat: create createResourceIdentifier() function for convenience
* feat: adapt PostOperationHandler.ts to new typing
* feat: change return types in RepresentationConvertingStore.ts
* feat: adapt DataAccessorBasedStore.ts implementation to new typings
* feat: adapt UnsecureWebSocketsProtocol.ts to new typing
* chore: add temporary comments
* fix: return correct Location header on POST request with slug
* fix: npm run lint command needs more packages
* fix: linting errors
* chore: revert ed9952b
* test: adapt PostOperationHandler tests
* test: adapt UnsecureWebSocketsProtocol tests
* test: adapt DataAccessorBasedStore tests
* fix: linting errors
* feat: emit specific created, deleted, updated events in MonitoringStore
* test: adapt RepresentationPatchHandler tests
* fix: revert UnsecureWebSocketsProtocol changes
* feat: emit extra parameter on changed
* test: adapt MonitoringStore tests
* fix: linting errors
* test: add test to MonitorStore.test for coverage
* fix: linting error
* chore: update doc in ResourceStore.ts
* test: improve MonitoringStore tests
* chore: update RELEASE_NOTES.md
* chore: add extra info about the MonitoringStore to documentation/resource-store.md
* chore: Update RELEASE_NOTES.md
Co-authored-by: Anton Wiklund <ixuz07@gmail.com>
* chore: Update documentation/resource-store.md
Co-authored-by: Anton Wiklund <ixuz07@gmail.com>
* chore: very small changes
* chore: simplify metadata creation
* fix: DataAccessorBasedStore improvement and bugfix
* chore: improve resource-store.md
* chore: adapt MonitoringStore event names, update docs and apply code suggestion
* chore: use ResourceStoreResponse type
* fix: typo
* chore: rename ResourceStoreResponse type to ChangeMap
* chore: adapt .gitignore to name change
Co-authored-by: Anton Wiklund <ixuz07@gmail.com>
This commit is contained in:
@@ -3,12 +3,11 @@ import type { Operation } from '../../../../src/http/Operation';
|
||||
import { BasicRepresentation } from '../../../../src/http/representation/BasicRepresentation';
|
||||
import type { Representation } from '../../../../src/http/representation/Representation';
|
||||
import { RepresentationMetadata } from '../../../../src/http/representation/RepresentationMetadata';
|
||||
import type { ResourceIdentifier } from '../../../../src/http/representation/ResourceIdentifier';
|
||||
import { BasicConditions } from '../../../../src/storage/BasicConditions';
|
||||
import type { ResourceStore } from '../../../../src/storage/ResourceStore';
|
||||
import type { ResourceStore, ChangeMap } from '../../../../src/storage/ResourceStore';
|
||||
import { BadRequestHttpError } from '../../../../src/util/errors/BadRequestHttpError';
|
||||
import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError';
|
||||
import { SOLID_HTTP } from '../../../../src/util/Vocabularies';
|
||||
import { AS, SOLID_AS, SOLID_HTTP } from '../../../../src/util/Vocabularies';
|
||||
|
||||
describe('A PostOperationHandler', (): void => {
|
||||
let operation: Operation;
|
||||
@@ -21,7 +20,16 @@ describe('A PostOperationHandler', (): void => {
|
||||
body = new BasicRepresentation('', 'text/turtle');
|
||||
operation = { method: 'POST', target: { path: 'http://test.com/foo' }, body, conditions, preferences: {}};
|
||||
store = {
|
||||
addResource: jest.fn(async(): Promise<ResourceIdentifier> => ({ path: 'newPath' } as ResourceIdentifier)),
|
||||
addResource: jest.fn(async(): Promise<ChangeMap> => ({
|
||||
'https://example.com/parent/newPath': new RepresentationMetadata(
|
||||
{ path: 'https://example.com/parent/newPath' },
|
||||
{ [SOLID_AS.terms.Activity.value]: AS.Create },
|
||||
),
|
||||
'https://example.com/parent/': new RepresentationMetadata(
|
||||
{ path: 'https://example.come/parent/' },
|
||||
{ [SOLID_AS.terms.Activity.value]: AS.Update },
|
||||
),
|
||||
})),
|
||||
} as unknown as ResourceStore;
|
||||
handler = new PostOperationHandler(store);
|
||||
});
|
||||
@@ -41,7 +49,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.terms.location)?.value).toBe('newPath');
|
||||
expect(result.metadata?.get(SOLID_HTTP.terms.location)?.value).toBe('https://example.com/parent/newPath');
|
||||
expect(result.data).toBeUndefined();
|
||||
expect(store.addResource).toHaveBeenCalledTimes(1);
|
||||
expect(store.addResource).toHaveBeenLastCalledWith(operation.target, body, conditions);
|
||||
|
||||
@@ -23,7 +23,7 @@ import type { Guarded } from '../../../src/util/GuardedStream';
|
||||
import { SingleRootIdentifierStrategy } from '../../../src/util/identifiers/SingleRootIdentifierStrategy';
|
||||
import { trimTrailingSlashes } from '../../../src/util/PathUtil';
|
||||
import { guardedStreamFrom } from '../../../src/util/StreamUtil';
|
||||
import { CONTENT_TYPE, SOLID_HTTP, LDP, PIM, RDF, SOLID_META, DC } from '../../../src/util/Vocabularies';
|
||||
import { CONTENT_TYPE, SOLID_HTTP, LDP, PIM, RDF, SOLID_META, DC, SOLID_AS, AS } from '../../../src/util/Vocabularies';
|
||||
const { namedNode, quad } = DataFactory;
|
||||
|
||||
const GENERATED_PREDICATE = namedNode('generated');
|
||||
@@ -265,11 +265,22 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
const resourceID = { path: root };
|
||||
representation.metadata.removeAll(RDF.terms.type);
|
||||
const result = await store.addResource(resourceID, representation);
|
||||
expect(result).toEqual({
|
||||
path: expect.stringMatching(new RegExp(`^${root}[^/]+$`, 'u')),
|
||||
});
|
||||
await expect(arrayifyStream(accessor.data[result.path].data)).resolves.toEqual([ resourceData ]);
|
||||
expect(accessor.data[result.path].metadata.get(DC.terms.modified)?.value).toBe(now.toISOString());
|
||||
|
||||
expect(Object.keys(result)).toEqual([
|
||||
root,
|
||||
expect.stringMatching(new RegExp(`^${root}[^/]+$`, 'u')),
|
||||
]);
|
||||
|
||||
expect(result[root].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Update);
|
||||
|
||||
const generatedID = Object.keys(result).find((key): boolean => key !== root)!;
|
||||
expect(generatedID).toBeDefined();
|
||||
expect(generatedID).toMatch(new RegExp(`^${root}[^/]+$`, 'u'));
|
||||
|
||||
await expect(arrayifyStream(accessor.data[generatedID].data)).resolves.toEqual([ resourceData ]);
|
||||
expect(accessor.data[generatedID]).toBeTruthy();
|
||||
expect(accessor.data[generatedID].metadata.get(DC.terms.modified)?.value).toBe(now.toISOString());
|
||||
expect(result[generatedID].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Create);
|
||||
});
|
||||
|
||||
it('can write containers.', async(): Promise<void> => {
|
||||
@@ -278,16 +289,25 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
representation.metadata.contentType = 'text/turtle';
|
||||
representation.data = guardedStreamFrom([ '<> a <http://test.com/coolContainer>.' ]);
|
||||
const result = await store.addResource(resourceID, representation);
|
||||
expect(result).toEqual({
|
||||
path: expect.stringMatching(new RegExp(`^${root}[^/]+/$`, 'u')),
|
||||
});
|
||||
expect(accessor.data[result.path]).toBeTruthy();
|
||||
expect(accessor.data[result.path].metadata.contentType).toBeUndefined();
|
||||
|
||||
const { data, metadata } = await store.getRepresentation(result);
|
||||
expect(Object.keys(result)).toEqual([
|
||||
root,
|
||||
expect.stringMatching(new RegExp(`^${root}[^/]+?/$`, 'u')),
|
||||
]);
|
||||
|
||||
expect(result[root].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Update);
|
||||
|
||||
const generatedID = Object.keys(result).find((key): boolean => key !== root)!;
|
||||
expect(generatedID).toBeDefined();
|
||||
expect(generatedID).toMatch(new RegExp(`^${root}[^/]+?/$`, 'u'));
|
||||
expect(accessor.data[generatedID]).toBeTruthy();
|
||||
expect(accessor.data[generatedID].metadata.contentType).toBeUndefined();
|
||||
expect(result[generatedID].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Create);
|
||||
|
||||
const { data, metadata } = await store.getRepresentation({ path: generatedID });
|
||||
const quads = await arrayifyStream<Quad>(data);
|
||||
expect(metadata.get(DC.terms.modified)?.value).toBe(now.toISOString());
|
||||
expect(quads.some((entry): boolean => entry.subject.value === result.path &&
|
||||
expect(quads.some((entry): boolean => entry.subject.value === generatedID &&
|
||||
entry.object.value === 'http://test.com/coolContainer')).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -295,10 +315,14 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
const resourceID = { path: root };
|
||||
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`,
|
||||
|
||||
const result = store.addResource(resourceID, representation);
|
||||
await expect(result).resolves.toEqual({
|
||||
[root]: expect.any(RepresentationMetadata),
|
||||
[`${root}newName`]: expect.any(RepresentationMetadata),
|
||||
});
|
||||
expect((await result)[root].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Update);
|
||||
expect((await result)[`${root}newName`].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Create);
|
||||
});
|
||||
|
||||
it('errors on a slug ending on / without Link rel:type Container header.', async(): Promise<void> => {
|
||||
@@ -320,10 +344,14 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
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({
|
||||
path: `${root}newContainer/`,
|
||||
[root]: expect.any(RepresentationMetadata),
|
||||
[`${root}newContainer/`]: expect.any(RepresentationMetadata),
|
||||
});
|
||||
expect(result[root].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Update);
|
||||
expect(result[`${root}newContainer/`].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Create);
|
||||
});
|
||||
|
||||
it('generates a new URI if adding the slug would create an existing URI.', async(): Promise<void> => {
|
||||
@@ -331,20 +359,26 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
representation.metadata.add(SOLID_HTTP.terms.slug, 'newName');
|
||||
accessor.data[`${root}newName`] = representation;
|
||||
const result = await store.addResource(resourceID, representation);
|
||||
expect(result).not.toEqual({
|
||||
path: `${root}newName`,
|
||||
});
|
||||
expect(result).not.toEqual({
|
||||
path: expect.stringMatching(new RegExp(`^${root}[^/]+/$`, 'u')),
|
||||
});
|
||||
expect(result).not.toEqual(expect.objectContaining({
|
||||
[`${root}newName`]: expect.any(RepresentationMetadata),
|
||||
}));
|
||||
expect(result).not.toEqual(expect.objectContaining({
|
||||
[expect.any(String)]: expect.stringMatching(new RegExp(`^${root}[^/]+/$`, 'u')),
|
||||
}));
|
||||
});
|
||||
|
||||
it('generates http://test.com/%26%26 when slug is &%26.', async(): Promise<void> => {
|
||||
const resourceID = { path: root };
|
||||
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` });
|
||||
|
||||
const result = store.addResource(resourceID, representation);
|
||||
await expect(result).resolves.toEqual({
|
||||
[root]: expect.any(RepresentationMetadata),
|
||||
[`${root}%26%26`]: expect.any(RepresentationMetadata),
|
||||
});
|
||||
expect((await result)[root].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Update);
|
||||
expect((await result)[`${root}%26%26`].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Create);
|
||||
});
|
||||
|
||||
it('errors if the slug contains a slash.', async(): Promise<void> => {
|
||||
@@ -408,8 +442,11 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
representation.metadata.contentType = 'text/turtle';
|
||||
representation.data = guardedStreamFrom([ `<${root}> a <coolContainer>.` ]);
|
||||
|
||||
await expect(store.setRepresentation(resourceID, representation)).resolves
|
||||
.toEqual([{ path: `${root}` }]);
|
||||
const result = store.setRepresentation(resourceID, representation);
|
||||
await expect(result).resolves.toEqual({
|
||||
[root]: expect.any(RepresentationMetadata),
|
||||
});
|
||||
expect((await result)[root].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Create);
|
||||
expect(mock).toHaveBeenCalledTimes(1);
|
||||
expect(mock).toHaveBeenLastCalledWith(resourceID);
|
||||
|
||||
@@ -438,10 +475,13 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
|
||||
it('can write resources.', async(): Promise<void> => {
|
||||
const resourceID = { path: `${root}resource` };
|
||||
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
|
||||
{ path: root },
|
||||
{ path: `${root}resource` },
|
||||
]);
|
||||
const result = store.setRepresentation(resourceID, representation);
|
||||
await expect(result).resolves.toEqual({
|
||||
[root]: expect.any(RepresentationMetadata),
|
||||
[resourceID.path]: expect.any(RepresentationMetadata),
|
||||
});
|
||||
expect((await result)[root].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Update);
|
||||
expect((await result)[resourceID.path].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Create);
|
||||
await expect(arrayifyStream(accessor.data[resourceID.path].data)).resolves.toEqual([ resourceData ]);
|
||||
expect(accessor.data[resourceID.path].metadata.get(DC.terms.modified)?.value).toBe(now.toISOString());
|
||||
expect(accessor.data[root].metadata.get(DC.terms.modified)?.value).toBe(now.toISOString());
|
||||
@@ -455,10 +495,13 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
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([
|
||||
{ path: root },
|
||||
{ path: `${root}container/` },
|
||||
]);
|
||||
const result = store.setRepresentation(resourceID, representation);
|
||||
await expect(result).resolves.toEqual({
|
||||
[root]: expect.any(RepresentationMetadata),
|
||||
[resourceID.path]: expect.any(RepresentationMetadata),
|
||||
});
|
||||
expect((await result)[root].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Update);
|
||||
expect((await result)[resourceID.path].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Create);
|
||||
expect(accessor.data[resourceID.path]).toBeTruthy();
|
||||
expect(accessor.data[resourceID.path].metadata.contentType).toBeUndefined();
|
||||
expect(accessor.data[resourceID.path].metadata.get(DC.terms.modified)?.value).toBe(now.toISOString());
|
||||
@@ -468,10 +511,13 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
|
||||
it('can overwrite resources which does not update parent metadata.', async(): Promise<void> => {
|
||||
const resourceID = { path: `${root}resource` };
|
||||
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
|
||||
{ path: root },
|
||||
{ path: `${root}resource` },
|
||||
]);
|
||||
const result = store.setRepresentation(resourceID, representation);
|
||||
await expect(result).resolves.toEqual({
|
||||
[root]: expect.any(RepresentationMetadata),
|
||||
[resourceID.path]: expect.any(RepresentationMetadata),
|
||||
});
|
||||
expect((await result)[root].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Update);
|
||||
expect((await result)[resourceID.path].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Create);
|
||||
await expect(arrayifyStream(accessor.data[resourceID.path].data)).resolves.toEqual([ resourceData ]);
|
||||
expect(accessor.data[resourceID.path].metadata.get(DC.terms.modified)?.value).toBe(now.toISOString());
|
||||
expect(accessor.data[root].metadata.get(DC.terms.modified)?.value).toBe(now.toISOString());
|
||||
@@ -479,9 +525,11 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
// Parent metadata does not get updated if the resource already exists
|
||||
representation = new BasicRepresentation('updatedText', 'text/plain');
|
||||
mockDate.mockReturnValue(later);
|
||||
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
|
||||
{ path: `${root}resource` },
|
||||
]);
|
||||
const result2 = store.setRepresentation(resourceID, representation);
|
||||
await expect(result2).resolves.toEqual({
|
||||
[resourceID.path]: expect.any(RepresentationMetadata),
|
||||
});
|
||||
expect((await result2)[resourceID.path].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Update);
|
||||
await expect(arrayifyStream(accessor.data[resourceID.path].data)).resolves.toEqual([ 'updatedText' ]);
|
||||
expect(accessor.data[resourceID.path].metadata.get(DC.terms.modified)?.value).toBe(later.toISOString());
|
||||
expect(accessor.data[root].metadata.get(DC.terms.modified)?.value).toBe(now.toISOString());
|
||||
@@ -492,10 +540,13 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
const resourceID = { path: `${root}resource` };
|
||||
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` },
|
||||
]);
|
||||
const result = store.setRepresentation(resourceID, representation);
|
||||
await expect(result).resolves.toEqual({
|
||||
[root]: expect.any(RepresentationMetadata),
|
||||
[resourceID.path]: expect.any(RepresentationMetadata),
|
||||
});
|
||||
expect((await result)[root].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Update);
|
||||
expect((await result)[resourceID.path].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Create);
|
||||
await expect(arrayifyStream(accessor.data[resourceID.path].data)).resolves.toEqual([ resourceData ]);
|
||||
expect(accessor.data[resourceID.path].metadata.get(namedNode('notGen'))?.value).toBe('value');
|
||||
expect(accessor.data[resourceID.path].metadata.get(namedNode('gen'))).toBeUndefined();
|
||||
@@ -505,10 +556,13 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete accessor.data[root];
|
||||
const resourceID = { path: `${root}resource` };
|
||||
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
|
||||
{ path: `${root}` },
|
||||
{ path: `${root}resource` },
|
||||
]);
|
||||
const result = store.setRepresentation(resourceID, representation);
|
||||
await expect(result).resolves.toEqual({
|
||||
[root]: expect.any(RepresentationMetadata),
|
||||
[resourceID.path]: expect.any(RepresentationMetadata),
|
||||
});
|
||||
expect((await result)[root].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Create);
|
||||
expect((await result)[resourceID.path].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Create);
|
||||
await expect(arrayifyStream(accessor.data[resourceID.path].data)).resolves.toEqual([ resourceData ]);
|
||||
});
|
||||
|
||||
@@ -521,10 +575,13 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
representation.data = guardedStreamFrom(
|
||||
[ quad(namedNode(`${root}resource/`), namedNode('a'), namedNode('coolContainer')) ],
|
||||
);
|
||||
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
|
||||
{ path: `${root}` },
|
||||
{ path: `${root}container/` },
|
||||
]);
|
||||
const result = store.setRepresentation(resourceID, representation);
|
||||
await expect(result).resolves.toEqual({
|
||||
[root]: expect.any(RepresentationMetadata),
|
||||
[resourceID.path]: expect.any(RepresentationMetadata),
|
||||
});
|
||||
expect((await result)[root].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Update);
|
||||
expect((await result)[resourceID.path].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Create);
|
||||
expect(accessor.data[resourceID.path]).toBeTruthy();
|
||||
expect(accessor.data[resourceID.path].metadata.contentType).toBeUndefined();
|
||||
});
|
||||
@@ -544,11 +601,17 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
|
||||
it('creates recursive containers when needed.', async(): Promise<void> => {
|
||||
const resourceID = { path: `${root}a/b/resource` };
|
||||
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
|
||||
{ path: `${root}a/` },
|
||||
{ path: `${root}a/b/` },
|
||||
{ path: `${root}a/b/resource` },
|
||||
]);
|
||||
const result = store.setRepresentation(resourceID, representation);
|
||||
await expect(result).resolves.toEqual({
|
||||
[`${root}`]: expect.any(RepresentationMetadata),
|
||||
[`${root}a/`]: expect.any(RepresentationMetadata),
|
||||
[`${root}a/b/`]: expect.any(RepresentationMetadata),
|
||||
[`${root}a/b/resource`]: expect.any(RepresentationMetadata),
|
||||
});
|
||||
expect((await result)[`${root}`].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Update);
|
||||
expect((await result)[`${root}a/`].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Create);
|
||||
expect((await result)[`${root}a/b/`].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Create);
|
||||
expect((await result)[`${root}a/b/resource`].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Create);
|
||||
await expect(arrayifyStream(accessor.data[resourceID.path].data)).resolves.toEqual([ resourceData ]);
|
||||
expect(accessor.data[`${root}a/`].metadata.getAll(RDF.terms.type).map((type): string => type.value))
|
||||
.toContain(LDP.Container);
|
||||
@@ -573,9 +636,11 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
representation.metadata.removeAll(RDF.terms.type);
|
||||
representation.metadata.contentType = 'text/turtle';
|
||||
representation.data = guardedStreamFrom([]);
|
||||
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
|
||||
{ path: `${root}` },
|
||||
]);
|
||||
const result = store.setRepresentation(resourceID, representation);
|
||||
await expect(result).resolves.toEqual({
|
||||
[root]: expect.any(RepresentationMetadata),
|
||||
});
|
||||
expect((await result)[root].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Create);
|
||||
expect(accessor.data[resourceID.path]).toBeTruthy();
|
||||
expect(Object.keys(accessor.data)).toHaveLength(1);
|
||||
expect(accessor.data[resourceID.path].metadata.contentType).toBeUndefined();
|
||||
@@ -658,51 +723,70 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
});
|
||||
|
||||
it('will delete resources.', async(): Promise<void> => {
|
||||
accessor.data[`${root}resource`] = representation;
|
||||
await expect(store.deleteResource({ path: `${root}resource` })).resolves.toEqual([
|
||||
{ path: `${root}resource` },
|
||||
{ path: root },
|
||||
]);
|
||||
expect(accessor.data[`${root}resource`]).toBeUndefined();
|
||||
const resourceID = { path: `${root}resource` };
|
||||
accessor.data[resourceID.path] = representation;
|
||||
await expect(store.deleteResource(resourceID)).resolves.toEqual({
|
||||
[root]: expect.any(RepresentationMetadata),
|
||||
[resourceID.path]: expect.any(RepresentationMetadata),
|
||||
});
|
||||
expect(accessor.data[resourceID.path]).toBeUndefined();
|
||||
expect(accessor.data[root].metadata.get(DC.terms.modified)?.value).toBe(now.toISOString());
|
||||
expect(accessor.data[root].metadata.get(GENERATED_PREDICATE)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('will delete root non-storage containers.', async(): Promise<void> => {
|
||||
accessor.data[root] = new BasicRepresentation(representation.data, containerMetadata);
|
||||
await expect(store.deleteResource({ path: root })).resolves.toEqual([
|
||||
{ path: root },
|
||||
]);
|
||||
await expect(store.deleteResource({ path: root })).resolves.toEqual(
|
||||
{ [root]: expect.any(RepresentationMetadata) },
|
||||
);
|
||||
expect(accessor.data[root]).toBeUndefined();
|
||||
});
|
||||
|
||||
it('will delete a root storage auxiliary resource of a non-root container.', async(): Promise<void> => {
|
||||
const resourceID = { path: `${root}container/` };
|
||||
const auxResourceID = { path: `${root}container/.dummy` };
|
||||
const storageMetadata = new RepresentationMetadata(representation.metadata);
|
||||
accessor.data[`${root}container/`] = new BasicRepresentation(representation.data, storageMetadata);
|
||||
accessor.data[`${root}container/.dummy`] = representation;
|
||||
accessor.data[resourceID.path] = new BasicRepresentation(representation.data, storageMetadata);
|
||||
accessor.data[auxResourceID.path] = representation;
|
||||
auxiliaryStrategy.isRequiredInRoot = jest.fn().mockReturnValue(true);
|
||||
await expect(store.deleteResource({ path: `${root}container/.dummy` })).resolves.toEqual([
|
||||
{ path: `${root}container/.dummy` },
|
||||
{ path: `${root}container/` },
|
||||
]);
|
||||
expect(accessor.data[`${root}container/.dummy`]).toBeUndefined();
|
||||
const result = store.deleteResource(auxResourceID);
|
||||
await expect(result).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
[resourceID.path]: expect.any(RepresentationMetadata),
|
||||
[auxResourceID.path]: expect.any(RepresentationMetadata),
|
||||
}),
|
||||
);
|
||||
expect((await result)[resourceID.path].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Update);
|
||||
expect((await result)[auxResourceID.path].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Delete);
|
||||
expect(accessor.data[auxResourceID.path]).toBeUndefined();
|
||||
});
|
||||
|
||||
it('will delete related auxiliary resources.', async(): Promise<void> => {
|
||||
accessor.data[`${root}container/`] = representation;
|
||||
accessor.data[`${root}container/.dummy`] = representation;
|
||||
await expect(store.deleteResource({ path: `${root}container/` })).resolves.toEqual([
|
||||
{ path: `${root}container/` },
|
||||
{ path: `${root}container/.dummy` },
|
||||
{ path: root },
|
||||
]);
|
||||
expect(accessor.data[`${root}container/`]).toBeUndefined();
|
||||
expect(accessor.data[`${root}container/.dummy`]).toBeUndefined();
|
||||
const resourceID = { path: `${root}container/` };
|
||||
const auxResourceID = { path: `${root}container/.dummy` };
|
||||
accessor.data[resourceID.path] = representation;
|
||||
accessor.data[auxResourceID.path] = representation;
|
||||
|
||||
const result = store.deleteResource(resourceID);
|
||||
await expect(result).resolves.toEqual(
|
||||
expect.objectContaining({
|
||||
[root]: expect.any(RepresentationMetadata),
|
||||
[resourceID.path]: expect.any(RepresentationMetadata),
|
||||
[auxResourceID.path]: expect.any(RepresentationMetadata),
|
||||
}),
|
||||
);
|
||||
expect((await result)[root].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Update);
|
||||
expect((await result)[resourceID.path].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Delete);
|
||||
expect((await result)[auxResourceID.path].get(SOLID_AS.terms.Activity)?.value).toBe(AS.Delete);
|
||||
expect(accessor.data[resourceID.path]).toBeUndefined();
|
||||
expect(accessor.data[auxResourceID.path]).toBeUndefined();
|
||||
});
|
||||
|
||||
it('will still delete a resource if deleting auxiliary resources causes errors.', async(): Promise<void> => {
|
||||
accessor.data[`${root}resource`] = representation;
|
||||
accessor.data[`${root}resource.dummy`] = representation;
|
||||
const resourceID = { path: `${root}resource` };
|
||||
const auxResourceID = { path: `${root}resource.dummy` };
|
||||
accessor.data[resourceID.path] = representation;
|
||||
accessor.data[auxResourceID.path] = representation;
|
||||
const deleteFn = accessor.deleteResource;
|
||||
accessor.deleteResource = jest.fn(async(identifier: ResourceIdentifier): Promise<void> => {
|
||||
if (auxiliaryStrategy.isAuxiliaryIdentifier(identifier)) {
|
||||
@@ -712,12 +796,14 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
});
|
||||
const { logger } = store as any;
|
||||
logger.error = jest.fn();
|
||||
await expect(store.deleteResource({ path: `${root}resource` })).resolves.toEqual([
|
||||
{ path: `${root}resource` },
|
||||
{ path: root },
|
||||
]);
|
||||
expect(accessor.data[`${root}resource`]).toBeUndefined();
|
||||
expect(accessor.data[`${root}resource.dummy`]).toBeDefined();
|
||||
const result = store.deleteResource(resourceID);
|
||||
expect(Object.keys(await result)).toHaveLength(2);
|
||||
await expect(result).resolves.toEqual({
|
||||
[root]: expect.any(RepresentationMetadata),
|
||||
[resourceID.path]: expect.any(RepresentationMetadata),
|
||||
});
|
||||
expect(accessor.data[resourceID.path]).toBeUndefined();
|
||||
expect(accessor.data[auxResourceID.path]).toBeDefined();
|
||||
expect(logger.error).toHaveBeenCalledTimes(1);
|
||||
expect(logger.error).toHaveBeenLastCalledWith(
|
||||
'Error deleting auxiliary resource http://test.com/resource.dummy: auxiliary error!',
|
||||
|
||||
@@ -1,33 +1,86 @@
|
||||
import type { Patch } from '../../../src/http/representation/Patch';
|
||||
import type { Representation } from '../../../src/http/representation/Representation';
|
||||
import { RepresentationMetadata } from '../../../src/http/representation/RepresentationMetadata';
|
||||
import { MonitoringStore } from '../../../src/storage/MonitoringStore';
|
||||
import type { ResourceStore } from '../../../src/storage/ResourceStore';
|
||||
import { AS, SOLID_AS } from '../../../src/util/Vocabularies';
|
||||
|
||||
describe('A MonitoringStore', (): void => {
|
||||
let store: MonitoringStore;
|
||||
let source: ResourceStore;
|
||||
|
||||
let changedCallback: () => void;
|
||||
const modified = [
|
||||
{ path: 'http://example.org/modified/1' },
|
||||
{ path: 'http://example.org/modified/2' },
|
||||
];
|
||||
let createdCallback: () => void;
|
||||
let updatedCallback: () => void;
|
||||
let deletedCallback: () => void;
|
||||
|
||||
const addResourceReturnMock = {
|
||||
'http://example.org/foo/bar/new': new RepresentationMetadata(
|
||||
{ path: 'http://example.org/foo/bar/new' },
|
||||
{ [SOLID_AS.terms.Activity.value]: AS.Create },
|
||||
),
|
||||
'http://example.org/foo/bar/': new RepresentationMetadata(
|
||||
{ path: 'http://example.org/foo/bar/' },
|
||||
{ [SOLID_AS.terms.Activity.value]: AS.Update },
|
||||
),
|
||||
};
|
||||
const setRepresentationReturnMock = {
|
||||
'http://example.org/foo/bar/new': new RepresentationMetadata(
|
||||
{ path: 'http://example.org/foo/bar/new' },
|
||||
{ [SOLID_AS.terms.Activity.value]: AS.Update },
|
||||
),
|
||||
};
|
||||
const deleteResourceReturnMock = {
|
||||
'http://example.org/foo/bar/new': new RepresentationMetadata(
|
||||
{ path: 'http://example.org/foo/bar/new' },
|
||||
{ [SOLID_AS.terms.Activity.value]: AS.Delete },
|
||||
),
|
||||
'http://example.org/foo/bar/': new RepresentationMetadata(
|
||||
{ path: 'http://example.org/foo/bar/' },
|
||||
{ [SOLID_AS.terms.Activity.value]: AS.Update },
|
||||
),
|
||||
};
|
||||
const modifyResourceReturnMock = {
|
||||
'http://example.org/foo/bar/old': new RepresentationMetadata(
|
||||
{ path: 'http://example.org/foo/bar/new' },
|
||||
{ [SOLID_AS.terms.Activity.value]: AS.Delete },
|
||||
),
|
||||
'http://example.org/foo/bar/new': new RepresentationMetadata(
|
||||
{ path: 'http://example.org/foo/bar/new' },
|
||||
{ [SOLID_AS.terms.Activity.value]: AS.Create },
|
||||
),
|
||||
'http://example.org/foo/bar/': new RepresentationMetadata(
|
||||
{ path: 'http://example.org/foo/bar/' },
|
||||
{ [SOLID_AS.terms.Activity.value]: AS.Update },
|
||||
),
|
||||
};
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
source = {
|
||||
getRepresentation: jest.fn(async(): Promise<any> => ({ success: true })),
|
||||
addResource: jest.fn(async(): Promise<any> => ({ path: 'http://example.org/foo/bar/new' })),
|
||||
setRepresentation: jest.fn(async(): Promise<any> => modified),
|
||||
deleteResource: jest.fn(async(): Promise<any> => modified),
|
||||
modifyResource: jest.fn(async(): Promise<any> => modified),
|
||||
addResource: jest.fn(async(): Promise<any> => addResourceReturnMock),
|
||||
setRepresentation: jest.fn(async(): Promise<any> => setRepresentationReturnMock),
|
||||
deleteResource: jest.fn(async(): Promise<any> => deleteResourceReturnMock),
|
||||
modifyResource: jest.fn(async(): Promise<any> => modifyResourceReturnMock),
|
||||
hasResource: jest.fn(async(): Promise<any> => undefined),
|
||||
};
|
||||
store = new MonitoringStore(source);
|
||||
|
||||
changedCallback = jest.fn();
|
||||
createdCallback = jest.fn();
|
||||
updatedCallback = jest.fn();
|
||||
deletedCallback = jest.fn();
|
||||
store.on('changed', changedCallback);
|
||||
store.on(AS.Create, createdCallback);
|
||||
store.on(AS.Update, updatedCallback);
|
||||
store.on(AS.Delete, deletedCallback);
|
||||
});
|
||||
|
||||
afterEach(async(): Promise<void> => {
|
||||
store.removeListener('changed', changedCallback);
|
||||
store.removeListener(AS.Create, createdCallback);
|
||||
store.removeListener(AS.Update, updatedCallback);
|
||||
store.removeListener(AS.Delete, deletedCallback);
|
||||
});
|
||||
|
||||
it('calls getRepresentation directly from the source.', async(): Promise<void> => {
|
||||
@@ -43,67 +96,89 @@ describe('A MonitoringStore', (): void => {
|
||||
});
|
||||
|
||||
it('calls addResource directly from the source.', async(): Promise<void> => {
|
||||
await expect(store.addResource({ path: 'http://example.org/foo/bar' }, {} as Representation)).resolves
|
||||
.toStrictEqual({ path: 'http://example.org/foo/bar/new' });
|
||||
await expect(store.addResource({ path: 'http://example.org/foo/bar' }, {} as Representation)).resolves.toEqual({
|
||||
'http://example.org/foo/bar/new': expect.any(RepresentationMetadata),
|
||||
'http://example.org/foo/bar/': expect.any(RepresentationMetadata),
|
||||
});
|
||||
expect(source.addResource).toHaveBeenCalledTimes(1);
|
||||
expect(source.addResource).toHaveBeenLastCalledWith({ path: 'http://example.org/foo/bar' }, {}, undefined);
|
||||
});
|
||||
|
||||
it('fires container and resource change events after addResource.', async(): Promise<void> => {
|
||||
it('fires appropriate events according to the return value of source.addResource.', async(): Promise<void> => {
|
||||
const result = store.addResource({ path: 'http://example.org/foo/bar/' }, {} as Representation);
|
||||
expect(changedCallback).toHaveBeenCalledTimes(0);
|
||||
await result;
|
||||
expect(changedCallback).toHaveBeenCalledTimes(2);
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar/' });
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar/new' });
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar/' }, AS.Update);
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar/new' }, AS.Create);
|
||||
expect(createdCallback).toHaveBeenCalledTimes(1);
|
||||
expect(createdCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar/new' });
|
||||
expect(updatedCallback).toHaveBeenCalledTimes(1);
|
||||
expect(updatedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar/' });
|
||||
expect(deletedCallback).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('calls setRepresentation directly from the source.', async(): Promise<void> => {
|
||||
await expect(store.setRepresentation({ path: 'http://example.org/foo/bar' }, {} as Representation))
|
||||
.resolves.toEqual(modified);
|
||||
.resolves.toEqual(setRepresentationReturnMock);
|
||||
expect(source.setRepresentation).toHaveBeenCalledTimes(1);
|
||||
expect(source.setRepresentation).toHaveBeenLastCalledWith({ path: 'http://example.org/foo/bar' }, {}, undefined);
|
||||
});
|
||||
|
||||
it('fires all modified change events after setRepresentation.', async(): Promise<void> => {
|
||||
it('fires appropriate events according to the return value of source.setRepresentation.', async(): Promise<void> => {
|
||||
const result = store.setRepresentation({ path: 'http://example.org/foo/bar' }, {} as Representation);
|
||||
expect(changedCallback).toHaveBeenCalledTimes(0);
|
||||
await result;
|
||||
expect(changedCallback).toHaveBeenCalledTimes(2);
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/modified/1' });
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/modified/2' });
|
||||
expect(changedCallback).toHaveBeenCalledTimes(1);
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar/new' }, AS.Update);
|
||||
expect(createdCallback).toHaveBeenCalledTimes(0);
|
||||
expect(updatedCallback).toHaveBeenCalledTimes(1);
|
||||
expect(updatedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar/new' });
|
||||
expect(deletedCallback).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('calls deleteResource directly from the source.', async(): Promise<void> => {
|
||||
await expect(store.deleteResource({ path: 'http://example.org/foo/bar' }))
|
||||
.resolves.toEqual(modified);
|
||||
.resolves.toEqual(deleteResourceReturnMock);
|
||||
expect(source.deleteResource).toHaveBeenCalledTimes(1);
|
||||
expect(source.deleteResource).toHaveBeenLastCalledWith({ path: 'http://example.org/foo/bar' }, undefined);
|
||||
});
|
||||
|
||||
it('fires all modified change events after deleteResource.', async(): Promise<void> => {
|
||||
it('fires appropriate events according to the return value of source.deleteResource.', async(): Promise<void> => {
|
||||
const result = store.deleteResource({ path: 'http://example.org/foo/bar' });
|
||||
expect(changedCallback).toHaveBeenCalledTimes(0);
|
||||
await result;
|
||||
expect(changedCallback).toHaveBeenCalledTimes(2);
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/modified/1' });
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/modified/2' });
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar/' }, AS.Update);
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar/new' }, AS.Delete);
|
||||
expect(createdCallback).toHaveBeenCalledTimes(0);
|
||||
expect(updatedCallback).toHaveBeenCalledTimes(1);
|
||||
expect(updatedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar/' });
|
||||
expect(deletedCallback).toHaveBeenCalledTimes(1);
|
||||
expect(deletedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar/new' });
|
||||
});
|
||||
|
||||
it('calls modifyResource directly from the source.', async(): Promise<void> => {
|
||||
await expect(store.modifyResource({ path: 'http://example.org/foo/bar' }, {} as Patch))
|
||||
.resolves.toEqual(modified);
|
||||
.resolves.toEqual(modifyResourceReturnMock);
|
||||
expect(source.modifyResource).toHaveBeenCalledTimes(1);
|
||||
expect(source.modifyResource).toHaveBeenLastCalledWith({ path: 'http://example.org/foo/bar' }, {}, undefined);
|
||||
});
|
||||
|
||||
it('fires all modified change events after modifyResource.', async(): Promise<void> => {
|
||||
it('fires appropriate events according to the return value of source.modifyResource.', async(): Promise<void> => {
|
||||
const result = store.modifyResource({ path: 'http://example.org/foo/bar' }, {} as Patch);
|
||||
expect(changedCallback).toHaveBeenCalledTimes(0);
|
||||
await result;
|
||||
expect(changedCallback).toHaveBeenCalledTimes(2);
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/modified/1' });
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/modified/2' });
|
||||
expect(changedCallback).toHaveBeenCalledTimes(3);
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar/new' }, AS.Create);
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar/' }, AS.Update);
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar/old' }, AS.Delete);
|
||||
expect(createdCallback).toHaveBeenCalledTimes(1);
|
||||
expect(createdCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar/new' });
|
||||
expect(updatedCallback).toHaveBeenCalledTimes(1);
|
||||
expect(updatedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar/' });
|
||||
expect(deletedCallback).toHaveBeenCalledTimes(1);
|
||||
expect(deletedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar/old' });
|
||||
});
|
||||
|
||||
it('calls hasResource directly from the source.', async(): Promise<void> => {
|
||||
@@ -111,4 +186,20 @@ describe('A MonitoringStore', (): void => {
|
||||
expect(source.hasResource).toHaveBeenCalledTimes(1);
|
||||
expect(source.hasResource).toHaveBeenLastCalledWith({ path: 'http://example.org/foo/bar' });
|
||||
});
|
||||
|
||||
it('should not emit an extra event when the Activity is not a valid AS value.', async(): Promise<void> => {
|
||||
source.addResource = jest.fn(async(): Promise<any> => ({
|
||||
'http://example.com/path': new RepresentationMetadata(
|
||||
{ path: 'http://example.com/path' },
|
||||
{ [SOLID_AS.terms.Activity.value]: 'SomethingRandom' },
|
||||
),
|
||||
}));
|
||||
|
||||
await store.addResource({ path: 'http://example.org/foo/bar' }, {} as Patch);
|
||||
|
||||
expect(changedCallback).toHaveBeenCalledTimes(1);
|
||||
expect(createdCallback).toHaveBeenCalledTimes(0);
|
||||
expect(updatedCallback).toHaveBeenCalledTimes(0);
|
||||
expect(deletedCallback).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { BasicRepresentation } from '../../../../src/http/representation/BasicRepresentation';
|
||||
import type { Patch } from '../../../../src/http/representation/Patch';
|
||||
import { RepresentationMetadata } from '../../../../src/http/representation/RepresentationMetadata';
|
||||
import type { PatchHandlerInput } from '../../../../src/storage/patch/PatchHandler';
|
||||
import type { RepresentationPatcher } from '../../../../src/storage/patch/RepresentationPatcher';
|
||||
import { RepresentationPatchHandler } from '../../../../src/storage/patch/RepresentationPatchHandler';
|
||||
@@ -20,7 +21,9 @@ describe('A RepresentationPatchHandler', (): void => {
|
||||
beforeEach(async(): Promise<void> => {
|
||||
source = {
|
||||
getRepresentation: jest.fn().mockResolvedValue(representation),
|
||||
setRepresentation: jest.fn().mockResolvedValue([ identifier ]),
|
||||
setRepresentation: jest.fn().mockResolvedValue({
|
||||
[identifier.path]: new RepresentationMetadata(identifier),
|
||||
}),
|
||||
} as any;
|
||||
|
||||
input = { source, identifier, patch };
|
||||
@@ -33,7 +36,9 @@ describe('A RepresentationPatchHandler', (): void => {
|
||||
});
|
||||
|
||||
it('calls the patcher with the representation from the store.', async(): Promise<void> => {
|
||||
await expect(handler.handle(input)).resolves.toEqual([ identifier ]);
|
||||
await expect(handler.handle(input)).resolves.toEqual({
|
||||
[identifier.path]: new RepresentationMetadata(identifier),
|
||||
});
|
||||
|
||||
expect(patcher.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(patcher.handleSafe).toHaveBeenLastCalledWith({ identifier, patch, representation });
|
||||
@@ -45,7 +50,9 @@ describe('A RepresentationPatchHandler', (): void => {
|
||||
it('calls the patcher with no representation if there is none.', async(): Promise<void> => {
|
||||
source.getRepresentation.mockRejectedValueOnce(new NotFoundHttpError());
|
||||
|
||||
await expect(handler.handle(input)).resolves.toEqual([ identifier ]);
|
||||
await expect(handler.handle(input)).resolves.toEqual({
|
||||
[identifier.path]: new RepresentationMetadata(identifier),
|
||||
});
|
||||
|
||||
expect(patcher.handleSafe).toHaveBeenCalledTimes(1);
|
||||
expect(patcher.handleSafe).toHaveBeenLastCalledWith({ identifier, patch });
|
||||
|
||||
Reference in New Issue
Block a user