refactor: Rename associated resource to subject resource

This commit is contained in:
Joachim Van Herwegen 2021-10-07 16:38:25 +02:00
parent 13c49045d4
commit 7c7fee5f5c
20 changed files with 93 additions and 93 deletions

View File

@ -8,8 +8,8 @@ import { PermissionReader } from './PermissionReader';
/** /**
* A PermissionReader for auxiliary resources such as acl or shape resources. * A PermissionReader for auxiliary resources such as acl or shape resources.
* The access permissions of an auxiliary resource depend on those of the resource it is associated with. * By default, the access permissions of an auxiliary resource depend on those of its subject resource.
* This authorizer calls the source authorizer with the identifier of the associated resource. * This authorizer calls the source authorizer with the identifier of the subject resource.
*/ */
export class AuxiliaryReader extends PermissionReader { export class AuxiliaryReader extends PermissionReader {
protected readonly logger = getLoggerFor(this); protected readonly logger = getLoggerFor(this);
@ -51,7 +51,7 @@ export class AuxiliaryReader extends PermissionReader {
return { return {
...auxiliaryAuth, ...auxiliaryAuth,
identifier: this.auxiliaryStrategy.getAssociatedIdentifier(auxiliaryAuth.identifier), identifier: this.auxiliaryStrategy.getSubjectIdentifier(auxiliaryAuth.identifier),
}; };
} }
} }

View File

@ -62,7 +62,7 @@ export class WebAclReader extends PermissionReader {
this.logger.debug(`Retrieving permissions of ${credentials.agent?.webId} for ${identifier.path}`); this.logger.debug(`Retrieving permissions of ${credentials.agent?.webId} for ${identifier.path}`);
const isAcl = this.aclStrategy.isAuxiliaryIdentifier(identifier); const isAcl = this.aclStrategy.isAuxiliaryIdentifier(identifier);
const mainIdentifier = isAcl ? this.aclStrategy.getAssociatedIdentifier(identifier) : identifier; const mainIdentifier = isAcl ? this.aclStrategy.getSubjectIdentifier(identifier) : identifier;
// Determine the full authorization for the agent granted by the applicable ACL // Determine the full authorization for the agent granted by the applicable ACL
const acl = await this.getAclRecursive(mainIdentifier); const acl = await this.getAclRecursive(mainIdentifier);

View File

@ -42,7 +42,7 @@ export interface AuxiliaryIdentifierStrategy {
* This does not guarantee that this resource exists. * This does not guarantee that this resource exists.
* @param identifier - Identifier of the auxiliary resource. * @param identifier - Identifier of the auxiliary resource.
* *
* @returns The ResourceIdentifier of the corresponding resource. * @returns The ResourceIdentifier of the subject resource.
*/ */
getAssociatedIdentifier: (identifier: ResourceIdentifier) => ResourceIdentifier; getSubjectIdentifier: (identifier: ResourceIdentifier) => ResourceIdentifier;
} }

View File

@ -10,7 +10,7 @@ import type { AuxiliaryIdentifierStrategy } from './AuxiliaryIdentifierStrategy'
*/ */
export interface AuxiliaryStrategy extends AuxiliaryIdentifierStrategy { export interface AuxiliaryStrategy extends AuxiliaryIdentifierStrategy {
/** /**
* Whether this auxiliary resources uses its own authorization instead of the associated resource authorization. * Whether this auxiliary resources uses its own authorization instead of the subject resource authorization.
* @param identifier - Identifier of the auxiliary resource. * @param identifier - Identifier of the auxiliary resource.
*/ */
usesOwnAuthorization: (identifier: ResourceIdentifier) => boolean; usesOwnAuthorization: (identifier: ResourceIdentifier) => boolean;
@ -25,7 +25,7 @@ export interface AuxiliaryStrategy extends AuxiliaryIdentifierStrategy {
/** /**
* Adds metadata related to this auxiliary resource, * Adds metadata related to this auxiliary resource,
* in case this is required for this type of auxiliary resource. * in case this is required for this type of auxiliary resource.
* The metadata that is added depends on the given identifier being an auxiliary or associated resource: * The metadata that is added depends on the given identifier being an auxiliary or subject resource:
* the metadata will be used to link to the other one, and potentially add extra typing info. * the metadata will be used to link to the other one, and potentially add extra typing info.
* *
* Used for: * Used for:
@ -33,7 +33,7 @@ export interface AuxiliaryStrategy extends AuxiliaryIdentifierStrategy {
* that resource MUST include an HTTP Link header pointing to the location of each auxiliary resource." * that resource MUST include an HTTP Link header pointing to the location of each auxiliary resource."
* https://solid.github.io/specification/protocol#auxiliary-resources-server * https://solid.github.io/specification/protocol#auxiliary-resources-server
* *
* The above is an example of how that metadata would only be added in case the input is the associated identifier. * The above is an example of how that metadata would only be added in case the input is the subject identifier.
* *
* @param metadata - Metadata to update. * @param metadata - Metadata to update.
*/ */

View File

@ -38,8 +38,8 @@ export class ComposedAuxiliaryStrategy implements AuxiliaryStrategy {
return this.identifierStrategy.isAuxiliaryIdentifier(identifier); return this.identifierStrategy.isAuxiliaryIdentifier(identifier);
} }
public getAssociatedIdentifier(identifier: ResourceIdentifier): ResourceIdentifier { public getSubjectIdentifier(identifier: ResourceIdentifier): ResourceIdentifier {
return this.identifierStrategy.getAssociatedIdentifier(identifier); return this.identifierStrategy.getSubjectIdentifier(identifier);
} }
public usesOwnAuthorization(): boolean { public usesOwnAuthorization(): boolean {

View File

@ -5,8 +5,8 @@ import type { AuxiliaryIdentifierStrategy } from './AuxiliaryIdentifierStrategy'
import { MetadataGenerator } from './MetadataGenerator'; import { MetadataGenerator } from './MetadataGenerator';
/** /**
* Adds a link to the auxiliary resource when called on the associated resource. * Adds a link to the auxiliary resource when called on the subject resource.
* Specifically: <associatedId> <link> <auxiliaryId> will be added. * Specifically: <subjectId> <link> <auxiliaryId> will be added.
* *
* In case the input is metadata of an auxiliary resource no metadata will be added * In case the input is metadata of an auxiliary resource no metadata will be added
*/ */

View File

@ -28,9 +28,9 @@ export class RoutingAuxiliaryIdentifierStrategy implements AuxiliaryIdentifierSt
return this.sources.some((source): boolean => source.isAuxiliaryIdentifier(identifier)); return this.sources.some((source): boolean => source.isAuxiliaryIdentifier(identifier));
} }
public getAssociatedIdentifier(identifier: ResourceIdentifier): ResourceIdentifier { public getSubjectIdentifier(identifier: ResourceIdentifier): ResourceIdentifier {
const source = this.getMatchingSource(identifier); const source = this.getMatchingSource(identifier);
return source.getAssociatedIdentifier(identifier); return source.getSubjectIdentifier(identifier);
} }
protected getMatchingSource(identifier: ResourceIdentifier): AuxiliaryIdentifierStrategy { protected getMatchingSource(identifier: ResourceIdentifier): AuxiliaryIdentifierStrategy {

View File

@ -8,7 +8,7 @@ import { RoutingAuxiliaryIdentifierStrategy } from './RoutingAuxiliaryIdentifier
* An {@link AuxiliaryStrategy} that combines multiple AuxiliaryStrategies into one. * An {@link AuxiliaryStrategy} that combines multiple AuxiliaryStrategies into one.
* Uses `isAuxiliaryIdentifier` to know which strategy to call for which call. * Uses `isAuxiliaryIdentifier` to know which strategy to call for which call.
* *
* `addMetadata` will either call all strategies if the input is the associated identifier, * `addMetadata` will either call all strategies if the input is the subject identifier,
* or only the matching strategy if the input is an auxiliary identifier. * or only the matching strategy if the input is an auxiliary identifier.
*/ */
export class RoutingAuxiliaryStrategy extends RoutingAuxiliaryIdentifierStrategy implements AuxiliaryStrategy { export class RoutingAuxiliaryStrategy extends RoutingAuxiliaryIdentifierStrategy implements AuxiliaryStrategy {

View File

@ -28,7 +28,7 @@ export class SuffixAuxiliaryIdentifierStrategy implements AuxiliaryIdentifierStr
return identifier.path.endsWith(this.suffix); return identifier.path.endsWith(this.suffix);
} }
public getAssociatedIdentifier(identifier: ResourceIdentifier): ResourceIdentifier { public getSubjectIdentifier(identifier: ResourceIdentifier): ResourceIdentifier {
if (!this.isAuxiliaryIdentifier(identifier)) { if (!this.isAuxiliaryIdentifier(identifier)) {
throw new InternalServerError(`${identifier.path} does not end on ${this.suffix} so no conversion is possible.`); throw new InternalServerError(`${identifier.path} does not end on ${this.suffix} so no conversion is possible.`);
} }

View File

@ -244,8 +244,8 @@ export class DataAccessorBasedStore implements ResourceStore {
} }
if (this.auxiliaryStrategy.isAuxiliaryIdentifier(identifier) && if (this.auxiliaryStrategy.isAuxiliaryIdentifier(identifier) &&
this.auxiliaryStrategy.isRequiredInRoot(identifier)) { this.auxiliaryStrategy.isRequiredInRoot(identifier)) {
const associatedIdentifier = this.auxiliaryStrategy.getAssociatedIdentifier(identifier); const subjectIdentifier = this.auxiliaryStrategy.getSubjectIdentifier(identifier);
const parentMetadata = await this.accessor.getMetadata(associatedIdentifier); const parentMetadata = await this.accessor.getMetadata(subjectIdentifier);
if (this.isRootStorage(parentMetadata)) { if (this.isRootStorage(parentMetadata)) {
throw new MethodNotAllowedHttpError(`Cannot delete ${identifier.path} from a root storage container.`); throw new MethodNotAllowedHttpError(`Cannot delete ${identifier.path} from a root storage container.`);
} }

View File

@ -17,7 +17,7 @@ import type { ResourceStore } from './ResourceStore';
* and releases it afterwards. * and releases it afterwards.
* In case the request returns a Representation the lock will only be released when the data stream is finished. * In case the request returns a Representation the lock will only be released when the data stream is finished.
* *
* For auxiliary resources the lock will be applied to the associated resource. * For auxiliary resources the lock will be applied to the subject resource.
* The actual operation is still executed on the auxiliary resource. * The actual operation is still executed on the auxiliary resource.
*/ */
export class LockingResourceStore implements AtomicResourceStore { export class LockingResourceStore implements AtomicResourceStore {
@ -71,11 +71,11 @@ export class LockingResourceStore implements AtomicResourceStore {
/** /**
* Acquires the correct identifier to lock this resource. * Acquires the correct identifier to lock this resource.
* For auxiliary resources this means the associated identifier. * For auxiliary resources this means the subject identifier.
*/ */
protected getLockIdentifier(identifier: ResourceIdentifier): ResourceIdentifier { protected getLockIdentifier(identifier: ResourceIdentifier): ResourceIdentifier {
return this.auxiliaryStrategy.isAuxiliaryIdentifier(identifier) ? return this.auxiliaryStrategy.isAuxiliaryIdentifier(identifier) ?
this.auxiliaryStrategy.getAssociatedIdentifier(identifier) : this.auxiliaryStrategy.getSubjectIdentifier(identifier) :
identifier; identifier;
} }

View File

@ -9,7 +9,7 @@ import { NotImplementedHttpError } from '../../../src/util/errors/NotImplemented
describe('An AuxiliaryReader', (): void => { describe('An AuxiliaryReader', (): void => {
const suffix = '.dummy'; const suffix = '.dummy';
const credentials = {}; const credentials = {};
const associatedIdentifier = { path: 'http://test.com/foo' }; const subjectIdentifier = { path: 'http://test.com/foo' };
const auxiliaryIdentifier = { path: 'http://test.com/foo.dummy' }; const auxiliaryIdentifier = { path: 'http://test.com/foo.dummy' };
const permissionSet: PermissionSet = { [CredentialGroup.agent]: { read: true }}; const permissionSet: PermissionSet = { [CredentialGroup.agent]: { read: true }};
let source: jest.Mocked<PermissionReader>; let source: jest.Mocked<PermissionReader>;
@ -25,20 +25,20 @@ describe('An AuxiliaryReader', (): void => {
strategy = { strategy = {
isAuxiliaryIdentifier: jest.fn((identifier: ResourceIdentifier): boolean => identifier.path.endsWith(suffix)), isAuxiliaryIdentifier: jest.fn((identifier: ResourceIdentifier): boolean => identifier.path.endsWith(suffix)),
getAssociatedIdentifier: jest.fn((identifier: ResourceIdentifier): ResourceIdentifier => getSubjectIdentifier: jest.fn((identifier: ResourceIdentifier): ResourceIdentifier =>
({ path: identifier.path.slice(0, -suffix.length) })), ({ path: identifier.path.slice(0, -suffix.length) })),
usesOwnAuthorization: jest.fn().mockReturnValue(false), usesOwnAuthorization: jest.fn().mockReturnValue(false),
} as any; } as any;
reader = new AuxiliaryReader(source, strategy); reader = new AuxiliaryReader(source, strategy);
}); });
it('can handle auxiliary resources if the source supports the associated resource.', async(): Promise<void> => { it('can handle auxiliary resources if the source supports the subject resource.', async(): Promise<void> => {
await expect(reader.canHandle({ identifier: auxiliaryIdentifier, credentials })) await expect(reader.canHandle({ identifier: auxiliaryIdentifier, credentials }))
.resolves.toBeUndefined(); .resolves.toBeUndefined();
expect(source.canHandle).toHaveBeenLastCalledWith( expect(source.canHandle).toHaveBeenLastCalledWith(
{ identifier: associatedIdentifier, credentials }, { identifier: subjectIdentifier, credentials },
); );
await expect(reader.canHandle({ identifier: associatedIdentifier, credentials })) await expect(reader.canHandle({ identifier: subjectIdentifier, credentials }))
.rejects.toThrow(NotImplementedHttpError); .rejects.toThrow(NotImplementedHttpError);
strategy.usesOwnAuthorization.mockReturnValueOnce(true); strategy.usesOwnAuthorization.mockReturnValueOnce(true);
@ -54,10 +54,10 @@ describe('An AuxiliaryReader', (): void => {
await expect(reader.handle({ identifier: auxiliaryIdentifier, credentials })) await expect(reader.handle({ identifier: auxiliaryIdentifier, credentials }))
.resolves.toBe(permissionSet); .resolves.toBe(permissionSet);
expect(source.handle).toHaveBeenLastCalledWith( expect(source.handle).toHaveBeenLastCalledWith(
{ identifier: associatedIdentifier, credentials }, { identifier: subjectIdentifier, credentials },
); );
// Safety checks are not present when calling `handle` // Safety checks are not present when calling `handle`
await expect(reader.handle({ identifier: associatedIdentifier, credentials })) await expect(reader.handle({ identifier: subjectIdentifier, credentials }))
.rejects.toThrow(NotImplementedHttpError); .rejects.toThrow(NotImplementedHttpError);
}); });
@ -65,10 +65,10 @@ describe('An AuxiliaryReader', (): void => {
await expect(reader.handleSafe({ identifier: auxiliaryIdentifier, credentials })) await expect(reader.handleSafe({ identifier: auxiliaryIdentifier, credentials }))
.resolves.toBe(permissionSet); .resolves.toBe(permissionSet);
expect(source.handleSafe).toHaveBeenLastCalledWith( expect(source.handleSafe).toHaveBeenLastCalledWith(
{ identifier: associatedIdentifier, credentials }, { identifier: subjectIdentifier, credentials },
); );
await expect(reader.handleSafe({ identifier: associatedIdentifier, credentials })) await expect(reader.handleSafe({ identifier: subjectIdentifier, credentials }))
.rejects.toThrow(NotImplementedHttpError); .rejects.toThrow(NotImplementedHttpError);
strategy.usesOwnAuthorization.mockReturnValueOnce(true); strategy.usesOwnAuthorization.mockReturnValueOnce(true);

View File

@ -25,7 +25,7 @@ describe('A WebAclReader', (): void => {
const aclStrategy: AuxiliaryIdentifierStrategy = { const aclStrategy: AuxiliaryIdentifierStrategy = {
getAuxiliaryIdentifier: (id: ResourceIdentifier): ResourceIdentifier => ({ path: `${id.path}.acl` }), getAuxiliaryIdentifier: (id: ResourceIdentifier): ResourceIdentifier => ({ path: `${id.path}.acl` }),
isAuxiliaryIdentifier: (id: ResourceIdentifier): boolean => id.path.endsWith('.acl'), isAuxiliaryIdentifier: (id: ResourceIdentifier): boolean => id.path.endsWith('.acl'),
getAssociatedIdentifier: (id: ResourceIdentifier): ResourceIdentifier => ({ path: id.path.slice(0, -4) }), getSubjectIdentifier: (id: ResourceIdentifier): ResourceIdentifier => ({ path: id.path.slice(0, -4) }),
} as any; } as any;
let store: jest.Mocked<ResourceStore>; let store: jest.Mocked<ResourceStore>;
const identifierStrategy = new SingleRootIdentifierStrategy('http://test.com/'); const identifierStrategy = new SingleRootIdentifierStrategy('http://test.com/');

View File

@ -15,7 +15,7 @@ describe('A ComposedAuxiliaryStrategy', (): void => {
identifierStrategy = { identifierStrategy = {
getAuxiliaryIdentifier: jest.fn(), getAuxiliaryIdentifier: jest.fn(),
getAuxiliaryIdentifiers: jest.fn(), getAuxiliaryIdentifiers: jest.fn(),
getAssociatedIdentifier: jest.fn(), getSubjectIdentifier: jest.fn(),
isAuxiliaryIdentifier: jest.fn(), isAuxiliaryIdentifier: jest.fn(),
}; };
metadataGenerator = { metadataGenerator = {
@ -36,9 +36,9 @@ describe('A ComposedAuxiliaryStrategy', (): void => {
expect(identifierStrategy.getAuxiliaryIdentifiers).toHaveBeenCalledTimes(1); expect(identifierStrategy.getAuxiliaryIdentifiers).toHaveBeenCalledTimes(1);
expect(identifierStrategy.getAuxiliaryIdentifiers).toHaveBeenLastCalledWith(identifier); expect(identifierStrategy.getAuxiliaryIdentifiers).toHaveBeenLastCalledWith(identifier);
strategy.getAssociatedIdentifier(identifier); strategy.getSubjectIdentifier(identifier);
expect(identifierStrategy.getAssociatedIdentifier).toHaveBeenCalledTimes(1); expect(identifierStrategy.getSubjectIdentifier).toHaveBeenCalledTimes(1);
expect(identifierStrategy.getAssociatedIdentifier).toHaveBeenLastCalledWith(identifier); expect(identifierStrategy.getSubjectIdentifier).toHaveBeenLastCalledWith(identifier);
strategy.isAuxiliaryIdentifier(identifier); strategy.isAuxiliaryIdentifier(identifier);
expect(identifierStrategy.isAuxiliaryIdentifier).toHaveBeenCalledTimes(1); expect(identifierStrategy.isAuxiliaryIdentifier).toHaveBeenCalledTimes(1);

View File

@ -6,7 +6,7 @@ import { SOLID_META } from '../../../../src/util/Vocabularies';
describe('A LinkMetadataGenerator', (): void => { describe('A LinkMetadataGenerator', (): void => {
const link = 'link'; const link = 'link';
const associatedId: ResourceIdentifier = { path: 'http://test.com/foo' }; const subjectId: ResourceIdentifier = { path: 'http://test.com/foo' };
const auxiliaryId: ResourceIdentifier = { path: 'http://test.com/foo.dummy' }; const auxiliaryId: ResourceIdentifier = { path: 'http://test.com/foo.dummy' };
let generator: LinkMetadataGenerator; let generator: LinkMetadataGenerator;
@ -15,7 +15,7 @@ describe('A LinkMetadataGenerator', (): void => {
getAuxiliaryIdentifier: (identifier: ResourceIdentifier): ResourceIdentifier => getAuxiliaryIdentifier: (identifier: ResourceIdentifier): ResourceIdentifier =>
({ path: `${identifier.path}.dummy` }), ({ path: `${identifier.path}.dummy` }),
isAuxiliaryIdentifier: (identifier: ResourceIdentifier): boolean => identifier.path.endsWith('.dummy'), isAuxiliaryIdentifier: (identifier: ResourceIdentifier): boolean => identifier.path.endsWith('.dummy'),
getAssociatedIdentifier: (identifier: ResourceIdentifier): ResourceIdentifier => getSubjectIdentifier: (identifier: ResourceIdentifier): ResourceIdentifier =>
({ path: identifier.path.slice(0, -'.dummy'.length) }), ({ path: identifier.path.slice(0, -'.dummy'.length) }),
} as AuxiliaryIdentifierStrategy; } as AuxiliaryIdentifierStrategy;
generator = new LinkMetadataGenerator(link, strategy); generator = new LinkMetadataGenerator(link, strategy);
@ -25,14 +25,14 @@ describe('A LinkMetadataGenerator', (): void => {
await expect(generator.canHandle(null as any)).resolves.toBeUndefined(); await expect(generator.canHandle(null as any)).resolves.toBeUndefined();
}); });
it('stores no metadata if the input is an associated resource.', async(): Promise<void> => { it('stores no metadata if the input is a subject resource.', async(): Promise<void> => {
const metadata = new RepresentationMetadata(auxiliaryId); const metadata = new RepresentationMetadata(auxiliaryId);
await expect(generator.handle(metadata)).resolves.toBeUndefined(); await expect(generator.handle(metadata)).resolves.toBeUndefined();
expect(metadata.quads()).toHaveLength(0); expect(metadata.quads()).toHaveLength(0);
}); });
it('uses the stored link to add metadata for associated resources.', async(): Promise<void> => { it('uses the stored link to add metadata for subject resources.', async(): Promise<void> => {
const metadata = new RepresentationMetadata(associatedId); const metadata = new RepresentationMetadata(subjectId);
await expect(generator.handle(metadata)).resolves.toBeUndefined(); await expect(generator.handle(metadata)).resolves.toBeUndefined();
expect(metadata.quads()).toHaveLength(1); expect(metadata.quads()).toHaveLength(1);
expect(metadata.get(link)?.value).toBe(auxiliaryId.path); expect(metadata.get(link)?.value).toBe(auxiliaryId.path);

View File

@ -23,7 +23,7 @@ class SimpleSuffixStrategy implements AuxiliaryIdentifierStrategy {
return identifier.path.endsWith(this.suffix); return identifier.path.endsWith(this.suffix);
} }
public getAssociatedIdentifier(identifier: ResourceIdentifier): ResourceIdentifier { public getSubjectIdentifier(identifier: ResourceIdentifier): ResourceIdentifier {
return { path: identifier.path.slice(0, -this.suffix.length) }; return { path: identifier.path.slice(0, -this.suffix.length) };
} }
} }
@ -58,9 +58,9 @@ describe('A RoutingAuxiliaryIdentifierStrategy', (): void => {
expect(strategy.isAuxiliaryIdentifier(dummy3Id)).toBe(false); expect(strategy.isAuxiliaryIdentifier(dummy3Id)).toBe(false);
}); });
it('#getAssociatedIdentifier returns the base id if a match is found.', async(): Promise<void> => { it('#getSubjectIdentifier returns the base id if a match is found.', async(): Promise<void> => {
expect(strategy.getAssociatedIdentifier(dummy1Id)).toEqual(baseId); expect(strategy.getSubjectIdentifier(dummy1Id)).toEqual(baseId);
expect(strategy.getAssociatedIdentifier(dummy2Id)).toEqual(baseId); expect(strategy.getSubjectIdentifier(dummy2Id)).toEqual(baseId);
expect((): any => strategy.getAssociatedIdentifier(dummy3Id)).toThrow(NotImplementedHttpError); expect((): any => strategy.getSubjectIdentifier(dummy3Id)).toThrow(NotImplementedHttpError);
}); });
}); });

View File

@ -27,7 +27,7 @@ class SimpleSuffixStrategy implements AuxiliaryStrategy {
return identifier.path.endsWith(this.suffix); return identifier.path.endsWith(this.suffix);
} }
public getAssociatedIdentifier(identifier: ResourceIdentifier): ResourceIdentifier { public getSubjectIdentifier(identifier: ResourceIdentifier): ResourceIdentifier {
return { path: identifier.path.slice(0, -this.suffix.length) }; return { path: identifier.path.slice(0, -this.suffix.length) };
} }

View File

@ -7,7 +7,7 @@ const suffix = '.dummy';
describe('A SuffixAuxiliaryManager', (): void => { describe('A SuffixAuxiliaryManager', (): void => {
let strategy: SuffixAuxiliaryIdentifierStrategy; let strategy: SuffixAuxiliaryIdentifierStrategy;
const associatedId: ResourceIdentifier = { path: 'http://test.com/foo' }; const subjectId: ResourceIdentifier = { path: 'http://test.com/foo' };
const auxiliaryId: ResourceIdentifier = { path: 'http://test.com/foo.dummy' }; const auxiliaryId: ResourceIdentifier = { path: 'http://test.com/foo.dummy' };
beforeEach(async(): Promise<void> => { beforeEach(async(): Promise<void> => {
@ -19,23 +19,23 @@ describe('A SuffixAuxiliaryManager', (): void => {
}); });
it('creates new identifiers by appending the suffix.', async(): Promise<void> => { it('creates new identifiers by appending the suffix.', async(): Promise<void> => {
expect(strategy.getAuxiliaryIdentifier(associatedId)).toEqual(auxiliaryId); expect(strategy.getAuxiliaryIdentifier(subjectId)).toEqual(auxiliaryId);
}); });
it('returns the same single identifier when requesting all of them.', async(): Promise<void> => { it('returns the same single identifier when requesting all of them.', async(): Promise<void> => {
expect(strategy.getAuxiliaryIdentifiers(associatedId)).toEqual([ auxiliaryId ]); expect(strategy.getAuxiliaryIdentifiers(subjectId)).toEqual([ auxiliaryId ]);
}); });
it('checks the suffix to determine if an identifier is auxiliary.', async(): Promise<void> => { it('checks the suffix to determine if an identifier is auxiliary.', async(): Promise<void> => {
expect(strategy.isAuxiliaryIdentifier(associatedId)).toBe(false); expect(strategy.isAuxiliaryIdentifier(subjectId)).toBe(false);
expect(strategy.isAuxiliaryIdentifier(auxiliaryId)).toBe(true); expect(strategy.isAuxiliaryIdentifier(auxiliaryId)).toBe(true);
}); });
it('errors when trying to get the associated id from a non-auxiliary identifier.', async(): Promise<void> => { it('errors when trying to get the subject id from a non-auxiliary identifier.', async(): Promise<void> => {
expect((): any => strategy.getAssociatedIdentifier(associatedId)).toThrow(InternalServerError); expect((): any => strategy.getSubjectIdentifier(subjectId)).toThrow(InternalServerError);
}); });
it('removes the suffix to create the associated identifier.', async(): Promise<void> => { it('removes the suffix to create the subject identifier.', async(): Promise<void> => {
expect(strategy.getAssociatedIdentifier(auxiliaryId)).toEqual(associatedId); expect(strategy.getSubjectIdentifier(auxiliaryId)).toEqual(subjectId);
}); });
}); });

View File

@ -107,7 +107,7 @@ class SimpleSuffixStrategy implements AuxiliaryStrategy {
return identifier.path.endsWith(this.suffix); return identifier.path.endsWith(this.suffix);
} }
public getAssociatedIdentifier(identifier: ResourceIdentifier): ResourceIdentifier { public getSubjectIdentifier(identifier: ResourceIdentifier): ResourceIdentifier {
return { path: identifier.path.slice(0, -this.suffix.length) }; return { path: identifier.path.slice(0, -this.suffix.length) };
} }

View File

@ -14,7 +14,7 @@ function emptyFn(): void {
describe('A LockingResourceStore', (): void => { describe('A LockingResourceStore', (): void => {
const auxiliaryId = { path: 'http://test.com/foo.dummy' }; const auxiliaryId = { path: 'http://test.com/foo.dummy' };
const associatedId = { path: 'http://test.com/foo' }; const subjectId = { path: 'http://test.com/foo' };
const data = { data: 'data!' } as any; const data = { data: 'data!' } as any;
let store: LockingResourceStore; let store: LockingResourceStore;
let locker: ExpiringReadWriteLocker; let locker: ExpiringReadWriteLocker;
@ -72,7 +72,7 @@ describe('A LockingResourceStore', (): void => {
auxiliaryStrategy = { auxiliaryStrategy = {
isAuxiliaryIdentifier: jest.fn((id: ResourceIdentifier): any => id.path.endsWith('.dummy')), isAuxiliaryIdentifier: jest.fn((id: ResourceIdentifier): any => id.path.endsWith('.dummy')),
getAssociatedIdentifier: jest.fn((id: ResourceIdentifier): any => ({ path: id.path.slice(0, -6) })), getSubjectIdentifier: jest.fn((id: ResourceIdentifier): any => ({ path: id.path.slice(0, -6) })),
} as any; } as any;
store = new LockingResourceStore(source, locker, auxiliaryStrategy); store = new LockingResourceStore(source, locker, auxiliaryStrategy);
@ -85,68 +85,68 @@ describe('A LockingResourceStore', (): void => {
} }
it('acquires a lock on the container when adding a representation.', async(): Promise<void> => { it('acquires a lock on the container when adding a representation.', async(): Promise<void> => {
await store.addResource(associatedId, data); await store.addResource(subjectId, data);
expect(locker.withWriteLock).toHaveBeenCalledTimes(1); expect(locker.withWriteLock).toHaveBeenCalledTimes(1);
expect((locker.withWriteLock as jest.Mock).mock.calls[0][0]).toEqual(associatedId); expect((locker.withWriteLock as jest.Mock).mock.calls[0][0]).toEqual(subjectId);
expect(source.addResource).toHaveBeenCalledTimes(1); expect(source.addResource).toHaveBeenCalledTimes(1);
expect(source.addResource).toHaveBeenLastCalledWith(associatedId, data, undefined); expect(source.addResource).toHaveBeenLastCalledWith(subjectId, data, undefined);
expect(order).toEqual([ 'lock write', 'addResource', 'unlock write' ]); expect(order).toEqual([ 'lock write', 'addResource', 'unlock write' ]);
order = []; order = [];
await expect(store.addResource(auxiliaryId, data)).resolves.toBeUndefined(); await expect(store.addResource(auxiliaryId, data)).resolves.toBeUndefined();
expect(locker.withWriteLock).toHaveBeenCalledTimes(2); expect(locker.withWriteLock).toHaveBeenCalledTimes(2);
expect((locker.withWriteLock as jest.Mock).mock.calls[1][0]).toEqual(associatedId); expect((locker.withWriteLock as jest.Mock).mock.calls[1][0]).toEqual(subjectId);
expect(source.addResource).toHaveBeenCalledTimes(2); expect(source.addResource).toHaveBeenCalledTimes(2);
expect(source.addResource).toHaveBeenLastCalledWith(auxiliaryId, data, undefined); expect(source.addResource).toHaveBeenLastCalledWith(auxiliaryId, data, undefined);
expect(order).toEqual([ 'lock write', 'addResource', 'unlock write' ]); expect(order).toEqual([ 'lock write', 'addResource', 'unlock write' ]);
}); });
it('acquires a lock on the resource when setting its representation.', async(): Promise<void> => { it('acquires a lock on the resource when setting its representation.', async(): Promise<void> => {
await store.setRepresentation(associatedId, data); await store.setRepresentation(subjectId, data);
expect(locker.withWriteLock).toHaveBeenCalledTimes(1); expect(locker.withWriteLock).toHaveBeenCalledTimes(1);
expect((locker.withWriteLock as jest.Mock).mock.calls[0][0]).toEqual(associatedId); expect((locker.withWriteLock as jest.Mock).mock.calls[0][0]).toEqual(subjectId);
expect(source.setRepresentation).toHaveBeenCalledTimes(1); expect(source.setRepresentation).toHaveBeenCalledTimes(1);
expect(source.setRepresentation).toHaveBeenLastCalledWith(associatedId, data, undefined); expect(source.setRepresentation).toHaveBeenLastCalledWith(subjectId, data, undefined);
expect(order).toEqual([ 'lock write', 'setRepresentation', 'unlock write' ]); expect(order).toEqual([ 'lock write', 'setRepresentation', 'unlock write' ]);
order = []; order = [];
await expect(store.setRepresentation(auxiliaryId, data)).resolves.toBeUndefined(); await expect(store.setRepresentation(auxiliaryId, data)).resolves.toBeUndefined();
expect(locker.withWriteLock).toHaveBeenCalledTimes(2); expect(locker.withWriteLock).toHaveBeenCalledTimes(2);
expect((locker.withWriteLock as jest.Mock).mock.calls[1][0]).toEqual(associatedId); expect((locker.withWriteLock as jest.Mock).mock.calls[1][0]).toEqual(subjectId);
expect(source.setRepresentation).toHaveBeenCalledTimes(2); expect(source.setRepresentation).toHaveBeenCalledTimes(2);
expect(source.setRepresentation).toHaveBeenLastCalledWith(auxiliaryId, data, undefined); expect(source.setRepresentation).toHaveBeenLastCalledWith(auxiliaryId, data, undefined);
expect(order).toEqual([ 'lock write', 'setRepresentation', 'unlock write' ]); expect(order).toEqual([ 'lock write', 'setRepresentation', 'unlock write' ]);
}); });
it('acquires a lock on the resource when deleting it.', async(): Promise<void> => { it('acquires a lock on the resource when deleting it.', async(): Promise<void> => {
await store.deleteResource(associatedId); await store.deleteResource(subjectId);
expect(locker.withWriteLock).toHaveBeenCalledTimes(1); expect(locker.withWriteLock).toHaveBeenCalledTimes(1);
expect((locker.withWriteLock as jest.Mock).mock.calls[0][0]).toEqual(associatedId); expect((locker.withWriteLock as jest.Mock).mock.calls[0][0]).toEqual(subjectId);
expect(source.deleteResource).toHaveBeenCalledTimes(1); expect(source.deleteResource).toHaveBeenCalledTimes(1);
expect(source.deleteResource).toHaveBeenLastCalledWith(associatedId, undefined); expect(source.deleteResource).toHaveBeenLastCalledWith(subjectId, undefined);
expect(order).toEqual([ 'lock write', 'deleteResource', 'unlock write' ]); expect(order).toEqual([ 'lock write', 'deleteResource', 'unlock write' ]);
order = []; order = [];
await expect(store.deleteResource(auxiliaryId)).resolves.toBeUndefined(); await expect(store.deleteResource(auxiliaryId)).resolves.toBeUndefined();
expect(locker.withWriteLock).toHaveBeenCalledTimes(2); expect(locker.withWriteLock).toHaveBeenCalledTimes(2);
expect((locker.withWriteLock as jest.Mock).mock.calls[1][0]).toEqual(associatedId); expect((locker.withWriteLock as jest.Mock).mock.calls[1][0]).toEqual(subjectId);
expect(source.deleteResource).toHaveBeenCalledTimes(2); expect(source.deleteResource).toHaveBeenCalledTimes(2);
expect(source.deleteResource).toHaveBeenLastCalledWith(auxiliaryId, undefined); expect(source.deleteResource).toHaveBeenLastCalledWith(auxiliaryId, undefined);
expect(order).toEqual([ 'lock write', 'deleteResource', 'unlock write' ]); expect(order).toEqual([ 'lock write', 'deleteResource', 'unlock write' ]);
}); });
it('acquires a lock on the resource when modifying its representation.', async(): Promise<void> => { it('acquires a lock on the resource when modifying its representation.', async(): Promise<void> => {
await store.modifyResource(associatedId, data as Patch); await store.modifyResource(subjectId, data as Patch);
expect(locker.withWriteLock).toHaveBeenCalledTimes(1); expect(locker.withWriteLock).toHaveBeenCalledTimes(1);
expect((locker.withWriteLock as jest.Mock).mock.calls[0][0]).toEqual(associatedId); expect((locker.withWriteLock as jest.Mock).mock.calls[0][0]).toEqual(subjectId);
expect(source.modifyResource).toHaveBeenCalledTimes(1); expect(source.modifyResource).toHaveBeenCalledTimes(1);
expect(source.modifyResource).toHaveBeenLastCalledWith(associatedId, data, undefined); expect(source.modifyResource).toHaveBeenLastCalledWith(subjectId, data, undefined);
expect(order).toEqual([ 'lock write', 'modifyResource', 'unlock write' ]); expect(order).toEqual([ 'lock write', 'modifyResource', 'unlock write' ]);
order = []; order = [];
await expect(store.modifyResource(auxiliaryId, data as Patch)).resolves.toBeUndefined(); await expect(store.modifyResource(auxiliaryId, data as Patch)).resolves.toBeUndefined();
expect(locker.withWriteLock).toHaveBeenCalledTimes(2); expect(locker.withWriteLock).toHaveBeenCalledTimes(2);
expect((locker.withWriteLock as jest.Mock).mock.calls[1][0]).toEqual(associatedId); expect((locker.withWriteLock as jest.Mock).mock.calls[1][0]).toEqual(subjectId);
expect(source.modifyResource).toHaveBeenCalledTimes(2); expect(source.modifyResource).toHaveBeenCalledTimes(2);
expect(source.modifyResource).toHaveBeenLastCalledWith(auxiliaryId, data, undefined); expect(source.modifyResource).toHaveBeenLastCalledWith(auxiliaryId, data, undefined);
expect(order).toEqual([ 'lock write', 'modifyResource', 'unlock write' ]); expect(order).toEqual([ 'lock write', 'modifyResource', 'unlock write' ]);
@ -157,15 +157,15 @@ describe('A LockingResourceStore', (): void => {
order.push('bad get'); order.push('bad get');
throw new Error('dummy'); throw new Error('dummy');
}; };
await expect(store.getRepresentation(associatedId, {})).rejects.toThrow('dummy'); await expect(store.getRepresentation(subjectId, {})).rejects.toThrow('dummy');
expect(locker.withReadLock).toHaveBeenCalledTimes(1); expect(locker.withReadLock).toHaveBeenCalledTimes(1);
expect((locker.withReadLock as jest.Mock).mock.calls[0][0]).toEqual(associatedId); expect((locker.withReadLock as jest.Mock).mock.calls[0][0]).toEqual(subjectId);
expect(order).toEqual([ 'lock read', 'bad get', 'unlock read' ]); expect(order).toEqual([ 'lock read', 'bad get', 'unlock read' ]);
}); });
it('releases the lock on the resource when data has been read.', async(): Promise<void> => { it('releases the lock on the resource when data has been read.', async(): Promise<void> => {
// Read all data from the representation // Read all data from the representation
const representation = await store.getRepresentation(associatedId, {}); const representation = await store.getRepresentation(subjectId, {});
representation.data.on('data', (): any => true); representation.data.on('data', (): any => true);
registerEventOrder(representation.data, 'end'); registerEventOrder(representation.data, 'end');
@ -174,13 +174,13 @@ describe('A LockingResourceStore', (): void => {
// Verify the lock was acquired and released at the right time // Verify the lock was acquired and released at the right time
expect(locker.withReadLock).toHaveBeenCalledTimes(1); expect(locker.withReadLock).toHaveBeenCalledTimes(1);
expect((locker.withReadLock as jest.Mock).mock.calls[0][0]).toEqual(associatedId); expect((locker.withReadLock as jest.Mock).mock.calls[0][0]).toEqual(subjectId);
expect(source.getRepresentation).toHaveBeenCalledTimes(1); expect(source.getRepresentation).toHaveBeenCalledTimes(1);
expect(source.getRepresentation).toHaveBeenLastCalledWith(associatedId, {}, undefined); expect(source.getRepresentation).toHaveBeenLastCalledWith(subjectId, {}, undefined);
expect(order).toEqual([ 'lock read', 'getRepresentation', 'end', 'unlock read' ]); expect(order).toEqual([ 'lock read', 'getRepresentation', 'end', 'unlock read' ]);
}); });
it('acquires the lock on the associated resource when reading an auxiliary resource.', async(): Promise<void> => { it('acquires the lock on the subject resource when reading an auxiliary resource.', async(): Promise<void> => {
// Read all data from the representation // Read all data from the representation
const representation = await store.getRepresentation(auxiliaryId, {}); const representation = await store.getRepresentation(auxiliaryId, {});
representation.data.on('data', (): any => true); representation.data.on('data', (): any => true);
@ -191,7 +191,7 @@ describe('A LockingResourceStore', (): void => {
// Verify the lock was acquired and released at the right time // Verify the lock was acquired and released at the right time
expect(locker.withReadLock).toHaveBeenCalledTimes(1); expect(locker.withReadLock).toHaveBeenCalledTimes(1);
expect((locker.withReadLock as jest.Mock).mock.calls[0][0]).toEqual(associatedId); expect((locker.withReadLock as jest.Mock).mock.calls[0][0]).toEqual(subjectId);
expect(source.getRepresentation).toHaveBeenCalledTimes(1); expect(source.getRepresentation).toHaveBeenCalledTimes(1);
expect(source.getRepresentation).toHaveBeenLastCalledWith(auxiliaryId, {}, undefined); expect(source.getRepresentation).toHaveBeenLastCalledWith(auxiliaryId, {}, undefined);
expect(order).toEqual([ 'lock read', 'getRepresentation', 'end', 'unlock read' ]); expect(order).toEqual([ 'lock read', 'getRepresentation', 'end', 'unlock read' ]);
@ -199,7 +199,7 @@ describe('A LockingResourceStore', (): void => {
it('destroys the resource and releases the lock when the readable errors.', async(): Promise<void> => { it('destroys the resource and releases the lock when the readable errors.', async(): Promise<void> => {
// Make the representation error // Make the representation error
const representation = await store.getRepresentation(associatedId, {}); const representation = await store.getRepresentation(subjectId, {});
setImmediate((): any => representation.data.emit('error', new Error('Error on the readable'))); setImmediate((): any => representation.data.emit('error', new Error('Error on the readable')));
registerEventOrder(representation.data, 'error'); registerEventOrder(representation.data, 'error');
registerEventOrder(representation.data, 'close'); registerEventOrder(representation.data, 'close');
@ -209,7 +209,7 @@ describe('A LockingResourceStore', (): void => {
// Verify the lock was acquired and released at the right time // Verify the lock was acquired and released at the right time
expect(locker.withReadLock).toHaveBeenCalledTimes(1); expect(locker.withReadLock).toHaveBeenCalledTimes(1);
expect((locker.withReadLock as jest.Mock).mock.calls[0][0]).toEqual(associatedId); expect((locker.withReadLock as jest.Mock).mock.calls[0][0]).toEqual(subjectId);
expect(source.getRepresentation).toHaveBeenCalledTimes(1); expect(source.getRepresentation).toHaveBeenCalledTimes(1);
expect(representation.data.destroy).toHaveBeenCalledTimes(1); expect(representation.data.destroy).toHaveBeenCalledTimes(1);
expect(order).toEqual([ 'lock read', 'getRepresentation', 'error', 'unlock read', 'close' ]); expect(order).toEqual([ 'lock read', 'getRepresentation', 'error', 'unlock read', 'close' ]);
@ -217,7 +217,7 @@ describe('A LockingResourceStore', (): void => {
it('releases the lock on the resource when readable is destroyed.', async(): Promise<void> => { it('releases the lock on the resource when readable is destroyed.', async(): Promise<void> => {
// Make the representation close // Make the representation close
const representation = await store.getRepresentation(associatedId, {}); const representation = await store.getRepresentation(subjectId, {});
representation.data.destroy(); representation.data.destroy();
registerEventOrder(representation.data, 'close'); registerEventOrder(representation.data, 'close');
@ -226,14 +226,14 @@ describe('A LockingResourceStore', (): void => {
// Verify the lock was acquired and released at the right time // Verify the lock was acquired and released at the right time
expect(locker.withReadLock).toHaveBeenCalledTimes(1); expect(locker.withReadLock).toHaveBeenCalledTimes(1);
expect((locker.withReadLock as jest.Mock).mock.calls[0][0]).toEqual(associatedId); expect((locker.withReadLock as jest.Mock).mock.calls[0][0]).toEqual(subjectId);
expect(source.getRepresentation).toHaveBeenCalledTimes(1); expect(source.getRepresentation).toHaveBeenCalledTimes(1);
expect(order).toEqual([ 'lock read', 'getRepresentation', 'close', 'unlock read' ]); expect(order).toEqual([ 'lock read', 'getRepresentation', 'close', 'unlock read' ]);
}); });
it('releases the lock only once when multiple events are triggered.', async(): Promise<void> => { it('releases the lock only once when multiple events are triggered.', async(): Promise<void> => {
// Read all data from the representation and trigger an additional close event // Read all data from the representation and trigger an additional close event
const representation = await store.getRepresentation(associatedId, {}); const representation = await store.getRepresentation(subjectId, {});
representation.data.on('data', (): any => true); representation.data.on('data', (): any => true);
representation.data.prependListener('end', (): any => { representation.data.prependListener('end', (): any => {
order.push('end'); order.push('end');
@ -245,13 +245,13 @@ describe('A LockingResourceStore', (): void => {
// Verify the lock was acquired and released at the right time // Verify the lock was acquired and released at the right time
expect(locker.withReadLock).toHaveBeenCalledTimes(1); expect(locker.withReadLock).toHaveBeenCalledTimes(1);
expect((locker.withReadLock as jest.Mock).mock.calls[0][0]).toEqual(associatedId); expect((locker.withReadLock as jest.Mock).mock.calls[0][0]).toEqual(subjectId);
expect(source.getRepresentation).toHaveBeenCalledTimes(1); expect(source.getRepresentation).toHaveBeenCalledTimes(1);
expect(order).toEqual([ 'lock read', 'getRepresentation', 'end', 'unlock read' ]); expect(order).toEqual([ 'lock read', 'getRepresentation', 'end', 'unlock read' ]);
}); });
it('releases the lock on the resource when readable times out.', async(): Promise<void> => { it('releases the lock on the resource when readable times out.', async(): Promise<void> => {
const representation = await store.getRepresentation(associatedId, {}); const representation = await store.getRepresentation(subjectId, {});
registerEventOrder(representation.data, 'close'); registerEventOrder(representation.data, 'close');
registerEventOrder(representation.data, 'error'); registerEventOrder(representation.data, 'error');
@ -262,7 +262,7 @@ describe('A LockingResourceStore', (): void => {
// Verify the lock was acquired and released at the right time // Verify the lock was acquired and released at the right time
expect(locker.withReadLock).toHaveBeenCalledTimes(1); expect(locker.withReadLock).toHaveBeenCalledTimes(1);
expect((locker.withReadLock as jest.Mock).mock.calls[0][0]).toEqual(associatedId); expect((locker.withReadLock as jest.Mock).mock.calls[0][0]).toEqual(subjectId);
expect(source.getRepresentation).toHaveBeenCalledTimes(1); expect(source.getRepresentation).toHaveBeenCalledTimes(1);
expect(representation.data.destroy).toHaveBeenCalledTimes(1); expect(representation.data.destroy).toHaveBeenCalledTimes(1);
expect(representation.data.destroy).toHaveBeenLastCalledWith(new Error('timeout')); expect(representation.data.destroy).toHaveBeenLastCalledWith(new Error('timeout'));
@ -276,23 +276,23 @@ describe('A LockingResourceStore', (): void => {
return new Promise(emptyFn); return new Promise(emptyFn);
}); });
const prom = store.getRepresentation(associatedId, {}); const prom = store.getRepresentation(subjectId, {});
timeoutTrigger.emit('timeout'); timeoutTrigger.emit('timeout');
await expect(prom).rejects.toThrow('timeout'); await expect(prom).rejects.toThrow('timeout');
expect(locker.withReadLock).toHaveBeenCalledTimes(1); expect(locker.withReadLock).toHaveBeenCalledTimes(1);
expect((locker.withReadLock as jest.Mock).mock.calls[0][0]).toEqual(associatedId); expect((locker.withReadLock as jest.Mock).mock.calls[0][0]).toEqual(subjectId);
expect(source.getRepresentation).toHaveBeenCalledTimes(1); expect(source.getRepresentation).toHaveBeenCalledTimes(1);
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('resourceExists should only acquire and release the read lock.', async(): Promise<void> => {
await store.resourceExists(associatedId); await store.resourceExists(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.resourceExists).toHaveBeenCalledTimes(1);
expect(source.resourceExists).toHaveBeenLastCalledWith(associatedId, undefined); expect(source.resourceExists).toHaveBeenLastCalledWith(subjectId, undefined);
expect(order).toEqual([ 'lock read', 'resourceExists', 'unlock read' ]); expect(order).toEqual([ 'lock read', 'resourceExists', 'unlock read' ]);
}); });
}); });