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:
Arthur Joppart
2022-07-06 14:40:28 +02:00
committed by GitHub
parent 1c65b06392
commit e0954cf2a7
22 changed files with 468 additions and 217 deletions

View File

@@ -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!',