feat: Added resourceExists method to ResourceStore

* feat: added resourceExists method to ResourceStore

* Merge remote-tracking branch 'origin/main' into feat/add-resourceExists-method-to-ResourceStore

* fix: adapted to review

* fix: adapted to review
This commit is contained in:
Arne Vandoorslaer 2021-02-25 13:43:58 +01:00 committed by GitHub
parent e5b7d99da4
commit b3f292d718
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 136 additions and 67 deletions

View File

@ -22,5 +22,5 @@ module.exports = {
'/test/',
],
// Slower machines had problems calling the WebSocket integration callbacks on time
testTimeout: 10000,
testTimeout: 15000,
};

View File

@ -4,7 +4,6 @@ import { BasicRepresentation } from '../ldp/representation/BasicRepresentation';
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
import { getLoggerFor } from '../logging/LogUtil';
import type { ResourceStore } from '../storage/ResourceStore';
import { containsResource } from '../storage/StoreUtil';
import { TEXT_TURTLE } from '../util/ContentTypes';
import { ensureTrailingSlash, joinFilePath } from '../util/PathUtil';
import { Initializer } from './Initializer';
@ -39,7 +38,7 @@ export class AclInitializer extends Initializer {
// https://solid.github.io/specification/protocol#storage
public async handle(): Promise<void> {
const rootAcl = this.aclStrategy.getAuxiliaryIdentifier(this.root);
if (await containsResource(this.store, rootAcl)) {
if (await this.store.resourceExists(rootAcl)) {
this.logger.debug(`Existing root ACL document found at ${rootAcl.path}`);
} else {
this.logger.debug(`Installing root ACL document at ${rootAcl.path}`);

View File

@ -4,7 +4,6 @@ import { RepresentationMetadata } from '../ldp/representation/RepresentationMeta
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
import { getLoggerFor } from '../logging/LogUtil';
import type { ResourceStore } from '../storage/ResourceStore';
import { containsResource } from '../storage/StoreUtil';
import { TEXT_TURTLE } from '../util/ContentTypes';
import { ensureTrailingSlash } from '../util/PathUtil';
import { generateResourceQuads } from '../util/ResourceUtil';
@ -32,7 +31,7 @@ export class RootContainerInitializer extends Initializer {
public async handle(): Promise<void> {
this.logger.debug(`Checking for root container at ${this.baseId.path}`);
if (!await containsResource(this.store, this.baseId)) {
if (!await this.store.resourceExists(this.baseId)) {
await this.createRootContainer();
} else {
this.logger.debug(`Existing root container found at ${this.baseId.path}`);

View File

@ -1,7 +1,6 @@
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
import { getLoggerFor } from '../logging/LogUtil';
import type { ResourceStore } from '../storage/ResourceStore';
import { containsResource } from '../storage/StoreUtil';
import { ConflictHttpError } from '../util/errors/ConflictHttpError';
import type { Agent } from './agent/Agent';
import type { IdentifierGenerator } from './generate/IdentifierGenerator';
@ -33,7 +32,7 @@ export class GeneratedPodManager implements PodManager {
public async createPod(agent: Agent): Promise<ResourceIdentifier> {
const podIdentifier = this.idGenerator.generate(agent.login);
this.logger.info(`Creating pod ${podIdentifier.path}`);
if (await containsResource(this.store, podIdentifier)) {
if (await this.store.resourceExists(podIdentifier)) {
throw new ConflictHttpError(`There already is a resource at ${podIdentifier.path}`);
}

View File

@ -11,6 +11,10 @@ import type { ResourceStore } from './ResourceStore';
*/
/* eslint-disable @typescript-eslint/no-unused-vars */
export class BaseResourceStore implements ResourceStore {
public async resourceExists(identifier: ResourceIdentifier, conditions?: Conditions): Promise<boolean> {
throw new NotImplementedHttpError();
}
public async getRepresentation(identifier: ResourceIdentifier, preferences: RepresentationPreferences,
conditions?: Conditions): Promise<Representation> {
throw new NotImplementedHttpError();

View File

@ -67,6 +67,19 @@ export class DataAccessorBasedStore implements ResourceStore {
this.auxiliaryStrategy = auxiliaryStrategy;
}
public async resourceExists(identifier: ResourceIdentifier): Promise<boolean> {
try {
this.validateIdentifier(identifier);
await this.accessor.getMetadata(identifier);
return true;
} catch (error: unknown) {
if (NotFoundHttpError.isInstance(error)) {
return false;
}
throw error;
}
}
public async getRepresentation(identifier: ResourceIdentifier): Promise<Representation> {
this.validateIdentifier(identifier);

View File

@ -35,6 +35,11 @@ export class LockingResourceStore implements AtomicResourceStore {
this.strategy = strategy;
}
public async resourceExists(identifier: ResourceIdentifier, conditions?: Conditions): Promise<boolean> {
return this.locks.withReadLock(this.getLockIdentifier(identifier),
async(): Promise<boolean> => this.source.resourceExists(identifier, conditions));
}
public async getRepresentation(identifier: ResourceIdentifier, preferences: RepresentationPreferences,
conditions?: Conditions): Promise<Representation> {
return this.lockedRepresentationRun(this.getLockIdentifier(identifier),

View File

@ -19,6 +19,10 @@ export class MonitoringStore<T extends ResourceStore = ResourceStore>
this.source = source;
}
public async resourceExists(identifier: ResourceIdentifier, conditions?: Conditions): Promise<boolean> {
return this.source.resourceExists(identifier, conditions);
}
public async getRepresentation(identifier: ResourceIdentifier, preferences: RepresentationPreferences,
conditions?: Conditions): Promise<Representation> {
return this.source.getRepresentation(identifier, preferences, conditions);

View File

@ -17,6 +17,15 @@ export class PassthroughStore<T extends ResourceStore = ResourceStore> implement
this.source = source;
}
public async resourceExists(identifier: ResourceIdentifier, conditions?: Conditions): Promise<boolean> {
return this.source.resourceExists(identifier, conditions);
}
public async getRepresentation(identifier: ResourceIdentifier, preferences: RepresentationPreferences,
conditions?: Conditions): Promise<Representation> {
return this.source.getRepresentation(identifier, preferences, conditions);
}
public async addResource(container: ResourceIdentifier, representation: Representation,
conditions?: Conditions): Promise<ResourceIdentifier> {
return this.source.addResource(container, representation, conditions);
@ -27,11 +36,6 @@ export class PassthroughStore<T extends ResourceStore = ResourceStore> implement
return this.source.deleteResource(identifier, conditions);
}
public async getRepresentation(identifier: ResourceIdentifier, preferences: RepresentationPreferences,
conditions?: Conditions): Promise<Representation> {
return this.source.getRepresentation(identifier, preferences, conditions);
}
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
conditions?: Conditions): Promise<ResourceIdentifier[]> {
return this.source.modifyResource(identifier, patch, conditions);

View File

@ -16,6 +16,15 @@ import type { Conditions } from './Conditions';
* should those be relevant to the store.
*/
export interface ResourceStore {
/**
* Check if a resource exists.
* @param identifier - Identifier of resource to check.
*
* @returns A promise resolving if the resource already exists
*/
resourceExists: (identifier: ResourceIdentifier, conditions?: Conditions) => Promise<boolean>;
/**
* Retrieves a representation of a resource.
* @param identifier - Identifier of the resource to read.

View File

@ -20,6 +20,11 @@ export class RoutingResourceStore implements ResourceStore {
this.rule = rule;
}
public async resourceExists(identifier: ResourceIdentifier, conditions?: Conditions):
Promise<boolean> {
return (await this.getStore(identifier)).resourceExists(identifier, conditions);
}
public async getRepresentation(identifier: ResourceIdentifier, preferences: RepresentationPreferences,
conditions?: Conditions): Promise<Representation> {
return (await this.getStore(identifier)).getRepresentation(identifier, preferences, conditions);

View File

@ -1,16 +0,0 @@
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
import { NotFoundHttpError } from '../util/errors/NotFoundHttpError';
import type { ResourceStore } from './ResourceStore';
export async function containsResource(store: ResourceStore, identifier: ResourceIdentifier): Promise<boolean> {
try {
const result = await store.getRepresentation(identifier, {});
result.data.destroy();
return true;
} catch (error: unknown) {
if (NotFoundHttpError.isInstance(error)) {
return false;
}
throw error;
}
}

View File

@ -1,6 +1,5 @@
import type { Representation } from '../../ldp/representation/Representation';
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
import { containsResource } from '../../storage/StoreUtil';
import type { ResourceStore } from '../ResourceStore';
import type { PreferenceSupport } from './PreferenceSupport';
import { RouterRule } from './RouterRule';
@ -40,8 +39,7 @@ export class ConvertingRouterRule extends RouterRule {
entry.supportChecker.supports({ identifier, representation }));
} else {
// No content-type given so we can only check if one of the stores has data for the identifier
store = await this.findStore(async(entry): Promise<boolean> =>
containsResource(entry.store, input.identifier));
store = await this.findStore(async(entry): Promise<boolean> => entry.store.resourceExists(input.identifier));
}
return store;
}

View File

@ -42,7 +42,6 @@ describe.each(stores)('An LDP handler with auth using %s', (name, { storeUrn, te
variables,
) as Record<string, any>;
({ handler, store, initializer } = instances);
// Set up the internal store
await initializer.handleSafe();

View File

@ -3,7 +3,6 @@ import { AclInitializer } from '../../../src/init/AclInitializer';
import type { AuxiliaryIdentifierStrategy } from '../../../src/ldp/auxiliary/AuxiliaryIdentifierStrategy';
import { BasicRepresentation } from '../../../src/ldp/representation/BasicRepresentation';
import type { ResourceStore } from '../../../src/storage/ResourceStore';
import { NotFoundHttpError } from '../../../src/util/errors/NotFoundHttpError';
import { joinFilePath } from '../../../src/util/PathUtil';
const createReadStream = jest.spyOn(fs, 'createReadStream').mockReturnValue('file contents' as any);
@ -15,8 +14,8 @@ const RepresentationMock: jest.Mock<BasicRepresentation> = BasicRepresentation a
describe('AclInitializer', (): void => {
const store: jest.Mocked<ResourceStore> = {
getRepresentation: jest.fn().mockRejectedValue(new NotFoundHttpError()),
setRepresentation: jest.fn(),
resourceExists: jest.fn().mockImplementation((): any => false),
} as any;
const aclIdentifier = { path: 'http://test.com/.acl' };
const aclStrategy: jest.Mocked<AuxiliaryIdentifierStrategy> = {
@ -33,8 +32,8 @@ describe('AclInitializer', (): void => {
await initializer.handle();
expect(aclStrategy.getAuxiliaryIdentifier).toHaveBeenCalledWith({ path: baseUrl });
expect(store.getRepresentation).toHaveBeenCalledTimes(1);
expect(store.getRepresentation).toHaveBeenCalledWith(aclIdentifier, {});
expect(store.resourceExists).toHaveBeenCalledTimes(1);
expect(store.resourceExists).toHaveBeenCalledWith(aclIdentifier);
expect(store.setRepresentation).toHaveBeenCalledTimes(1);
expect(store.setRepresentation).toHaveBeenCalledWith(
{ path: 'http://test.com/.acl' }, RepresentationMock.mock.instances[0],
@ -49,8 +48,8 @@ describe('AclInitializer', (): void => {
await initializer.handle();
expect(aclStrategy.getAuxiliaryIdentifier).toHaveBeenCalledWith({ path: baseUrl });
expect(store.getRepresentation).toHaveBeenCalledTimes(1);
expect(store.getRepresentation).toHaveBeenCalledWith(aclIdentifier, {});
expect(store.resourceExists).toHaveBeenCalledTimes(1);
expect(store.resourceExists).toHaveBeenCalledWith(aclIdentifier);
expect(store.setRepresentation).toHaveBeenCalledTimes(1);
expect(store.setRepresentation).toHaveBeenCalledWith(
{ path: 'http://test.com/.acl' }, RepresentationMock.mock.instances[0],
@ -61,21 +60,19 @@ describe('AclInitializer', (): void => {
});
it('does not invoke ACL initialization when a root ACL already exists.', async(): Promise<void> => {
store.getRepresentation.mockReturnValueOnce(Promise.resolve({
data: { destroy: jest.fn() },
} as any));
store.resourceExists.mockResolvedValueOnce(true);
const initializer = new AclInitializer({ baseUrl, store, aclStrategy });
await initializer.handle();
expect(aclStrategy.getAuxiliaryIdentifier).toHaveBeenCalledWith({ path: baseUrl });
expect(store.getRepresentation).toHaveBeenCalledTimes(1);
expect(store.getRepresentation).toHaveBeenCalledWith(aclIdentifier, {});
expect(store.resourceExists).toHaveBeenCalledTimes(1);
expect(store.resourceExists).toHaveBeenCalledWith(aclIdentifier);
expect(store.setRepresentation).toHaveBeenCalledTimes(0);
});
it('errors when the root ACL check errors.', async(): Promise<void> => {
store.getRepresentation.mockRejectedValueOnce(new Error('Fatal'));
store.resourceExists.mockRejectedValueOnce(new Error('Fatal'));
const initializer = new AclInitializer({ baseUrl, store, aclStrategy });
await expect(initializer.handle()).rejects.toThrow('Fatal');

View File

@ -7,6 +7,7 @@ describe('A RootContainerInitializer', (): void => {
const store: jest.Mocked<ResourceStore> = {
getRepresentation: jest.fn().mockRejectedValue(new NotFoundHttpError()),
setRepresentation: jest.fn(),
resourceExists: jest.fn(),
} as any;
const initializer = new RootContainerInitializer({ store, baseUrl });
@ -15,27 +16,29 @@ describe('A RootContainerInitializer', (): void => {
});
it('invokes ResourceStore initialization.', async(): Promise<void> => {
store.resourceExists.mockResolvedValueOnce(false);
await initializer.handle();
expect(store.getRepresentation).toHaveBeenCalledTimes(1);
expect(store.getRepresentation).toHaveBeenCalledWith({ path: baseUrl }, {});
expect(store.resourceExists).toHaveBeenCalledTimes(1);
expect(store.resourceExists).toHaveBeenCalledWith({ path: baseUrl });
expect(store.setRepresentation).toHaveBeenCalledTimes(1);
});
it('does not invoke ResourceStore initialization when a root container already exists.', async(): Promise<void> => {
store.resourceExists.mockResolvedValueOnce(true);
store.getRepresentation.mockReturnValueOnce(Promise.resolve({
data: { destroy: jest.fn() },
} as any));
await initializer.handle();
expect(store.getRepresentation).toHaveBeenCalledTimes(1);
expect(store.getRepresentation).toHaveBeenCalledWith({ path: 'http://test.com/' }, {});
expect(store.resourceExists).toHaveBeenCalledTimes(1);
expect(store.resourceExists).toHaveBeenCalledWith({ path: 'http://test.com/' });
expect(store.setRepresentation).toHaveBeenCalledTimes(0);
});
it('errors when the store errors writing the root container.', async(): Promise<void> => {
store.getRepresentation.mockRejectedValueOnce(new Error('Fatal'));
store.resourceExists.mockRejectedValueOnce(new Error('Fatal'));
await expect(initializer.handle()).rejects.toThrow('Fatal');
});
});

View File

@ -1,4 +1,3 @@
import { BasicRepresentation } from '../../../src/ldp/representation/BasicRepresentation';
import type { ResourceIdentifier } from '../../../src/ldp/representation/ResourceIdentifier';
import type { Agent } from '../../../src/pods/agent/Agent';
import type { IdentifierGenerator } from '../../../src/pods/generate/IdentifierGenerator';
@ -6,12 +5,11 @@ import type { Resource, ResourcesGenerator } from '../../../src/pods/generate/Re
import { GeneratedPodManager } from '../../../src/pods/GeneratedPodManager';
import type { ResourceStore } from '../../../src/storage/ResourceStore';
import { ConflictHttpError } from '../../../src/util/errors/ConflictHttpError';
import { NotFoundHttpError } from '../../../src/util/errors/NotFoundHttpError';
describe('A GeneratedPodManager', (): void => {
const base = 'http://test.com/';
let agent: Agent;
let store: ResourceStore;
let store: jest.Mocked<ResourceStore>;
let generatorData: Resource[];
const idGenerator: IdentifierGenerator = {
generate: (slug: string): ResourceIdentifier => ({ path: `${base}${slug}/` }),
@ -26,10 +24,8 @@ describe('A GeneratedPodManager', (): void => {
webId: 'http://secure/webId',
};
store = {
getRepresentation: jest.fn((): any => {
throw new NotFoundHttpError();
}),
setRepresentation: jest.fn(),
resourceExists: jest.fn(),
} as any;
generatorData = [
{ identifier: { path: '/path/' }, representation: '/' as any },
@ -45,7 +41,7 @@ describe('A GeneratedPodManager', (): void => {
});
it('throws an error if the generate identifier is not available.', async(): Promise<void> => {
(store.getRepresentation as jest.Mock).mockImplementationOnce((): any => new BasicRepresentation([], {}));
store.resourceExists.mockResolvedValueOnce(true);
const result = manager.createPod(agent);
await expect(result).rejects.toThrow(`There already is a resource at ${base}user/`);
await expect(result).rejects.toThrow(ConflictHttpError);

View File

@ -24,4 +24,8 @@ describe('A BaseResourceStore', (): void => {
it('errors on modifyResource.', async(): Promise<void> => {
await expect(store.modifyResource(any, any)).rejects.toThrow(NotImplementedHttpError);
});
it('errors on resourceExists.', async(): Promise<void> => {
await expect(store.resourceExists(any)).rejects.toThrow(NotImplementedHttpError);
});
});

View File

@ -604,5 +604,28 @@ describe('A DataAccessorBasedStore', (): void => {
);
});
});
describe('resource Exists', (): void => {
it('should return false when the resource does not exist.', async(): Promise<void> => {
const resourceID = { path: `${root}resource` };
await expect(store.resourceExists(resourceID)).resolves.toBeFalsy();
});
it('should return true when the resource exists.', async(): Promise<void> => {
const resourceID = { path: `${root}resource` };
accessor.data[resourceID.path] = representation;
await expect(store.resourceExists(resourceID)).resolves.toBeTruthy();
});
it('should rethrow any unexpected errors from validateIdentifier.', async(): Promise<void> => {
const resourceID = { path: `${root}resource` };
const originalMetaData = accessor.getMetadata;
accessor.getMetadata = jest.fn(async(): Promise<any> => {
throw new Error('error');
});
await expect(store.resourceExists(resourceID)).rejects.toThrow('error');
accessor.getMetadata = originalMetaData;
});
});
});

View File

@ -39,6 +39,7 @@ describe('A LockingResourceStore', (): void => {
setRepresentation: jest.fn((): any => addOrder('setRepresentation')),
deleteResource: jest.fn((): any => addOrder('deleteResource')),
modifyResource: jest.fn((): any => addOrder('modifyResource')),
resourceExists: jest.fn((): any => addOrder('resourceExists')),
};
timeoutTrigger = new EventEmitter();
@ -285,4 +286,13 @@ describe('A LockingResourceStore', (): void => {
expect(source.getRepresentation).toHaveBeenCalledTimes(1);
expect(order).toEqual([ 'lock read', 'useless get', 'timeout', 'unlock read' ]);
});
it('resourceExists should only acquire and release the read lock.', async(): Promise<void> => {
await store.resourceExists(associatedId);
expect(locker.withReadLock).toHaveBeenCalledTimes(1);
expect(locker.withWriteLock).toHaveBeenCalledTimes(0);
expect(source.resourceExists).toHaveBeenCalledTimes(1);
expect(source.resourceExists).toHaveBeenLastCalledWith(associatedId, undefined);
expect(order).toEqual([ 'lock read', 'resourceExists', 'unlock read' ]);
});
});

View File

@ -19,6 +19,7 @@ describe('A MonitoringStore', (): void => {
setRepresentation: jest.fn(async(): Promise<any> => modified),
deleteResource: jest.fn(async(): Promise<any> => modified),
modifyResource: jest.fn(async(): Promise<any> => modified),
resourceExists: jest.fn(async(): Promise<any> => undefined),
};
store = new MonitoringStore(source);
changedCallback = jest.fn();
@ -104,4 +105,10 @@ describe('A MonitoringStore', (): void => {
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/modified/1' });
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/modified/2' });
});
it('calls resourceExists directly from the source.', async(): Promise<void> => {
await expect(store.resourceExists({ path: 'http://example.org/foo/bar' })).resolves.toBeUndefined();
expect(source.resourceExists).toHaveBeenCalledTimes(1);
expect(source.resourceExists).toHaveBeenLastCalledWith({ path: 'http://example.org/foo/bar' }, undefined);
});
});

View File

@ -14,6 +14,7 @@ describe('A PassthroughStore', (): void => {
setRepresentation: jest.fn(async(): Promise<any> => 'set'),
deleteResource: jest.fn(async(): Promise<any> => 'delete'),
modifyResource: jest.fn(async(): Promise<any> => 'modify'),
resourceExists: jest.fn(async(): Promise<any> => 'exists'),
};
store = new PassthroughStore(source);
@ -48,4 +49,10 @@ describe('A PassthroughStore', (): void => {
expect(source.modifyResource).toHaveBeenCalledTimes(1);
expect(source.modifyResource).toHaveBeenLastCalledWith({ path: 'modifyPath' }, {}, undefined);
});
it('calls resourceExists directly from the source.', async(): Promise<void> => {
await expect(store.resourceExists({ path: 'existsPath' })).resolves.toBe('exists');
expect(source.resourceExists).toHaveBeenCalledTimes(1);
expect(source.resourceExists).toHaveBeenLastCalledWith({ path: 'existsPath' }, undefined);
});
});

View File

@ -17,6 +17,7 @@ describe('A RoutingResourceStore', (): void => {
setRepresentation: jest.fn(),
modifyResource: jest.fn(),
deleteResource: jest.fn(),
resourceExists: jest.fn(),
};
rule = new StaticAsyncHandler(true, source);
@ -59,6 +60,12 @@ describe('A RoutingResourceStore', (): void => {
expect(source.deleteResource).toHaveBeenLastCalledWith(identifier, 'conditions');
});
it('calls resourceExists on the resulting store.', async(): Promise<void> => {
await expect(store.resourceExists(identifier)).resolves.toBeUndefined();
expect(source.resourceExists).toHaveBeenCalledTimes(1);
expect(source.resourceExists).toHaveBeenLastCalledWith(identifier, undefined);
});
it('throws a 404 if there is no body and no store was found.', async(): Promise<void> => {
rule.canHandle = (): any => {
throw new NotImplementedHttpError();

View File

@ -1,11 +1,9 @@
import type { Readable } from 'stream';
import type { Representation } from '../../../../src/ldp/representation/Representation';
import { RepresentationMetadata } from '../../../../src/ldp/representation/RepresentationMetadata';
import type { ResourceStore } from '../../../../src/storage/ResourceStore';
import { ConvertingRouterRule } from '../../../../src/storage/routing/ConvertingRouterRule';
import type { PreferenceSupport } from '../../../../src/storage/routing/PreferenceSupport';
import { InternalServerError } from '../../../../src/util/errors/InternalServerError';
import { NotFoundHttpError } from '../../../../src/util/errors/NotFoundHttpError';
describe('A ConvertingRouterRule', (): void => {
let store1: ResourceStore;
@ -41,23 +39,18 @@ describe('A ConvertingRouterRule', (): void => {
});
it('checks if the stores contain the identifier if there is no data.', async(): Promise<void> => {
const data: Readable = { destroy: jest.fn() } as any;
store1.getRepresentation = async(): Promise<Representation> => ({ data } as any);
store1.resourceExists = jest.fn().mockImplementationOnce((): any => true);
await expect(rule.handle({ identifier: { path: 'identifier' }})).resolves.toBe(store1);
expect(data.destroy).toHaveBeenCalledTimes(1);
expect(store1.resourceExists).toHaveBeenCalledTimes(1);
});
it('returns the defaultStore if no other store has the resource.', async(): Promise<void> => {
store1.getRepresentation = (): any => {
throw new NotFoundHttpError();
};
store1.resourceExists = jest.fn().mockImplementationOnce((): any => false);
await expect(rule.handle({ identifier: { path: 'identifier' }})).resolves.toBe(defaultStore);
});
it('throws the error if a store had a non-404 error.', async(): Promise<void> => {
store1.getRepresentation = (): any => {
throw new InternalServerError();
};
store1.resourceExists = jest.fn().mockRejectedValueOnce(new InternalServerError());
await expect(rule.handle({ identifier: { path: 'identifier' }})).rejects.toThrow(InternalServerError);
});
});