refactor: Rename resourceExists to hasResource

The function was also moved to the smaller interface ResourceSet.
This commit is contained in:
Joachim Van Herwegen 2022-02-23 15:41:31 +01:00
parent 2ae5924dde
commit 4404fa07d9
26 changed files with 73 additions and 64 deletions

View File

@ -21,6 +21,8 @@ The following changes are relevant for v3 custom configs that replaced certain f
These changes are relevant if you wrote custom modules for the server that depend on existing interfaces. These changes are relevant if you wrote custom modules for the server that depend on existing interfaces.
- The output of `parseContentType` in `HeaderUtil` was changed to include parameters. - The output of `parseContentType` in `HeaderUtil` was changed to include parameters.
- `PermissionReader`s take an additional `modes` parameter as input. - `PermissionReader`s take an additional `modes` parameter as input.
- The `ResourceStore` function `resourceExists` has been renamed to `hasResource`
and has been moved to a separate `ResourceSet` interface.
## v3.0.0 ## v3.0.0
### New features ### New features

View File

@ -42,7 +42,7 @@ export class PatchOperationHandler extends OperationHandler {
// RFC7231, §4.3.4: If the target resource does not have a current representation and the // RFC7231, §4.3.4: If the target resource does not have a current representation and the
// PUT successfully creates one, then the origin server MUST inform the // PUT successfully creates one, then the origin server MUST inform the
// user agent by sending a 201 (Created) response. // user agent by sending a 201 (Created) response.
const exists = await this.store.resourceExists(operation.target, operation.conditions); const exists = await this.store.hasResource(operation.target);
await this.store.modifyResource(operation.target, operation.body as Patch, operation.conditions); await this.store.modifyResource(operation.target, operation.body as Patch, operation.conditions);
if (exists) { if (exists) {
return new ResetResponseDescription(); return new ResetResponseDescription();

View File

@ -38,7 +38,7 @@ export class PutOperationHandler extends OperationHandler {
} }
// A more efficient approach would be to have the server return metadata indicating if a resource was new // A more efficient approach would be to have the server return metadata indicating if a resource was new
// See https://github.com/solid/community-server/issues/632 // See https://github.com/solid/community-server/issues/632
const exists = await this.store.resourceExists(operation.target, operation.conditions); const exists = await this.store.hasResource(operation.target);
await this.store.setRepresentation(operation.target, operation.body, operation.conditions); await this.store.setRepresentation(operation.target, operation.body, operation.conditions);
if (exists) { if (exists) {
return new ResetResponseDescription(); return new ResetResponseDescription();

View File

@ -358,6 +358,7 @@ export * from './storage/PassthroughStore';
export * from './storage/PatchingStore'; export * from './storage/PatchingStore';
export * from './storage/ReadOnlyStore'; export * from './storage/ReadOnlyStore';
export * from './storage/RepresentationConvertingStore'; export * from './storage/RepresentationConvertingStore';
export * from './storage/ResourceSet';
export * from './storage/ResourceStore'; export * from './storage/ResourceStore';
export * from './storage/RoutingResourceStore'; export * from './storage/RoutingResourceStore';

View File

@ -28,7 +28,7 @@ export class GeneratedPodManager implements PodManager {
*/ */
public async createPod(identifier: ResourceIdentifier, settings: PodSettings, overwrite: boolean): Promise<void> { public async createPod(identifier: ResourceIdentifier, settings: PodSettings, overwrite: boolean): Promise<void> {
this.logger.info(`Creating pod ${identifier.path}`); this.logger.info(`Creating pod ${identifier.path}`);
if (!overwrite && await this.store.resourceExists(identifier)) { if (!overwrite && await this.store.hasResource(identifier)) {
throw new ConflictHttpError(`There already is a resource at ${identifier.path}`); throw new ConflictHttpError(`There already is a resource at ${identifier.path}`);
} }

View File

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

View File

@ -81,7 +81,7 @@ export class DataAccessorBasedStore implements ResourceStore {
this.auxiliaryStrategy = auxiliaryStrategy; this.auxiliaryStrategy = auxiliaryStrategy;
} }
public async resourceExists(identifier: ResourceIdentifier): Promise<boolean> { public async hasResource(identifier: ResourceIdentifier): Promise<boolean> {
try { try {
this.validateIdentifier(identifier); this.validateIdentifier(identifier);
await this.accessor.getMetadata(identifier); await this.accessor.getMetadata(identifier);
@ -530,7 +530,7 @@ export class DataAccessorBasedStore implements ResourceStore {
// Make sure we don't already have a resource with this exact name (or with differing trailing slash) // Make sure we don't already have a resource with this exact name (or with differing trailing slash)
const withSlash = { path: ensureTrailingSlash(newID.path) }; const withSlash = { path: ensureTrailingSlash(newID.path) };
const withoutSlash = { path: trimTrailingSlashes(newID.path) }; const withoutSlash = { path: trimTrailingSlashes(newID.path) };
if (await this.resourceExists(withSlash) || await this.resourceExists(withoutSlash)) { if (await this.hasResource(withSlash) || await this.hasResource(withoutSlash)) {
newID = this.createURI(container, isContainer); newID = this.createURI(container, isContainer);
} }

View File

@ -34,9 +34,9 @@ export class LockingResourceStore implements AtomicResourceStore {
this.auxiliaryStrategy = auxiliaryStrategy; this.auxiliaryStrategy = auxiliaryStrategy;
} }
public async resourceExists(identifier: ResourceIdentifier, conditions?: Conditions): Promise<boolean> { public async hasResource(identifier: ResourceIdentifier): Promise<boolean> {
return this.locks.withReadLock(this.getLockIdentifier(identifier), return this.locks.withReadLock(this.getLockIdentifier(identifier),
async(): Promise<boolean> => this.source.resourceExists(identifier, conditions)); async(): Promise<boolean> => this.source.hasResource(identifier));
} }
public async getRepresentation(identifier: ResourceIdentifier, preferences: RepresentationPreferences, public async getRepresentation(identifier: ResourceIdentifier, preferences: RepresentationPreferences,

View File

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

View File

@ -17,8 +17,8 @@ export class PassthroughStore<T extends ResourceStore = ResourceStore> implement
this.source = source; this.source = source;
} }
public async resourceExists(identifier: ResourceIdentifier, conditions?: Conditions): Promise<boolean> { public async hasResource(identifier: ResourceIdentifier): Promise<boolean> {
return this.source.resourceExists(identifier, conditions); return this.source.hasResource(identifier);
} }
public async getRepresentation(identifier: ResourceIdentifier, preferences: RepresentationPreferences, public async getRepresentation(identifier: ResourceIdentifier, preferences: RepresentationPreferences,

View File

@ -0,0 +1,14 @@
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
/**
* A set containing resources.
*/
export interface ResourceSet {
/**
* Check if a resource exists in this ResourceSet.
* @param identifier - Identifier of resource to check.
*
* @returns A promise resolving if the resource already exists.
*/
hasResource: (identifier: ResourceIdentifier) => Promise<boolean>;
}

View File

@ -3,6 +3,7 @@ import type { Representation } from '../http/representation/Representation';
import type { RepresentationPreferences } from '../http/representation/RepresentationPreferences'; import type { RepresentationPreferences } from '../http/representation/RepresentationPreferences';
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier'; import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
import type { Conditions } from './Conditions'; import type { Conditions } from './Conditions';
import type { ResourceSet } from './ResourceSet';
/** /**
* A ResourceStore represents a collection of resources. * A ResourceStore represents a collection of resources.
@ -15,16 +16,7 @@ import type { Conditions } from './Conditions';
* ResourceStores are also responsible for taking auxiliary resources into account * ResourceStores are also responsible for taking auxiliary resources into account
* should those be relevant to the store. * should those be relevant to the store.
*/ */
export interface ResourceStore { export interface ResourceStore extends ResourceSet {
/**
* 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. * Retrieves a representation of a resource.
* @param identifier - Identifier of the resource to read. * @param identifier - Identifier of the resource to read.

View File

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

View File

@ -42,7 +42,7 @@ export class JsonResourceStorage implements KeyValueStorage<string, unknown> {
public async has(key: string): Promise<boolean> { public async has(key: string): Promise<boolean> {
const identifier = this.createIdentifier(key); const identifier = this.createIdentifier(key);
return await this.source.resourceExists(identifier); return await this.source.hasResource(identifier);
} }
public async set(key: string, value: unknown): Promise<this> { public async set(key: string, value: unknown): Promise<this> {

View File

@ -39,7 +39,7 @@ export class ConvertingRouterRule extends RouterRule {
entry.supportChecker.supports({ identifier, representation })); entry.supportChecker.supports({ identifier, representation }));
} else { } else {
// No content-type given so we can only check if one of the stores has data for the identifier // 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> => entry.store.resourceExists(identifier)); store = await this.findStore(async(entry): Promise<boolean> => entry.store.hasResource(identifier));
} }
return store; return store;
} }

View File

@ -19,7 +19,7 @@ describe('A PatchOperationHandler', (): void => {
operation = { method: 'PATCH', target: { path: 'http://test.com/foo' }, body, conditions, preferences: {}}; operation = { method: 'PATCH', target: { path: 'http://test.com/foo' }, body, conditions, preferences: {}};
store = { store = {
resourceExists: jest.fn(), hasResource: jest.fn(),
modifyResource: jest.fn(), modifyResource: jest.fn(),
} as any; } as any;
@ -47,7 +47,7 @@ describe('A PatchOperationHandler', (): void => {
}); });
it('returns the correct response if the resource already exists.', async(): Promise<void> => { it('returns the correct response if the resource already exists.', async(): Promise<void> => {
store.resourceExists.mockResolvedValueOnce(true); store.hasResource.mockResolvedValueOnce(true);
const result = await handler.handle({ operation }); const result = await handler.handle({ operation });
expect(store.modifyResource).toHaveBeenCalledTimes(1); expect(store.modifyResource).toHaveBeenCalledTimes(1);
expect(store.modifyResource).toHaveBeenLastCalledWith(operation.target, body, conditions); expect(store.modifyResource).toHaveBeenLastCalledWith(operation.target, body, conditions);

View File

@ -18,7 +18,7 @@ describe('A PutOperationHandler', (): void => {
body = new BasicRepresentation('', 'text/turtle'); body = new BasicRepresentation('', 'text/turtle');
operation = { method: 'PUT', target: { path: 'http://test.com/foo' }, body, conditions, preferences: {}}; operation = { method: 'PUT', target: { path: 'http://test.com/foo' }, body, conditions, preferences: {}};
store = { store = {
resourceExists: jest.fn(), hasResource: jest.fn(),
setRepresentation: jest.fn(), setRepresentation: jest.fn(),
} as any; } as any;
@ -46,7 +46,7 @@ describe('A PutOperationHandler', (): void => {
}); });
it('returns the correct response if the resource already exists.', async(): Promise<void> => { it('returns the correct response if the resource already exists.', async(): Promise<void> => {
store.resourceExists.mockResolvedValueOnce(true); store.hasResource.mockResolvedValueOnce(true);
const result = await handler.handle({ operation }); const result = await handler.handle({ operation });
expect(store.setRepresentation).toHaveBeenCalledTimes(1); expect(store.setRepresentation).toHaveBeenCalledTimes(1);
expect(store.setRepresentation).toHaveBeenLastCalledWith(operation.target, body, conditions); expect(store.setRepresentation).toHaveBeenLastCalledWith(operation.target, body, conditions);

View File

@ -20,7 +20,7 @@ describe('A GeneratedPodManager', (): void => {
}; };
store = { store = {
setRepresentation: jest.fn(), setRepresentation: jest.fn(),
resourceExists: jest.fn(), hasResource: jest.fn(),
} as any; } as any;
generatorData = [ generatorData = [
{ identifier: { path: '/path/' }, representation: '/' as any }, { identifier: { path: '/path/' }, representation: '/' as any },
@ -36,7 +36,7 @@ describe('A GeneratedPodManager', (): void => {
}); });
it('throws an error if the generate identifier is not available.', async(): Promise<void> => { it('throws an error if the generate identifier is not available.', async(): Promise<void> => {
store.resourceExists.mockResolvedValueOnce(true); store.hasResource.mockResolvedValueOnce(true);
const result = manager.createPod({ path: `${base}user/` }, settings, false); const result = manager.createPod({ path: `${base}user/` }, settings, false);
await expect(result).rejects.toThrow(`There already is a resource at ${base}user/`); await expect(result).rejects.toThrow(`There already is a resource at ${base}user/`);
await expect(result).rejects.toThrow(ConflictHttpError); await expect(result).rejects.toThrow(ConflictHttpError);
@ -52,7 +52,7 @@ describe('A GeneratedPodManager', (): void => {
}); });
it('allows overwriting when enabled.', async(): Promise<void> => { it('allows overwriting when enabled.', async(): Promise<void> => {
store.resourceExists.mockResolvedValueOnce(true); store.hasResource.mockResolvedValueOnce(true);
await expect(manager.createPod({ path: `${base}${settings.login}/` }, settings, true)).resolves.toBeUndefined(); await expect(manager.createPod({ path: `${base}${settings.login}/` }, settings, true)).resolves.toBeUndefined();
expect(store.setRepresentation).toHaveBeenCalledTimes(3); expect(store.setRepresentation).toHaveBeenCalledTimes(3);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -19,7 +19,7 @@ describe('A JsonResourceStorage', (): void => {
beforeEach(async(): Promise<void> => { beforeEach(async(): Promise<void> => {
const data: Record<string, string> = { }; const data: Record<string, string> = { };
store = { store = {
async resourceExists(identifier: ResourceIdentifier): Promise<boolean> { async hasResource(identifier: ResourceIdentifier): Promise<boolean> {
return Boolean(data[identifier.path]); return Boolean(data[identifier.path]);
}, },
async getRepresentation(identifier: ResourceIdentifier): Promise<Representation> { async getRepresentation(identifier: ResourceIdentifier): Promise<Representation> {

View File

@ -39,18 +39,18 @@ describe('A ConvertingRouterRule', (): void => {
}); });
it('checks if the stores contain the identifier if there is no data.', async(): Promise<void> => { it('checks if the stores contain the identifier if there is no data.', async(): Promise<void> => {
store1.resourceExists = jest.fn().mockImplementationOnce((): any => true); store1.hasResource = jest.fn().mockImplementationOnce((): any => true);
await expect(rule.handle({ identifier: { path: 'identifier' }})).resolves.toBe(store1); await expect(rule.handle({ identifier: { path: 'identifier' }})).resolves.toBe(store1);
expect(store1.resourceExists).toHaveBeenCalledTimes(1); expect(store1.hasResource).toHaveBeenCalledTimes(1);
}); });
it('returns the defaultStore if no other store has the resource.', async(): Promise<void> => { it('returns the defaultStore if no other store has the resource.', async(): Promise<void> => {
store1.resourceExists = jest.fn().mockImplementationOnce((): any => false); store1.hasResource = jest.fn().mockImplementationOnce((): any => false);
await expect(rule.handle({ identifier: { path: 'identifier' }})).resolves.toBe(defaultStore); 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> => { it('throws the error if a store had a non-404 error.', async(): Promise<void> => {
store1.resourceExists = jest.fn().mockRejectedValueOnce(new InternalServerError()); store1.hasResource = jest.fn().mockRejectedValueOnce(new InternalServerError());
await expect(rule.handle({ identifier: { path: 'identifier' }})).rejects.toThrow(InternalServerError); await expect(rule.handle({ identifier: { path: 'identifier' }})).rejects.toThrow(InternalServerError);
}); });
}); });