mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Make stores return modified resources.
This commit is contained in:
parent
28c0eb7e88
commit
6edc255707
@ -11,26 +11,28 @@ 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 addResource(container: ResourceIdentifier, representation: Representation,
|
|
||||||
conditions?: Conditions): Promise<ResourceIdentifier> {
|
|
||||||
throw new NotImplementedHttpError();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async deleteResource(identifier: ResourceIdentifier, conditions?: Conditions): Promise<void> {
|
|
||||||
throw new NotImplementedHttpError();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getRepresentation(identifier: ResourceIdentifier, preferences: RepresentationPreferences,
|
public async getRepresentation(identifier: ResourceIdentifier, preferences: RepresentationPreferences,
|
||||||
conditions?: Conditions): Promise<Representation> {
|
conditions?: Conditions): Promise<Representation> {
|
||||||
throw new NotImplementedHttpError();
|
throw new NotImplementedHttpError();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async modifyResource(identifier: ResourceIdentifier, patch: Patch, conditions?: Conditions): Promise<void> {
|
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
||||||
|
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||||
throw new NotImplementedHttpError();
|
throw new NotImplementedHttpError();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
public async addResource(container: ResourceIdentifier, representation: Representation,
|
||||||
conditions?: Conditions): Promise<void> {
|
conditions?: Conditions): Promise<ResourceIdentifier> {
|
||||||
|
throw new NotImplementedHttpError();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async deleteResource(identifier: ResourceIdentifier,
|
||||||
|
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||||
|
throw new NotImplementedHttpError();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
|
||||||
|
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||||
throw new NotImplementedHttpError();
|
throw new NotImplementedHttpError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,8 @@ export class DataAccessorBasedStore implements ResourceStore {
|
|||||||
return newID;
|
return newID;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation): Promise<void> {
|
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation):
|
||||||
|
Promise<ResourceIdentifier[]> {
|
||||||
this.validateIdentifier(identifier);
|
this.validateIdentifier(identifier);
|
||||||
|
|
||||||
// Ensure the representation is supported by the accessor
|
// Ensure the representation is supported by the accessor
|
||||||
@ -161,14 +162,14 @@ export class DataAccessorBasedStore implements ResourceStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Potentially have to create containers if it didn't exist yet
|
// Potentially have to create containers if it didn't exist yet
|
||||||
await this.writeData(identifier, representation, isContainer, !oldMetadata);
|
return this.writeData(identifier, representation, isContainer, !oldMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async modifyResource(): Promise<void> {
|
public async modifyResource(): Promise<ResourceIdentifier[]> {
|
||||||
throw new NotImplementedHttpError('Patches are not supported by the default store.');
|
throw new NotImplementedHttpError('Patches are not supported by the default store.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteResource(identifier: ResourceIdentifier): Promise<void> {
|
public async deleteResource(identifier: ResourceIdentifier): Promise<ResourceIdentifier[]> {
|
||||||
this.validateIdentifier(identifier);
|
this.validateIdentifier(identifier);
|
||||||
const metadata = await this.accessor.getMetadata(identifier);
|
const metadata = await this.accessor.getMetadata(identifier);
|
||||||
// Solid, §5.4: "When a DELETE request targets storage’s root container or its associated ACL resource,
|
// Solid, §5.4: "When a DELETE request targets storage’s root container or its associated ACL resource,
|
||||||
@ -199,11 +200,14 @@ export class DataAccessorBasedStore implements ResourceStore {
|
|||||||
// Solid, §5.4: "When a contained resource is deleted, the server MUST also delete the associated auxiliary
|
// Solid, §5.4: "When a contained resource is deleted, the server MUST also delete the associated auxiliary
|
||||||
// resources"
|
// resources"
|
||||||
// https://solid.github.io/specification/protocol#deleting-resources
|
// https://solid.github.io/specification/protocol#deleting-resources
|
||||||
|
const deleted = [];
|
||||||
if (!this.auxiliaryStrategy.isAuxiliaryIdentifier(identifier)) {
|
if (!this.auxiliaryStrategy.isAuxiliaryIdentifier(identifier)) {
|
||||||
await this.safelyDeleteAuxiliaryResources(this.auxiliaryStrategy.getAuxiliaryIdentifiers(identifier));
|
const auxiliaries = this.auxiliaryStrategy.getAuxiliaryIdentifiers(identifier);
|
||||||
|
deleted.push(...await this.safelyDeleteAuxiliaryResources(auxiliaries));
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.accessor.deleteResource(identifier);
|
deleted.unshift(...await this.accessor.deleteResource(identifier));
|
||||||
|
return deleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -265,9 +269,11 @@ export class DataAccessorBasedStore implements ResourceStore {
|
|||||||
* @param representation - Corresponding Representation.
|
* @param representation - Corresponding Representation.
|
||||||
* @param isContainer - Is the incoming resource a container?
|
* @param isContainer - Is the incoming resource a container?
|
||||||
* @param createContainers - Should parent containers (potentially) be created?
|
* @param createContainers - Should parent containers (potentially) be created?
|
||||||
|
*
|
||||||
|
* @returns Identifiers of resources that were possibly modified.
|
||||||
*/
|
*/
|
||||||
protected async writeData(identifier: ResourceIdentifier, representation: Representation, isContainer: boolean,
|
protected async writeData(identifier: ResourceIdentifier, representation: Representation, isContainer: boolean,
|
||||||
createContainers?: boolean): Promise<void> {
|
createContainers?: boolean): Promise<ResourceIdentifier[]> {
|
||||||
// Make sure the metadata has the correct identifier and correct type quads
|
// Make sure the metadata has the correct identifier and correct type quads
|
||||||
// Need to do this before handling container data to have the correct identifier
|
// Need to do this before handling container data to have the correct identifier
|
||||||
const { metadata } = representation;
|
const { metadata } = representation;
|
||||||
@ -288,13 +294,22 @@ export class DataAccessorBasedStore implements ResourceStore {
|
|||||||
// Solid, §5.3: "Servers MUST create intermediate containers and include corresponding containment triples
|
// Solid, §5.3: "Servers MUST create intermediate containers and include corresponding containment triples
|
||||||
// in container representations derived from the URI path component of PUT and PATCH requests."
|
// in container representations derived from the URI path component of PUT and PATCH requests."
|
||||||
// https://solid.github.io/specification/protocol#writing-resources
|
// https://solid.github.io/specification/protocol#writing-resources
|
||||||
if (createContainers && !this.identifierStrategy.isRootContainer(identifier)) {
|
const modified = [];
|
||||||
await this.createRecursiveContainers(this.identifierStrategy.getParentContainer(identifier));
|
if (!this.identifierStrategy.isRootContainer(identifier)) {
|
||||||
|
const container = this.identifierStrategy.getParentContainer(identifier);
|
||||||
|
if (!createContainers) {
|
||||||
|
modified.push(container);
|
||||||
|
} else {
|
||||||
|
const created = await this.createRecursiveContainers(container);
|
||||||
|
modified.push(...created.length === 0 ? [ container ] : created);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await (isContainer ?
|
await (isContainer ?
|
||||||
this.accessor.writeContainer(identifier, representation.metadata) :
|
this.accessor.writeContainer(identifier, representation.metadata) :
|
||||||
this.accessor.writeDocument(identifier, representation.data, representation.metadata));
|
this.accessor.writeDocument(identifier, representation.data, representation.metadata));
|
||||||
|
|
||||||
|
return [ ...modified, identifier ];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -442,10 +457,12 @@ export class DataAccessorBasedStore implements ResourceStore {
|
|||||||
* Deletes the given array of auxiliary identifiers.
|
* Deletes the given array of auxiliary identifiers.
|
||||||
* Does not throw an error if something goes wrong.
|
* Does not throw an error if something goes wrong.
|
||||||
*/
|
*/
|
||||||
protected async safelyDeleteAuxiliaryResources(identifiers: ResourceIdentifier[]): Promise<void[]> {
|
protected async safelyDeleteAuxiliaryResources(identifiers: ResourceIdentifier[]): Promise<ResourceIdentifier[]> {
|
||||||
return Promise.all(identifiers.map(async(identifier): Promise<void> => {
|
const deleted: ResourceIdentifier[] = [];
|
||||||
|
await Promise.all(identifiers.map(async(identifier): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
await this.accessor.deleteResource(identifier);
|
await this.accessor.deleteResource(identifier);
|
||||||
|
deleted.push(identifier);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (!NotFoundHttpError.isInstance(error)) {
|
if (!NotFoundHttpError.isInstance(error)) {
|
||||||
const errorMsg = isNativeError(error) ? error.message : error;
|
const errorMsg = isNativeError(error) ? error.message : error;
|
||||||
@ -453,6 +470,7 @@ export class DataAccessorBasedStore implements ResourceStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
return deleted;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -460,7 +478,8 @@ export class DataAccessorBasedStore implements ResourceStore {
|
|||||||
* Will throw errors if the identifier of the last existing "container" corresponds to an existing document.
|
* Will throw errors if the identifier of the last existing "container" corresponds to an existing document.
|
||||||
* @param container - Identifier of the container which will need to exist.
|
* @param container - Identifier of the container which will need to exist.
|
||||||
*/
|
*/
|
||||||
protected async createRecursiveContainers(container: ResourceIdentifier): Promise<void> {
|
protected async createRecursiveContainers(container: ResourceIdentifier): Promise<ResourceIdentifier[]> {
|
||||||
|
// Verify whether the container already exists
|
||||||
try {
|
try {
|
||||||
const metadata = await this.getNormalizedMetadata(container);
|
const metadata = await this.getNormalizedMetadata(container);
|
||||||
// See #480
|
// See #480
|
||||||
@ -471,16 +490,18 @@ export class DataAccessorBasedStore implements ResourceStore {
|
|||||||
if (!isContainerPath(metadata.identifier.value)) {
|
if (!isContainerPath(metadata.identifier.value)) {
|
||||||
throw new ForbiddenHttpError(`Creating container ${container.path} conflicts with an existing resource.`);
|
throw new ForbiddenHttpError(`Creating container ${container.path} conflicts with an existing resource.`);
|
||||||
}
|
}
|
||||||
|
return [];
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
if (NotFoundHttpError.isInstance(error)) {
|
if (!NotFoundHttpError.isInstance(error)) {
|
||||||
// Make sure the parent exists first
|
|
||||||
if (!this.identifierStrategy.isRootContainer(container)) {
|
|
||||||
await this.createRecursiveContainers(this.identifierStrategy.getParentContainer(container));
|
|
||||||
}
|
|
||||||
await this.writeData(container, new BasicRepresentation([], container), true);
|
|
||||||
} else {
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create the container, starting with its parent
|
||||||
|
const ancestors = this.identifierStrategy.isRootContainer(container) ?
|
||||||
|
[] :
|
||||||
|
await this.createRecursiveContainers(this.identifierStrategy.getParentContainer(container));
|
||||||
|
await this.writeData(container, new BasicRepresentation([], container), true);
|
||||||
|
return [ ...ancestors, container ];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,19 +48,21 @@ export class LockingResourceStore implements AtomicResourceStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
||||||
conditions?: Conditions): Promise<void> {
|
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||||
return this.locks.withWriteLock(this.getLockIdentifier(identifier),
|
return this.locks.withWriteLock(this.getLockIdentifier(identifier),
|
||||||
async(): Promise<void> => this.source.setRepresentation(identifier, representation, conditions));
|
async(): Promise<ResourceIdentifier[]> => this.source.setRepresentation(identifier, representation, conditions));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteResource(identifier: ResourceIdentifier, conditions?: Conditions): Promise<void> {
|
public async deleteResource(identifier: ResourceIdentifier,
|
||||||
|
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||||
return this.locks.withWriteLock(this.getLockIdentifier(identifier),
|
return this.locks.withWriteLock(this.getLockIdentifier(identifier),
|
||||||
async(): Promise<void> => this.source.deleteResource(identifier, conditions));
|
async(): Promise<ResourceIdentifier[]> => this.source.deleteResource(identifier, conditions));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async modifyResource(identifier: ResourceIdentifier, patch: Patch, conditions?: Conditions): Promise<void> {
|
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
|
||||||
|
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||||
return this.locks.withWriteLock(this.getLockIdentifier(identifier),
|
return this.locks.withWriteLock(this.getLockIdentifier(identifier),
|
||||||
async(): Promise<void> => this.source.modifyResource(identifier, patch, conditions));
|
async(): Promise<ResourceIdentifier[]> => this.source.modifyResource(identifier, patch, conditions));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,8 +33,9 @@ export class MonitoringStore<T extends ResourceStore = ResourceStore>
|
|||||||
return identifier;
|
return identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteResource(identifier: ResourceIdentifier, conditions?: Conditions): Promise<void> {
|
public async deleteResource(identifier: ResourceIdentifier,
|
||||||
await this.source.deleteResource(identifier, conditions);
|
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||||
|
const modified = await this.source.deleteResource(identifier, conditions);
|
||||||
|
|
||||||
// Both the container contents and the resource itself have changed
|
// Both the container contents and the resource itself have changed
|
||||||
if (!this.identifierStrategy.isRootContainer(identifier)) {
|
if (!this.identifierStrategy.isRootContainer(identifier)) {
|
||||||
@ -42,6 +43,8 @@ export class MonitoringStore<T extends ResourceStore = ResourceStore>
|
|||||||
this.emit('changed', container);
|
this.emit('changed', container);
|
||||||
}
|
}
|
||||||
this.emit('changed', identifier);
|
this.emit('changed', identifier);
|
||||||
|
|
||||||
|
return modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getRepresentation(identifier: ResourceIdentifier, preferences: RepresentationPreferences,
|
public async getRepresentation(identifier: ResourceIdentifier, preferences: RepresentationPreferences,
|
||||||
@ -49,14 +52,17 @@ export class MonitoringStore<T extends ResourceStore = ResourceStore>
|
|||||||
return this.source.getRepresentation(identifier, preferences, conditions);
|
return this.source.getRepresentation(identifier, preferences, conditions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async modifyResource(identifier: ResourceIdentifier, patch: Patch, conditions?: Conditions): Promise<void> {
|
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
|
||||||
await this.source.modifyResource(identifier, patch, conditions);
|
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||||
|
const modified = await this.source.modifyResource(identifier, patch, conditions);
|
||||||
this.emit('changed', identifier);
|
this.emit('changed', identifier);
|
||||||
|
return modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
||||||
conditions?: Conditions): Promise<void> {
|
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||||
await this.source.setRepresentation(identifier, representation, conditions);
|
const modified = await this.source.setRepresentation(identifier, representation, conditions);
|
||||||
this.emit('changed', identifier);
|
this.emit('changed', identifier);
|
||||||
|
return modified;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,8 @@ export class PassthroughStore<T extends ResourceStore = ResourceStore> implement
|
|||||||
return this.source.addResource(container, representation, conditions);
|
return this.source.addResource(container, representation, conditions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteResource(identifier: ResourceIdentifier, conditions?: Conditions): Promise<void> {
|
public async deleteResource(identifier: ResourceIdentifier,
|
||||||
|
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||||
return this.source.deleteResource(identifier, conditions);
|
return this.source.deleteResource(identifier, conditions);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,12 +32,13 @@ export class PassthroughStore<T extends ResourceStore = ResourceStore> implement
|
|||||||
return this.source.getRepresentation(identifier, preferences, conditions);
|
return this.source.getRepresentation(identifier, preferences, conditions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async modifyResource(identifier: ResourceIdentifier, patch: Patch, conditions?: Conditions): Promise<void> {
|
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
|
||||||
|
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||||
return this.source.modifyResource(identifier, patch, conditions);
|
return this.source.modifyResource(identifier, patch, conditions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
||||||
conditions?: Conditions): Promise<void> {
|
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||||
return this.source.setRepresentation(identifier, representation, conditions);
|
return this.source.setRepresentation(identifier, representation, conditions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,8 @@ export class PatchingStore<T extends ResourceStore = ResourceStore> extends Pass
|
|||||||
this.patcher = patcher;
|
this.patcher = patcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async modifyResource(identifier: ResourceIdentifier, patch: Patch, conditions?: Conditions): Promise<void> {
|
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
|
||||||
|
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||||
try {
|
try {
|
||||||
return await this.source.modifyResource(identifier, patch, conditions);
|
return await this.source.modifyResource(identifier, patch, conditions);
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -20,16 +20,18 @@ export class ReadOnlyStore<T extends ResourceStore = ResourceStore> extends Pass
|
|||||||
throw new ForbiddenHttpError();
|
throw new ForbiddenHttpError();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteResource(identifier: ResourceIdentifier, conditions?: Conditions): Promise<void> {
|
public async deleteResource(identifier: ResourceIdentifier,
|
||||||
|
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||||
throw new ForbiddenHttpError();
|
throw new ForbiddenHttpError();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async modifyResource(identifier: ResourceIdentifier, patch: Patch, conditions?: Conditions): Promise<void> {
|
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
|
||||||
|
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||||
throw new ForbiddenHttpError();
|
throw new ForbiddenHttpError();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
||||||
conditions?: Conditions): Promise<void> {
|
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||||
throw new ForbiddenHttpError();
|
throw new ForbiddenHttpError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ export class RepresentationConvertingStore<T extends ResourceStore = ResourceSto
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
||||||
conditions?: Conditions): Promise<void> {
|
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||||
representation = await this.inConverter.handleSafe({ identifier, representation, preferences: this.inPreferences });
|
representation = await this.inConverter.handleSafe({ identifier, representation, preferences: this.inPreferences });
|
||||||
return this.source.setRepresentation(identifier, representation, conditions);
|
return this.source.setRepresentation(identifier, representation, conditions);
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,12 @@ import type { Conditions } from './Conditions';
|
|||||||
*/
|
*/
|
||||||
export interface ResourceStore {
|
export interface ResourceStore {
|
||||||
/**
|
/**
|
||||||
* Read a resource.
|
* Retrieves a representation of a resource.
|
||||||
* @param identifier - Identifier of the resource to read.
|
* @param identifier - Identifier of the resource to read.
|
||||||
* @param preferences - Representation preferences.
|
* @param preferences - Preferences indicating desired representations.
|
||||||
* @param conditions - Optional conditions.
|
* @param conditions - Optional conditions under which to proceed.
|
||||||
*
|
*
|
||||||
* @returns A promise containing the representation.
|
* @returns A representation corresponding to the identifier.
|
||||||
*/
|
*/
|
||||||
getRepresentation: (
|
getRepresentation: (
|
||||||
identifier: ResourceIdentifier,
|
identifier: ResourceIdentifier,
|
||||||
@ -31,12 +31,27 @@ export interface ResourceStore {
|
|||||||
) => Promise<Representation>;
|
) => Promise<Representation>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a resource.
|
* Sets or replaces the representation of a resource,
|
||||||
|
* creating a new resource and intermediary containers as needed.
|
||||||
|
* @param identifier - Identifier of resource to update.
|
||||||
|
* @param representation - New representation of the resource.
|
||||||
|
* @param conditions - Optional conditions under which to proceed.
|
||||||
|
*
|
||||||
|
* @returns Identifiers of resources that were possibly modified.
|
||||||
|
*/
|
||||||
|
setRepresentation: (
|
||||||
|
identifier: ResourceIdentifier,
|
||||||
|
representation: Representation,
|
||||||
|
conditions?: Conditions,
|
||||||
|
) => Promise<ResourceIdentifier[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new resource in the container.
|
||||||
* @param container - Container in which to create a resource.
|
* @param container - Container in which to create a resource.
|
||||||
* @param representation - Representation of the new resource
|
* @param representation - Representation of the new resource
|
||||||
* @param conditions - Optional conditions.
|
* @param conditions - Optional conditions under which to proceed.
|
||||||
*
|
*
|
||||||
* @returns A promise containing the new identifier.
|
* @returns The identifier of the newly created resource.
|
||||||
*/
|
*/
|
||||||
addResource: (
|
addResource: (
|
||||||
container: ResourceIdentifier,
|
container: ResourceIdentifier,
|
||||||
@ -45,35 +60,29 @@ export interface ResourceStore {
|
|||||||
) => Promise<ResourceIdentifier>;
|
) => Promise<ResourceIdentifier>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fully update a resource.
|
* Deletes a resource.
|
||||||
* @param identifier - Identifier of resource to update.
|
|
||||||
* @param representation - New representation of the resource.
|
|
||||||
* @param conditions - Optional conditions.
|
|
||||||
*
|
|
||||||
* @returns A promise resolving when the update is finished.
|
|
||||||
*/
|
|
||||||
setRepresentation: (
|
|
||||||
identifier: ResourceIdentifier,
|
|
||||||
representation: Representation,
|
|
||||||
conditions?: Conditions,
|
|
||||||
) => Promise<void>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a resource.
|
|
||||||
* @param identifier - Identifier of resource to delete.
|
* @param identifier - Identifier of resource to delete.
|
||||||
* @param conditions - Optional conditions.
|
* @param conditions - Optional conditions under which to proceed.
|
||||||
*
|
*
|
||||||
* @returns A promise resolving when the delete is finished.
|
* @returns Identifiers of resources that were possibly modified.
|
||||||
*/
|
*/
|
||||||
deleteResource: (identifier: ResourceIdentifier, conditions?: Conditions) => Promise<void>;
|
deleteResource: (
|
||||||
|
identifier: ResourceIdentifier,
|
||||||
|
conditions?: Conditions,
|
||||||
|
) => Promise<ResourceIdentifier[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Partially update a resource.
|
* Sets or updates the representation of a resource,
|
||||||
|
* creating a new resource and intermediary containers as needed.
|
||||||
* @param identifier - Identifier of resource to update.
|
* @param identifier - Identifier of resource to update.
|
||||||
* @param patch - Description of which parts to update.
|
* @param patch - Description of which parts to update.
|
||||||
* @param conditions - Optional conditions.
|
* @param conditions - Optional conditions under which to proceed.
|
||||||
*
|
*
|
||||||
* @returns A promise resolving when the update is finished.
|
* @returns Identifiers of resources that were possibly modified.
|
||||||
*/
|
*/
|
||||||
modifyResource: (identifier: ResourceIdentifier, patch: Patch, conditions?: Conditions) => Promise<void>;
|
modifyResource: (
|
||||||
|
identifier: ResourceIdentifier,
|
||||||
|
patch: Patch,
|
||||||
|
conditions?: Conditions,
|
||||||
|
) => Promise<ResourceIdentifier[]>;
|
||||||
}
|
}
|
||||||
|
@ -31,16 +31,17 @@ export class RoutingResourceStore implements ResourceStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
||||||
conditions?: Conditions): Promise<void> {
|
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||||
return (await this.getStore(identifier, representation)).setRepresentation(identifier, representation, conditions);
|
return (await this.getStore(identifier, representation)).setRepresentation(identifier, representation, conditions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteResource(identifier: ResourceIdentifier, conditions?: Conditions): Promise<void> {
|
public async deleteResource(identifier: ResourceIdentifier,
|
||||||
|
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||||
return (await this.getStore(identifier)).deleteResource(identifier, conditions);
|
return (await this.getStore(identifier)).deleteResource(identifier, conditions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async modifyResource(identifier: ResourceIdentifier, patch: Patch, conditions?: Conditions):
|
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
|
||||||
Promise<void> {
|
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||||
return (await this.getStore(identifier)).modifyResource(identifier, patch, conditions);
|
return (await this.getStore(identifier)).modifyResource(identifier, patch, conditions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,6 +63,8 @@ export interface DataAccessor {
|
|||||||
* https://solid.github.io/specification/protocol#deleting-resources
|
* https://solid.github.io/specification/protocol#deleting-resources
|
||||||
*
|
*
|
||||||
* @param identifier - Resource to delete.
|
* @param identifier - Resource to delete.
|
||||||
|
*
|
||||||
|
* @returns Identifiers of resources that were possibly modified.
|
||||||
*/
|
*/
|
||||||
deleteResource: (identifier: ResourceIdentifier) => Promise<void>;
|
deleteResource: (identifier: ResourceIdentifier) => Promise<ResourceIdentifier[]>;
|
||||||
}
|
}
|
||||||
|
@ -117,12 +117,15 @@ export class FileDataAccessor implements DataAccessor {
|
|||||||
/**
|
/**
|
||||||
* Removes the corresponding file/folder (and metadata file).
|
* Removes the corresponding file/folder (and metadata file).
|
||||||
*/
|
*/
|
||||||
public async deleteResource(identifier: ResourceIdentifier): Promise<void> {
|
public async deleteResource(identifier: ResourceIdentifier): Promise<ResourceIdentifier[]> {
|
||||||
const link = await this.resourceMapper.mapUrlToFilePath(identifier);
|
const link = await this.resourceMapper.mapUrlToFilePath(identifier);
|
||||||
|
const metadataLink = await this.getMetadataLink(link.identifier);
|
||||||
const stats = await this.getStats(link.filePath);
|
const stats = await this.getStats(link.filePath);
|
||||||
|
const modified: ResourceIdentifier[] = [ identifier ];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await fsPromises.unlink((await this.getMetadataLink(link.identifier)).filePath);
|
await fsPromises.unlink(metadataLink.filePath);
|
||||||
|
modified.push(metadataLink.identifier);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
// Ignore if it doesn't exist
|
// Ignore if it doesn't exist
|
||||||
if (!isSystemError(error) || error.code !== 'ENOENT') {
|
if (!isSystemError(error) || error.code !== 'ENOENT') {
|
||||||
@ -137,6 +140,8 @@ export class FileDataAccessor implements DataAccessor {
|
|||||||
} else {
|
} else {
|
||||||
throw new NotFoundHttpError();
|
throw new NotFoundHttpError();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return modified;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -83,13 +83,14 @@ export class InMemoryDataAccessor implements DataAccessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteResource(identifier: ResourceIdentifier): Promise<void> {
|
public async deleteResource(identifier: ResourceIdentifier): Promise<ResourceIdentifier[]> {
|
||||||
const { parent, name } = this.getParentEntry(identifier);
|
const { parent, name } = this.getParentEntry(identifier);
|
||||||
if (!parent.entries[name]) {
|
if (!parent.entries[name]) {
|
||||||
throw new NotFoundHttpError();
|
throw new NotFoundHttpError();
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||||
delete parent.entries[name];
|
delete parent.entries[name];
|
||||||
|
return [ identifier ];
|
||||||
}
|
}
|
||||||
|
|
||||||
private isDataEntry(entry: CacheEntry): entry is DataEntry {
|
private isDataEntry(entry: CacheEntry): entry is DataEntry {
|
||||||
|
@ -134,9 +134,10 @@ export class SparqlDataAccessor implements DataAccessor {
|
|||||||
/**
|
/**
|
||||||
* Removes all graph data relevant to the given identifier.
|
* Removes all graph data relevant to the given identifier.
|
||||||
*/
|
*/
|
||||||
public async deleteResource(identifier: ResourceIdentifier): Promise<void> {
|
public async deleteResource(identifier: ResourceIdentifier): Promise<ResourceIdentifier[]> {
|
||||||
const { name, parent } = this.getRelatedNames(identifier);
|
const { name, parent } = this.getRelatedNames(identifier);
|
||||||
return this.sendSparqlUpdate(this.sparqlDelete(name, parent));
|
await this.sendSparqlUpdate(this.sparqlDelete(name, parent));
|
||||||
|
return [ identifier ];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,4 +2,5 @@ import type { Patch } from '../../ldp/http/Patch';
|
|||||||
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
|
import type { ResourceIdentifier } from '../../ldp/representation/ResourceIdentifier';
|
||||||
import { AsyncHandler } from '../../util/handlers/AsyncHandler';
|
import { AsyncHandler } from '../../util/handlers/AsyncHandler';
|
||||||
|
|
||||||
export abstract class PatchHandler extends AsyncHandler<{identifier: ResourceIdentifier; patch: Patch}> {}
|
export abstract class PatchHandler
|
||||||
|
extends AsyncHandler<{identifier: ResourceIdentifier; patch: Patch}, ResourceIdentifier[]> {}
|
||||||
|
@ -34,13 +34,15 @@ export class SparqlUpdatePatchHandler extends PatchHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async handle(input: {identifier: ResourceIdentifier; patch: SparqlUpdatePatch}): Promise<void> {
|
public async handle(input: {identifier: ResourceIdentifier; patch: SparqlUpdatePatch}):
|
||||||
|
Promise<ResourceIdentifier[]> {
|
||||||
// Verify the patch
|
// Verify the patch
|
||||||
const { identifier, patch } = input;
|
const { identifier, patch } = input;
|
||||||
const op = patch.algebra;
|
const op = patch.algebra;
|
||||||
this.validateUpdate(op);
|
this.validateUpdate(op);
|
||||||
|
|
||||||
await this.applyPatch(identifier, op);
|
await this.applyPatch(identifier, op);
|
||||||
|
return [ identifier ];
|
||||||
}
|
}
|
||||||
|
|
||||||
private isDeleteInsert(op: Algebra.Operation): op is Algebra.DeleteInsert {
|
private isDeleteInsert(op: Algebra.Operation): op is Algebra.DeleteInsert {
|
||||||
|
@ -7,7 +7,7 @@ describe('A DeleteOperationHandler', (): void => {
|
|||||||
const store = {} as unknown as ResourceStore;
|
const store = {} as unknown as ResourceStore;
|
||||||
const handler = new DeleteOperationHandler(store);
|
const handler = new DeleteOperationHandler(store);
|
||||||
beforeEach(async(): Promise<void> => {
|
beforeEach(async(): Promise<void> => {
|
||||||
store.deleteResource = jest.fn(async(): Promise<void> => undefined);
|
store.deleteResource = jest.fn(async(): Promise<any> => undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('only supports DELETE operations.', async(): Promise<void> => {
|
it('only supports DELETE operations.', async(): Promise<void> => {
|
||||||
|
@ -9,7 +9,7 @@ describe('A PatchOperationHandler', (): void => {
|
|||||||
const store = {} as unknown as ResourceStore;
|
const store = {} as unknown as ResourceStore;
|
||||||
const handler = new PatchOperationHandler(store);
|
const handler = new PatchOperationHandler(store);
|
||||||
beforeEach(async(): Promise<void> => {
|
beforeEach(async(): Promise<void> => {
|
||||||
store.modifyResource = jest.fn(async(): Promise<void> => undefined);
|
store.modifyResource = jest.fn(async(): Promise<any> => undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('only supports PATCH operations.', async(): Promise<void> => {
|
it('only supports PATCH operations.', async(): Promise<void> => {
|
||||||
|
@ -10,7 +10,7 @@ describe('A PutOperationHandler', (): void => {
|
|||||||
const handler = new PutOperationHandler(store);
|
const handler = new PutOperationHandler(store);
|
||||||
beforeEach(async(): Promise<void> => {
|
beforeEach(async(): Promise<void> => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
store.setRepresentation = jest.fn(async(): Promise<void> => {});
|
store.setRepresentation = jest.fn(async(): Promise<any> => {});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('only supports PUT operations.', async(): Promise<void> => {
|
it('only supports PUT operations.', async(): Promise<void> => {
|
||||||
|
@ -40,11 +40,11 @@ class SimpleDataAccessor implements DataAccessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteResource(identifier: ResourceIdentifier): Promise<void> {
|
public async deleteResource(identifier: ResourceIdentifier): Promise<ResourceIdentifier[]> {
|
||||||
this.checkExists(identifier);
|
this.checkExists(identifier);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||||
delete this.data[identifier.path];
|
delete this.data[identifier.path];
|
||||||
return undefined;
|
return [ identifier ];
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getData(identifier: ResourceIdentifier): Promise<Guarded<Readable>> {
|
public async getData(identifier: ResourceIdentifier): Promise<Guarded<Readable>> {
|
||||||
@ -353,8 +353,8 @@ describe('A DataAccessorBasedStore', (): void => {
|
|||||||
representation.metadata.contentType = 'text/turtle';
|
representation.metadata.contentType = 'text/turtle';
|
||||||
representation.data = guardedStreamFrom([ `<${`${root}`}> a <coolContainer>.` ]);
|
representation.data = guardedStreamFrom([ `<${`${root}`}> a <coolContainer>.` ]);
|
||||||
|
|
||||||
await expect(store.setRepresentation(resourceID, representation))
|
await expect(store.setRepresentation(resourceID, representation)).resolves
|
||||||
.resolves.toBeUndefined();
|
.toEqual([{ path: `${root}` }]);
|
||||||
expect(mock).toHaveBeenCalledTimes(1);
|
expect(mock).toHaveBeenCalledTimes(1);
|
||||||
expect(mock).toHaveBeenLastCalledWith(resourceID);
|
expect(mock).toHaveBeenLastCalledWith(resourceID);
|
||||||
|
|
||||||
@ -383,7 +383,10 @@ describe('A DataAccessorBasedStore', (): void => {
|
|||||||
|
|
||||||
it('can write resources.', async(): Promise<void> => {
|
it('can write resources.', async(): Promise<void> => {
|
||||||
const resourceID = { path: `${root}resource` };
|
const resourceID = { path: `${root}resource` };
|
||||||
await expect(store.setRepresentation(resourceID, representation)).resolves.toBeUndefined();
|
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
|
||||||
|
{ path: 'http://test.com/' },
|
||||||
|
{ path: 'http://test.com/resource' },
|
||||||
|
]);
|
||||||
await expect(arrayifyStream(accessor.data[resourceID.path].data)).resolves.toEqual([ resourceData ]);
|
await expect(arrayifyStream(accessor.data[resourceID.path].data)).resolves.toEqual([ resourceData ]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -394,7 +397,10 @@ describe('A DataAccessorBasedStore', (): void => {
|
|||||||
representation.metadata.removeAll(RDF.type);
|
representation.metadata.removeAll(RDF.type);
|
||||||
representation.metadata.contentType = 'text/turtle';
|
representation.metadata.contentType = 'text/turtle';
|
||||||
representation.data = guardedStreamFrom([ `<${`${root}resource/`}> a <coolContainer>.` ]);
|
representation.data = guardedStreamFrom([ `<${`${root}resource/`}> a <coolContainer>.` ]);
|
||||||
await expect(store.setRepresentation(resourceID, representation)).resolves.toBeUndefined();
|
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
|
||||||
|
{ path: `${root}` },
|
||||||
|
{ path: `${root}container/` },
|
||||||
|
]);
|
||||||
expect(accessor.data[resourceID.path]).toBeTruthy();
|
expect(accessor.data[resourceID.path]).toBeTruthy();
|
||||||
expect(accessor.data[resourceID.path].metadata.contentType).toBeUndefined();
|
expect(accessor.data[resourceID.path].metadata.contentType).toBeUndefined();
|
||||||
});
|
});
|
||||||
@ -403,7 +409,10 @@ describe('A DataAccessorBasedStore', (): void => {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||||
delete accessor.data[root];
|
delete accessor.data[root];
|
||||||
const resourceID = { path: `${root}resource` };
|
const resourceID = { path: `${root}resource` };
|
||||||
await expect(store.setRepresentation(resourceID, representation)).resolves.toBeUndefined();
|
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
|
||||||
|
{ path: `${root}` },
|
||||||
|
{ path: `${root}resource` },
|
||||||
|
]);
|
||||||
await expect(arrayifyStream(accessor.data[resourceID.path].data)).resolves.toEqual([ resourceData ]);
|
await expect(arrayifyStream(accessor.data[resourceID.path].data)).resolves.toEqual([ resourceData ]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -416,7 +425,10 @@ describe('A DataAccessorBasedStore', (): void => {
|
|||||||
representation.data = guardedStreamFrom(
|
representation.data = guardedStreamFrom(
|
||||||
[ quad(namedNode(`${root}resource/`), namedNode('a'), namedNode('coolContainer')) ],
|
[ quad(namedNode(`${root}resource/`), namedNode('a'), namedNode('coolContainer')) ],
|
||||||
);
|
);
|
||||||
await expect(store.setRepresentation(resourceID, representation)).resolves.toBeUndefined();
|
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
|
||||||
|
{ path: `${root}` },
|
||||||
|
{ path: `${root}container/` },
|
||||||
|
]);
|
||||||
expect(accessor.data[resourceID.path]).toBeTruthy();
|
expect(accessor.data[resourceID.path]).toBeTruthy();
|
||||||
expect(accessor.data[resourceID.path].metadata.contentType).toBeUndefined();
|
expect(accessor.data[resourceID.path].metadata.contentType).toBeUndefined();
|
||||||
});
|
});
|
||||||
@ -436,7 +448,11 @@ describe('A DataAccessorBasedStore', (): void => {
|
|||||||
|
|
||||||
it('creates recursive containers when needed.', async(): Promise<void> => {
|
it('creates recursive containers when needed.', async(): Promise<void> => {
|
||||||
const resourceID = { path: `${root}a/b/resource` };
|
const resourceID = { path: `${root}a/b/resource` };
|
||||||
await expect(store.setRepresentation(resourceID, representation)).resolves.toBeUndefined();
|
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
|
||||||
|
{ path: `${root}a/` },
|
||||||
|
{ path: `${root}a/b/` },
|
||||||
|
{ path: `${root}a/b/resource` },
|
||||||
|
]);
|
||||||
await expect(arrayifyStream(accessor.data[resourceID.path].data)).resolves.toEqual([ resourceData ]);
|
await expect(arrayifyStream(accessor.data[resourceID.path].data)).resolves.toEqual([ resourceData ]);
|
||||||
expect(accessor.data[`${root}a/`].metadata.getAll(RDF.type).map((type): string => type.value))
|
expect(accessor.data[`${root}a/`].metadata.getAll(RDF.type).map((type): string => type.value))
|
||||||
.toContain(LDP.Container);
|
.toContain(LDP.Container);
|
||||||
@ -461,7 +477,9 @@ describe('A DataAccessorBasedStore', (): void => {
|
|||||||
representation.metadata.removeAll(RDF.type);
|
representation.metadata.removeAll(RDF.type);
|
||||||
representation.metadata.contentType = 'text/turtle';
|
representation.metadata.contentType = 'text/turtle';
|
||||||
representation.data = guardedStreamFrom([]);
|
representation.data = guardedStreamFrom([]);
|
||||||
await expect(store.setRepresentation(resourceID, representation)).resolves.toBeUndefined();
|
await expect(store.setRepresentation(resourceID, representation)).resolves.toEqual([
|
||||||
|
{ path: `${root}` },
|
||||||
|
]);
|
||||||
expect(accessor.data[resourceID.path]).toBeTruthy();
|
expect(accessor.data[resourceID.path]).toBeTruthy();
|
||||||
expect(Object.keys(accessor.data)).toHaveLength(1);
|
expect(Object.keys(accessor.data)).toHaveLength(1);
|
||||||
expect(accessor.data[resourceID.path].metadata.contentType).toBeUndefined();
|
expect(accessor.data[resourceID.path].metadata.contentType).toBeUndefined();
|
||||||
@ -513,7 +531,9 @@ describe('A DataAccessorBasedStore', (): void => {
|
|||||||
|
|
||||||
it('will delete resources.', async(): Promise<void> => {
|
it('will delete resources.', async(): Promise<void> => {
|
||||||
accessor.data[`${root}resource`] = representation;
|
accessor.data[`${root}resource`] = representation;
|
||||||
await expect(store.deleteResource({ path: `${root}resource` })).resolves.toBeUndefined();
|
await expect(store.deleteResource({ path: `${root}resource` })).resolves.toEqual([
|
||||||
|
{ path: `${root}resource` },
|
||||||
|
]);
|
||||||
expect(accessor.data[`${root}resource`]).toBeUndefined();
|
expect(accessor.data[`${root}resource`]).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -522,14 +542,19 @@ describe('A DataAccessorBasedStore', (): void => {
|
|||||||
accessor.data[`${root}container/`] = new BasicRepresentation(representation.data, storageMetadata);
|
accessor.data[`${root}container/`] = new BasicRepresentation(representation.data, storageMetadata);
|
||||||
accessor.data[`${root}container/.dummy`] = representation;
|
accessor.data[`${root}container/.dummy`] = representation;
|
||||||
auxStrategy.isRootRequired = jest.fn().mockReturnValue(true);
|
auxStrategy.isRootRequired = jest.fn().mockReturnValue(true);
|
||||||
await expect(store.deleteResource({ path: `${root}container/.dummy` })).resolves.toBeUndefined();
|
await expect(store.deleteResource({ path: `${root}container/.dummy` })).resolves.toEqual([
|
||||||
|
{ path: `${root}container/.dummy` },
|
||||||
|
]);
|
||||||
expect(accessor.data[`${root}container/.dummy`]).toBeUndefined();
|
expect(accessor.data[`${root}container/.dummy`]).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('will delete related auxiliary resources.', async(): Promise<void> => {
|
it('will delete related auxiliary resources.', async(): Promise<void> => {
|
||||||
accessor.data[`${root}container/`] = representation;
|
accessor.data[`${root}container/`] = representation;
|
||||||
accessor.data[`${root}container/.dummy`] = representation;
|
accessor.data[`${root}container/.dummy`] = representation;
|
||||||
await expect(store.deleteResource({ path: `${root}container/` })).resolves.toBeUndefined();
|
await expect(store.deleteResource({ path: `${root}container/` })).resolves.toEqual([
|
||||||
|
{ path: `${root}container/` },
|
||||||
|
{ path: `${root}container/.dummy` },
|
||||||
|
]);
|
||||||
expect(accessor.data[`${root}container/`]).toBeUndefined();
|
expect(accessor.data[`${root}container/`]).toBeUndefined();
|
||||||
expect(accessor.data[`${root}container/.dummy`]).toBeUndefined();
|
expect(accessor.data[`${root}container/.dummy`]).toBeUndefined();
|
||||||
});
|
});
|
||||||
@ -538,15 +563,18 @@ describe('A DataAccessorBasedStore', (): void => {
|
|||||||
accessor.data[`${root}resource`] = representation;
|
accessor.data[`${root}resource`] = representation;
|
||||||
accessor.data[`${root}resource.dummy`] = representation;
|
accessor.data[`${root}resource.dummy`] = representation;
|
||||||
const deleteFn = accessor.deleteResource;
|
const deleteFn = accessor.deleteResource;
|
||||||
accessor.deleteResource = jest.fn(async(identifier: ResourceIdentifier): Promise<void> => {
|
accessor.deleteResource = jest.fn(async(identifier: ResourceIdentifier): Promise<ResourceIdentifier[]> => {
|
||||||
if (auxStrategy.isAuxiliaryIdentifier(identifier)) {
|
if (auxStrategy.isAuxiliaryIdentifier(identifier)) {
|
||||||
throw new Error('auxiliary error!');
|
throw new Error('auxiliary error!');
|
||||||
}
|
}
|
||||||
await deleteFn.call(accessor, identifier);
|
await deleteFn.call(accessor, identifier);
|
||||||
|
return [ identifier ];
|
||||||
});
|
});
|
||||||
const { logger } = store as any;
|
const { logger } = store as any;
|
||||||
logger.error = jest.fn();
|
logger.error = jest.fn();
|
||||||
await expect(store.deleteResource({ path: `${root}resource` })).resolves.toBeUndefined();
|
await expect(store.deleteResource({ path: `${root}resource` })).resolves.toEqual([
|
||||||
|
{ path: `${root}resource` },
|
||||||
|
]);
|
||||||
expect(accessor.data[`${root}resource`]).toBeUndefined();
|
expect(accessor.data[`${root}resource`]).toBeUndefined();
|
||||||
expect(accessor.data[`${root}resource.dummy`]).not.toBeUndefined();
|
expect(accessor.data[`${root}resource.dummy`]).not.toBeUndefined();
|
||||||
expect(logger.error).toHaveBeenCalledTimes(1);
|
expect(logger.error).toHaveBeenCalledTimes(1);
|
||||||
@ -559,15 +587,18 @@ describe('A DataAccessorBasedStore', (): void => {
|
|||||||
accessor.data[`${root}resource`] = representation;
|
accessor.data[`${root}resource`] = representation;
|
||||||
accessor.data[`${root}resource.dummy`] = representation;
|
accessor.data[`${root}resource.dummy`] = representation;
|
||||||
const deleteFn = accessor.deleteResource;
|
const deleteFn = accessor.deleteResource;
|
||||||
accessor.deleteResource = jest.fn(async(identifier: ResourceIdentifier): Promise<void> => {
|
accessor.deleteResource = jest.fn(async(identifier: ResourceIdentifier): Promise<ResourceIdentifier[]> => {
|
||||||
if (auxStrategy.isAuxiliaryIdentifier(identifier)) {
|
if (auxStrategy.isAuxiliaryIdentifier(identifier)) {
|
||||||
throw 'auxiliary error!';
|
throw 'auxiliary error!';
|
||||||
}
|
}
|
||||||
await deleteFn.call(accessor, identifier);
|
await deleteFn.call(accessor, identifier);
|
||||||
|
return [ identifier ];
|
||||||
});
|
});
|
||||||
const { logger } = store as any;
|
const { logger } = store as any;
|
||||||
logger.error = jest.fn();
|
logger.error = jest.fn();
|
||||||
await expect(store.deleteResource({ path: `${root}resource` })).resolves.toBeUndefined();
|
await expect(store.deleteResource({ path: `${root}resource` })).resolves.toEqual([
|
||||||
|
{ path: `${root}resource` },
|
||||||
|
]);
|
||||||
expect(accessor.data[`${root}resource`]).toBeUndefined();
|
expect(accessor.data[`${root}resource`]).toBeUndefined();
|
||||||
expect(accessor.data[`${root}resource.dummy`]).not.toBeUndefined();
|
expect(accessor.data[`${root}resource.dummy`]).not.toBeUndefined();
|
||||||
expect(logger.error).toHaveBeenCalledTimes(1);
|
expect(logger.error).toHaveBeenCalledTimes(1);
|
||||||
|
@ -333,7 +333,8 @@ describe('A FileDataAccessor', (): void => {
|
|||||||
|
|
||||||
it('deletes the corresponding file for document.', async(): Promise<void> => {
|
it('deletes the corresponding file for document.', async(): Promise<void> => {
|
||||||
cache.data = { resource: 'apple' };
|
cache.data = { resource: 'apple' };
|
||||||
await expect(accessor.deleteResource({ path: `${base}resource` })).resolves.toBeUndefined();
|
await expect(accessor.deleteResource({ path: `${base}resource` })).resolves
|
||||||
|
.toEqual([{ path: `${base}resource` }]);
|
||||||
expect(cache.data.resource).toBeUndefined();
|
expect(cache.data.resource).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -344,22 +345,31 @@ describe('A FileDataAccessor', (): void => {
|
|||||||
|
|
||||||
it('removes the corresponding folder for containers.', async(): Promise<void> => {
|
it('removes the corresponding folder for containers.', async(): Promise<void> => {
|
||||||
cache.data = { container: {}};
|
cache.data = { container: {}};
|
||||||
await expect(accessor.deleteResource({ path: `${base}container/` })).resolves.toBeUndefined();
|
await expect(accessor.deleteResource({ path: `${base}container/` })).resolves
|
||||||
|
.toEqual([{ path: `${base}container/` }]);
|
||||||
expect(cache.data.container).toBeUndefined();
|
expect(cache.data.container).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('removes the corresponding metadata.', async(): Promise<void> => {
|
it('removes the corresponding metadata.', async(): Promise<void> => {
|
||||||
cache.data = { container: { resource: 'apple', 'resource.meta': 'metaApple', '.meta': 'metadata' }};
|
cache.data = { container: { resource: 'apple', 'resource.meta': 'metaApple', '.meta': 'metadata' }};
|
||||||
await expect(accessor.deleteResource({ path: `${base}container/resource` })).resolves.toBeUndefined();
|
await expect(accessor.deleteResource({ path: `${base}container/resource` })).resolves.toEqual([
|
||||||
|
{ path: `${base}container/resource` },
|
||||||
|
{ path: `${base}container/resource.meta` },
|
||||||
|
]);
|
||||||
expect(cache.data.container.resource).toBeUndefined();
|
expect(cache.data.container.resource).toBeUndefined();
|
||||||
expect(cache.data.container['resource.meta']).toBeUndefined();
|
expect(cache.data.container['resource.meta']).toBeUndefined();
|
||||||
await expect(accessor.deleteResource({ path: `${base}container/` })).resolves.toBeUndefined();
|
await expect(accessor.deleteResource({ path: `${base}container/` })).resolves.toEqual([
|
||||||
|
{ path: `${base}container/` },
|
||||||
|
{ path: `${base}container/.meta` },
|
||||||
|
]);
|
||||||
expect(cache.data.container).toBeUndefined();
|
expect(cache.data.container).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can delete the root container.', async(): Promise<void> => {
|
it('can delete the root container.', async(): Promise<void> => {
|
||||||
cache.data = { };
|
cache.data = { };
|
||||||
await expect(accessor.deleteResource({ path: `${base}` })).resolves.toBeUndefined();
|
await expect(accessor.deleteResource({ path: `${base}` })).resolves.toEqual([
|
||||||
|
{ path: base },
|
||||||
|
]);
|
||||||
expect(cache.data).toBeUndefined();
|
expect(cache.data).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -170,17 +170,20 @@ describe('An InMemoryDataAccessor', (): void => {
|
|||||||
it('removes the corresponding resource.', async(): Promise<void> => {
|
it('removes the corresponding resource.', async(): Promise<void> => {
|
||||||
await expect(accessor.writeDocument({ path: `${base}resource` }, data, metadata)).resolves.toBeUndefined();
|
await expect(accessor.writeDocument({ path: `${base}resource` }, data, metadata)).resolves.toBeUndefined();
|
||||||
await expect(accessor.writeContainer({ path: `${base}container/` }, metadata)).resolves.toBeUndefined();
|
await expect(accessor.writeContainer({ path: `${base}container/` }, metadata)).resolves.toBeUndefined();
|
||||||
await expect(accessor.deleteResource({ path: `${base}resource` })).resolves.toBeUndefined();
|
await expect(accessor.deleteResource({ path: `${base}resource` })).resolves
|
||||||
await expect(accessor.deleteResource({ path: `${base}container/` })).resolves.toBeUndefined();
|
.toEqual([{ path: 'http://test.com/resource' }]);
|
||||||
|
await expect(accessor.deleteResource({ path: `${base}container/` })).resolves
|
||||||
|
.toEqual([{ path: 'http://test.com/container/' }]);
|
||||||
await expect(accessor.getMetadata({ path: `${base}resource` })).rejects.toThrow(NotFoundHttpError);
|
await expect(accessor.getMetadata({ path: `${base}resource` })).rejects.toThrow(NotFoundHttpError);
|
||||||
await expect(accessor.getMetadata({ path: `${base}container/` })).rejects.toThrow(NotFoundHttpError);
|
await expect(accessor.getMetadata({ path: `${base}container/` })).rejects.toThrow(NotFoundHttpError);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can delete the root container and write to it again.', async(): Promise<void> => {
|
it('can delete the root container and write to it again.', async(): Promise<void> => {
|
||||||
await expect(accessor.deleteResource({ path: `${base}` })).resolves.toBeUndefined();
|
await expect(accessor.deleteResource({ path: base })).resolves
|
||||||
await expect(accessor.getMetadata({ path: `${base}` })).rejects.toThrow(NotFoundHttpError);
|
.toEqual([{ path: base }]);
|
||||||
|
await expect(accessor.getMetadata({ path: base })).rejects.toThrow(NotFoundHttpError);
|
||||||
await expect(accessor.getMetadata({ path: `${base}test/` })).rejects.toThrow(NotFoundHttpError);
|
await expect(accessor.getMetadata({ path: `${base}test/` })).rejects.toThrow(NotFoundHttpError);
|
||||||
await expect(accessor.writeContainer({ path: `${base}` }, metadata)).resolves.toBeUndefined();
|
await expect(accessor.writeContainer({ path: base }, metadata)).resolves.toBeUndefined();
|
||||||
const resultMetadata = await accessor.getMetadata({ path: `${base}` });
|
const resultMetadata = await accessor.getMetadata({ path: `${base}` });
|
||||||
expect(resultMetadata.quads()).toBeRdfIsomorphic(metadata.quads());
|
expect(resultMetadata.quads()).toBeRdfIsomorphic(metadata.quads());
|
||||||
});
|
});
|
||||||
|
@ -210,7 +210,8 @@ describe('A SparqlDataAccessor', (): void => {
|
|||||||
it('removes all references when deleting a resource.', async(): Promise<void> => {
|
it('removes all references when deleting a resource.', async(): Promise<void> => {
|
||||||
metadata = new RepresentationMetadata({ path: 'http://test.com/container/' },
|
metadata = new RepresentationMetadata({ path: 'http://test.com/container/' },
|
||||||
{ [RDF.type]: [ LDP.terms.Resource, LDP.terms.Container ]});
|
{ [RDF.type]: [ LDP.terms.Resource, LDP.terms.Container ]});
|
||||||
await expect(accessor.deleteResource({ path: 'http://test.com/container/' })).resolves.toBeUndefined();
|
await expect(accessor.deleteResource({ path: 'http://test.com/container/' })).resolves
|
||||||
|
.toEqual([{ path: 'http://test.com/container/' }]);
|
||||||
|
|
||||||
expect(fetchUpdate).toHaveBeenCalledTimes(1);
|
expect(fetchUpdate).toHaveBeenCalledTimes(1);
|
||||||
expect(fetchUpdate.mock.calls[0][0]).toBe(endpoint);
|
expect(fetchUpdate.mock.calls[0][0]).toBe(endpoint);
|
||||||
@ -224,7 +225,8 @@ describe('A SparqlDataAccessor', (): void => {
|
|||||||
it('does not try to remove containment triples when deleting a root container.', async(): Promise<void> => {
|
it('does not try to remove containment triples when deleting a root container.', async(): Promise<void> => {
|
||||||
metadata = new RepresentationMetadata({ path: 'http://test.com/' },
|
metadata = new RepresentationMetadata({ path: 'http://test.com/' },
|
||||||
{ [RDF.type]: [ LDP.terms.Resource, LDP.terms.Container ]});
|
{ [RDF.type]: [ LDP.terms.Resource, LDP.terms.Container ]});
|
||||||
await expect(accessor.deleteResource({ path: 'http://test.com/' })).resolves.toBeUndefined();
|
await expect(accessor.deleteResource({ path: 'http://test.com/' })).resolves
|
||||||
|
.toEqual([{ path: 'http://test.com/' }]);
|
||||||
|
|
||||||
expect(fetchUpdate).toHaveBeenCalledTimes(1);
|
expect(fetchUpdate).toHaveBeenCalledTimes(1);
|
||||||
expect(fetchUpdate.mock.calls[0][0]).toBe(endpoint);
|
expect(fetchUpdate.mock.calls[0][0]).toBe(endpoint);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user