mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
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:
parent
e5b7d99da4
commit
b3f292d718
@ -22,5 +22,5 @@ module.exports = {
|
||||
'/test/',
|
||||
],
|
||||
// Slower machines had problems calling the WebSocket integration callbacks on time
|
||||
testTimeout: 10000,
|
||||
testTimeout: 15000,
|
||||
};
|
||||
|
@ -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}`);
|
||||
|
@ -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}`);
|
||||
|
@ -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}`);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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');
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -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' ]);
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user