mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Emit container pub event on PUT.
Closes https://github.com/solid/community-server/issues/612
This commit is contained in:
parent
6edc255707
commit
c3cff553e3
@ -6,9 +6,6 @@
|
||||
"@type": "MonitoringStore",
|
||||
"MonitoringStore:_source": {
|
||||
"@id": "urn:solid-server:default:ResourceStore_Locking"
|
||||
},
|
||||
"MonitoringStore:_identifierStrategy": {
|
||||
"@id": "urn:solid-server:default:IdentifierStrategy"
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -200,13 +200,12 @@ export class DataAccessorBasedStore implements ResourceStore {
|
||||
// Solid, §5.4: "When a contained resource is deleted, the server MUST also delete the associated auxiliary
|
||||
// resources"
|
||||
// https://solid.github.io/specification/protocol#deleting-resources
|
||||
const deleted = [];
|
||||
const deleted = [ identifier ];
|
||||
if (!this.auxiliaryStrategy.isAuxiliaryIdentifier(identifier)) {
|
||||
const auxiliaries = this.auxiliaryStrategy.getAuxiliaryIdentifiers(identifier);
|
||||
deleted.push(...await this.safelyDeleteAuxiliaryResources(auxiliaries));
|
||||
}
|
||||
|
||||
deleted.unshift(...await this.accessor.deleteResource(identifier));
|
||||
await this.accessor.deleteResource(identifier);
|
||||
return deleted;
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@ import type { Patch } from '../ldp/http/Patch';
|
||||
import type { Representation } from '../ldp/representation/Representation';
|
||||
import type { RepresentationPreferences } from '../ldp/representation/RepresentationPreferences';
|
||||
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
|
||||
import type { IdentifierStrategy } from '../util/identifiers/IdentifierStrategy';
|
||||
import type { Conditions } from './Conditions';
|
||||
import type { ResourceStore } from './ResourceStore';
|
||||
|
||||
@ -14,37 +13,10 @@ import type { ResourceStore } from './ResourceStore';
|
||||
export class MonitoringStore<T extends ResourceStore = ResourceStore>
|
||||
extends EventEmitter implements ResourceStore {
|
||||
private readonly source: T;
|
||||
private readonly identifierStrategy: IdentifierStrategy;
|
||||
|
||||
public constructor(source: T, identifierStrategy: IdentifierStrategy) {
|
||||
public constructor(source: T) {
|
||||
super();
|
||||
this.source = source;
|
||||
this.identifierStrategy = identifierStrategy;
|
||||
}
|
||||
|
||||
public async addResource(container: ResourceIdentifier, representation: Representation,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier> {
|
||||
const identifier = await this.source.addResource(container, representation, conditions);
|
||||
|
||||
// Both the container contents and the resource itself have changed
|
||||
this.emit('changed', container);
|
||||
this.emit('changed', identifier);
|
||||
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public async deleteResource(identifier: ResourceIdentifier,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
const modified = await this.source.deleteResource(identifier, conditions);
|
||||
|
||||
// Both the container contents and the resource itself have changed
|
||||
if (!this.identifierStrategy.isRootContainer(identifier)) {
|
||||
const container = this.identifierStrategy.getParentContainer(identifier);
|
||||
this.emit('changed', container);
|
||||
}
|
||||
this.emit('changed', identifier);
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
public async getRepresentation(identifier: ResourceIdentifier, preferences: RepresentationPreferences,
|
||||
@ -52,17 +24,32 @@ export class MonitoringStore<T extends ResourceStore = ResourceStore>
|
||||
return this.source.getRepresentation(identifier, preferences, conditions);
|
||||
}
|
||||
|
||||
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
|
||||
public async addResource(container: ResourceIdentifier, representation: Representation,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier> {
|
||||
const identifier = await this.source.addResource(container, representation, conditions);
|
||||
this.emitChanged([ container, identifier ]);
|
||||
return identifier;
|
||||
}
|
||||
|
||||
public async deleteResource(identifier: ResourceIdentifier,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
const modified = await this.source.modifyResource(identifier, patch, conditions);
|
||||
this.emit('changed', identifier);
|
||||
return modified;
|
||||
return this.emitChanged(await this.source.deleteResource(identifier, conditions));
|
||||
}
|
||||
|
||||
public async setRepresentation(identifier: ResourceIdentifier, representation: Representation,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
const modified = await this.source.setRepresentation(identifier, representation, conditions);
|
||||
this.emit('changed', identifier);
|
||||
return modified;
|
||||
return this.emitChanged(await this.source.setRepresentation(identifier, representation, conditions));
|
||||
}
|
||||
|
||||
public async modifyResource(identifier: ResourceIdentifier, patch: Patch,
|
||||
conditions?: Conditions): Promise<ResourceIdentifier[]> {
|
||||
return this.emitChanged(await this.source.modifyResource(identifier, patch, conditions));
|
||||
}
|
||||
|
||||
private emitChanged(identifiers: ResourceIdentifier[]): typeof identifiers {
|
||||
for (const identifier of identifiers) {
|
||||
this.emit('changed', identifier);
|
||||
}
|
||||
return identifiers;
|
||||
}
|
||||
}
|
||||
|
@ -63,8 +63,6 @@ export interface DataAccessor {
|
||||
* https://solid.github.io/specification/protocol#deleting-resources
|
||||
*
|
||||
* @param identifier - Resource to delete.
|
||||
*
|
||||
* @returns Identifiers of resources that were possibly modified.
|
||||
*/
|
||||
deleteResource: (identifier: ResourceIdentifier) => Promise<ResourceIdentifier[]>;
|
||||
deleteResource: (identifier: ResourceIdentifier) => Promise<void>;
|
||||
}
|
||||
|
@ -117,15 +117,12 @@ export class FileDataAccessor implements DataAccessor {
|
||||
/**
|
||||
* Removes the corresponding file/folder (and metadata file).
|
||||
*/
|
||||
public async deleteResource(identifier: ResourceIdentifier): Promise<ResourceIdentifier[]> {
|
||||
public async deleteResource(identifier: ResourceIdentifier): Promise<void> {
|
||||
const link = await this.resourceMapper.mapUrlToFilePath(identifier);
|
||||
const metadataLink = await this.getMetadataLink(link.identifier);
|
||||
const stats = await this.getStats(link.filePath);
|
||||
const modified: ResourceIdentifier[] = [ identifier ];
|
||||
|
||||
try {
|
||||
await fsPromises.unlink(metadataLink.filePath);
|
||||
modified.push(metadataLink.identifier);
|
||||
await fsPromises.unlink((await this.getMetadataLink(link.identifier)).filePath);
|
||||
} catch (error: unknown) {
|
||||
// Ignore if it doesn't exist
|
||||
if (!isSystemError(error) || error.code !== 'ENOENT') {
|
||||
@ -140,8 +137,6 @@ export class FileDataAccessor implements DataAccessor {
|
||||
} else {
|
||||
throw new NotFoundHttpError();
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -83,14 +83,13 @@ export class InMemoryDataAccessor implements DataAccessor {
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteResource(identifier: ResourceIdentifier): Promise<ResourceIdentifier[]> {
|
||||
public async deleteResource(identifier: ResourceIdentifier): Promise<void> {
|
||||
const { parent, name } = this.getParentEntry(identifier);
|
||||
if (!parent.entries[name]) {
|
||||
throw new NotFoundHttpError();
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete parent.entries[name];
|
||||
return [ identifier ];
|
||||
}
|
||||
|
||||
private isDataEntry(entry: CacheEntry): entry is DataEntry {
|
||||
|
@ -134,10 +134,9 @@ export class SparqlDataAccessor implements DataAccessor {
|
||||
/**
|
||||
* Removes all graph data relevant to the given identifier.
|
||||
*/
|
||||
public async deleteResource(identifier: ResourceIdentifier): Promise<ResourceIdentifier[]> {
|
||||
public async deleteResource(identifier: ResourceIdentifier): Promise<void> {
|
||||
const { name, parent } = this.getRelatedNames(identifier);
|
||||
await this.sendSparqlUpdate(this.sparqlDelete(name, parent));
|
||||
return [ identifier ];
|
||||
return this.sendSparqlUpdate(this.sparqlDelete(name, parent));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -41,8 +41,7 @@ export class SparqlUpdatePatchHandler extends PatchHandler {
|
||||
const op = patch.algebra;
|
||||
this.validateUpdate(op);
|
||||
|
||||
await this.applyPatch(identifier, op);
|
||||
return [ identifier ];
|
||||
return this.applyPatch(identifier, op);
|
||||
}
|
||||
|
||||
private isDeleteInsert(op: Algebra.Operation): op is Algebra.DeleteInsert {
|
||||
@ -109,7 +108,7 @@ export class SparqlUpdatePatchHandler extends PatchHandler {
|
||||
/**
|
||||
* Apply the given algebra operation to the given identifier.
|
||||
*/
|
||||
private async applyPatch(identifier: ResourceIdentifier, op: Algebra.Operation): Promise<void> {
|
||||
private async applyPatch(identifier: ResourceIdentifier, op: Algebra.Operation): Promise<ResourceIdentifier[]> {
|
||||
const store = new Store<BaseQuad>();
|
||||
try {
|
||||
// Read the quads of the current representation
|
||||
@ -134,7 +133,8 @@ export class SparqlUpdatePatchHandler extends PatchHandler {
|
||||
this.logger.debug(`${store.size} quads will be stored to ${identifier.path}.`);
|
||||
|
||||
// Write the result
|
||||
await this.source.setRepresentation(identifier, new BasicRepresentation(store.match() as Readable, INTERNAL_QUADS));
|
||||
const patched = new BasicRepresentation(store.match() as Readable, INTERNAL_QUADS);
|
||||
return this.source.setRepresentation(identifier, patched);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,14 +68,20 @@ describe('A server with the Solid WebSockets API behind a proxy', (): void => {
|
||||
]);
|
||||
});
|
||||
|
||||
describe('when the client subscribes to a resource', (): void => {
|
||||
describe('when the client subscribes to resources', (): void => {
|
||||
beforeAll(async(): Promise<void> => {
|
||||
client.send(`sub https://example.pod/my-resource`);
|
||||
client.send('sub https://example.pod/my-resource');
|
||||
client.send('sub https://example.pod/other-resource');
|
||||
client.send('sub https://example.pod/');
|
||||
await new Promise((resolve): any => client.once('message', resolve));
|
||||
});
|
||||
|
||||
it('acknowledges the subscription.', async(): Promise<void> => {
|
||||
expect(messages).toEqual([ `ack https://example.pod/my-resource` ]);
|
||||
expect(messages).toEqual([
|
||||
'ack https://example.pod/my-resource',
|
||||
'ack https://example.pod/other-resource',
|
||||
'ack https://example.pod/',
|
||||
]);
|
||||
});
|
||||
|
||||
it('notifies the client of resource updates.', async(): Promise<void> => {
|
||||
@ -87,7 +93,10 @@ describe('A server with the Solid WebSockets API behind a proxy', (): void => {
|
||||
},
|
||||
body: '{}',
|
||||
});
|
||||
expect(messages).toEqual([ `pub https://example.pod/my-resource` ]);
|
||||
expect(messages).toEqual([
|
||||
'pub https://example.pod/',
|
||||
'pub https://example.pod/my-resource',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -40,11 +40,10 @@ class SimpleDataAccessor implements DataAccessor {
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteResource(identifier: ResourceIdentifier): Promise<ResourceIdentifier[]> {
|
||||
public async deleteResource(identifier: ResourceIdentifier): Promise<void> {
|
||||
this.checkExists(identifier);
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete this.data[identifier.path];
|
||||
return [ identifier ];
|
||||
}
|
||||
|
||||
public async getData(identifier: ResourceIdentifier): Promise<Guarded<Readable>> {
|
||||
@ -563,12 +562,11 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
accessor.data[`${root}resource`] = representation;
|
||||
accessor.data[`${root}resource.dummy`] = representation;
|
||||
const deleteFn = accessor.deleteResource;
|
||||
accessor.deleteResource = jest.fn(async(identifier: ResourceIdentifier): Promise<ResourceIdentifier[]> => {
|
||||
accessor.deleteResource = jest.fn(async(identifier: ResourceIdentifier): Promise<void> => {
|
||||
if (auxStrategy.isAuxiliaryIdentifier(identifier)) {
|
||||
throw new Error('auxiliary error!');
|
||||
}
|
||||
await deleteFn.call(accessor, identifier);
|
||||
return [ identifier ];
|
||||
});
|
||||
const { logger } = store as any;
|
||||
logger.error = jest.fn();
|
||||
@ -587,12 +585,11 @@ describe('A DataAccessorBasedStore', (): void => {
|
||||
accessor.data[`${root}resource`] = representation;
|
||||
accessor.data[`${root}resource.dummy`] = representation;
|
||||
const deleteFn = accessor.deleteResource;
|
||||
accessor.deleteResource = jest.fn(async(identifier: ResourceIdentifier): Promise<ResourceIdentifier[]> => {
|
||||
accessor.deleteResource = jest.fn(async(identifier: ResourceIdentifier): Promise<void> => {
|
||||
if (auxStrategy.isAuxiliaryIdentifier(identifier)) {
|
||||
throw 'auxiliary error!';
|
||||
}
|
||||
await deleteFn.call(accessor, identifier);
|
||||
return [ identifier ];
|
||||
});
|
||||
const { logger } = store as any;
|
||||
logger.error = jest.fn();
|
||||
|
@ -2,23 +2,25 @@ import type { Patch } from '../../../src/ldp/http/Patch';
|
||||
import type { Representation } from '../../../src/ldp/representation/Representation';
|
||||
import { MonitoringStore } from '../../../src/storage/MonitoringStore';
|
||||
import type { ResourceStore } from '../../../src/storage/ResourceStore';
|
||||
import { SingleRootIdentifierStrategy } from '../../../src/util/identifiers/SingleRootIdentifierStrategy';
|
||||
|
||||
describe('A MonitoringStore', (): void => {
|
||||
let store: MonitoringStore;
|
||||
let source: ResourceStore;
|
||||
const identifierStrategy = new SingleRootIdentifierStrategy('http://example.org/');
|
||||
let changedCallback: () => void;
|
||||
const modified = [
|
||||
{ path: 'http://example.org/modified/1' },
|
||||
{ path: 'http://example.org/modified/2' },
|
||||
];
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
source = {
|
||||
getRepresentation: jest.fn(async(): Promise<any> => ({ success: true })),
|
||||
addResource: jest.fn(async(): Promise<any> => ({ path: 'http://example.org/foo/bar/new' })),
|
||||
setRepresentation: jest.fn(async(): Promise<any> => undefined),
|
||||
deleteResource: jest.fn(async(): Promise<any> => undefined),
|
||||
modifyResource: jest.fn(async(): Promise<any> => undefined),
|
||||
setRepresentation: jest.fn(async(): Promise<any> => modified),
|
||||
deleteResource: jest.fn(async(): Promise<any> => modified),
|
||||
modifyResource: jest.fn(async(): Promise<any> => modified),
|
||||
};
|
||||
store = new MonitoringStore(source, identifierStrategy);
|
||||
store = new MonitoringStore(source);
|
||||
changedCallback = jest.fn();
|
||||
store.on('changed', changedCallback);
|
||||
});
|
||||
@ -33,7 +35,7 @@ describe('A MonitoringStore', (): void => {
|
||||
expect(source.getRepresentation).toHaveBeenLastCalledWith({ path: 'getPath' }, {}, undefined);
|
||||
});
|
||||
|
||||
it('does not fire a change event after completing getRepresentation.', async(): Promise<void> => {
|
||||
it('does not fire a change event after getRepresentation.', async(): Promise<void> => {
|
||||
expect(changedCallback).toHaveBeenCalledTimes(0);
|
||||
await store.getRepresentation({ path: 'http://example.org/foo/bar' }, {});
|
||||
expect(changedCallback).toHaveBeenCalledTimes(0);
|
||||
@ -46,65 +48,60 @@ describe('A MonitoringStore', (): void => {
|
||||
expect(source.addResource).toHaveBeenLastCalledWith({ path: 'http://example.org/foo/bar' }, {}, undefined);
|
||||
});
|
||||
|
||||
it('fires resource and container change events after completing addResource.', async(): Promise<void> => {
|
||||
const result = store.addResource({ path: 'http://example.org/foo/bar' }, {} as Representation);
|
||||
it('fires container and resource change events after addResource.', async(): Promise<void> => {
|
||||
const result = store.addResource({ path: 'http://example.org/foo/bar/' }, {} as Representation);
|
||||
expect(changedCallback).toHaveBeenCalledTimes(0);
|
||||
await result;
|
||||
expect(changedCallback).toHaveBeenCalledTimes(2);
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar' });
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar/' });
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar/new' });
|
||||
});
|
||||
|
||||
it('calls setRepresentation directly from the source.', async(): Promise<void> => {
|
||||
await expect(store.setRepresentation({ path: 'http://example.org/foo/bar' }, {} as Representation))
|
||||
.resolves.toBeUndefined();
|
||||
.resolves.toEqual(modified);
|
||||
expect(source.setRepresentation).toHaveBeenCalledTimes(1);
|
||||
expect(source.setRepresentation).toHaveBeenLastCalledWith({ path: 'http://example.org/foo/bar' }, {}, undefined);
|
||||
});
|
||||
|
||||
it('fires a resource change event after completing setRepresentation.', async(): Promise<void> => {
|
||||
it('fires all modified change events after setRepresentation.', async(): Promise<void> => {
|
||||
const result = store.setRepresentation({ path: 'http://example.org/foo/bar' }, {} as Representation);
|
||||
expect(changedCallback).toHaveBeenCalledTimes(0);
|
||||
await result;
|
||||
expect(changedCallback).toHaveBeenCalledTimes(1);
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar' });
|
||||
expect(changedCallback).toHaveBeenCalledTimes(2);
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/modified/1' });
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/modified/2' });
|
||||
});
|
||||
|
||||
it('calls deleteResource directly from the source.', async(): Promise<void> => {
|
||||
await expect(store.deleteResource({ path: 'http://example.org/foo/bar' })).resolves.toBeUndefined();
|
||||
await expect(store.deleteResource({ path: 'http://example.org/foo/bar' }))
|
||||
.resolves.toEqual(modified);
|
||||
expect(source.deleteResource).toHaveBeenCalledTimes(1);
|
||||
expect(source.deleteResource).toHaveBeenLastCalledWith({ path: 'http://example.org/foo/bar' }, undefined);
|
||||
});
|
||||
|
||||
it('fires resource and container change events after completing deleteResource.', async(): Promise<void> => {
|
||||
it('fires all modified change events after deleteResource.', async(): Promise<void> => {
|
||||
const result = store.deleteResource({ path: 'http://example.org/foo/bar' });
|
||||
expect(changedCallback).toHaveBeenCalledTimes(0);
|
||||
await result;
|
||||
expect(changedCallback).toHaveBeenCalledTimes(2);
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/' });
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar' });
|
||||
});
|
||||
|
||||
it('fires a resource change event after completing deleteResource on the root.', async(): Promise<void> => {
|
||||
const result = store.deleteResource({ path: 'http://example.org/' });
|
||||
expect(changedCallback).toHaveBeenCalledTimes(0);
|
||||
await result;
|
||||
expect(changedCallback).toHaveBeenCalledTimes(1);
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/' });
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/modified/1' });
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/modified/2' });
|
||||
});
|
||||
|
||||
it('calls modifyResource directly from the source.', async(): Promise<void> => {
|
||||
await expect(store.modifyResource({ path: 'http://example.org/foo/bar' }, {} as Patch))
|
||||
.resolves.toBeUndefined();
|
||||
.resolves.toEqual(modified);
|
||||
expect(source.modifyResource).toHaveBeenCalledTimes(1);
|
||||
expect(source.modifyResource).toHaveBeenLastCalledWith({ path: 'http://example.org/foo/bar' }, {}, undefined);
|
||||
});
|
||||
|
||||
it('fires a resource change event after completing modifyResource.', async(): Promise<void> => {
|
||||
it('fires all modified change events after modifyResource.', async(): Promise<void> => {
|
||||
const result = store.modifyResource({ path: 'http://example.org/foo/bar' }, {} as Patch);
|
||||
expect(changedCallback).toHaveBeenCalledTimes(0);
|
||||
await result;
|
||||
expect(changedCallback).toHaveBeenCalledTimes(1);
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/foo/bar' });
|
||||
expect(changedCallback).toHaveBeenCalledTimes(2);
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/modified/1' });
|
||||
expect(changedCallback).toHaveBeenCalledWith({ path: 'http://example.org/modified/2' });
|
||||
});
|
||||
});
|
||||
|
@ -333,8 +333,7 @@ describe('A FileDataAccessor', (): void => {
|
||||
|
||||
it('deletes the corresponding file for document.', async(): Promise<void> => {
|
||||
cache.data = { resource: 'apple' };
|
||||
await expect(accessor.deleteResource({ path: `${base}resource` })).resolves
|
||||
.toEqual([{ path: `${base}resource` }]);
|
||||
await expect(accessor.deleteResource({ path: `${base}resource` })).resolves.toBeUndefined();
|
||||
expect(cache.data.resource).toBeUndefined();
|
||||
});
|
||||
|
||||
@ -345,31 +344,22 @@ describe('A FileDataAccessor', (): void => {
|
||||
|
||||
it('removes the corresponding folder for containers.', async(): Promise<void> => {
|
||||
cache.data = { container: {}};
|
||||
await expect(accessor.deleteResource({ path: `${base}container/` })).resolves
|
||||
.toEqual([{ path: `${base}container/` }]);
|
||||
await expect(accessor.deleteResource({ path: `${base}container/` })).resolves.toBeUndefined();
|
||||
expect(cache.data.container).toBeUndefined();
|
||||
});
|
||||
|
||||
it('removes the corresponding metadata.', async(): Promise<void> => {
|
||||
cache.data = { container: { resource: 'apple', 'resource.meta': 'metaApple', '.meta': 'metadata' }};
|
||||
await expect(accessor.deleteResource({ path: `${base}container/resource` })).resolves.toEqual([
|
||||
{ path: `${base}container/resource` },
|
||||
{ path: `${base}container/resource.meta` },
|
||||
]);
|
||||
await expect(accessor.deleteResource({ path: `${base}container/resource` })).resolves.toBeUndefined();
|
||||
expect(cache.data.container.resource).toBeUndefined();
|
||||
expect(cache.data.container['resource.meta']).toBeUndefined();
|
||||
await expect(accessor.deleteResource({ path: `${base}container/` })).resolves.toEqual([
|
||||
{ path: `${base}container/` },
|
||||
{ path: `${base}container/.meta` },
|
||||
]);
|
||||
await expect(accessor.deleteResource({ path: `${base}container/` })).resolves.toBeUndefined();
|
||||
expect(cache.data.container).toBeUndefined();
|
||||
});
|
||||
|
||||
it('can delete the root container.', async(): Promise<void> => {
|
||||
cache.data = { };
|
||||
await expect(accessor.deleteResource({ path: `${base}` })).resolves.toEqual([
|
||||
{ path: base },
|
||||
]);
|
||||
await expect(accessor.deleteResource({ path: `${base}` })).resolves.toBeUndefined();
|
||||
expect(cache.data).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
@ -170,20 +170,17 @@ describe('An InMemoryDataAccessor', (): void => {
|
||||
it('removes the corresponding resource.', async(): Promise<void> => {
|
||||
await expect(accessor.writeDocument({ path: `${base}resource` }, data, metadata)).resolves.toBeUndefined();
|
||||
await expect(accessor.writeContainer({ path: `${base}container/` }, metadata)).resolves.toBeUndefined();
|
||||
await expect(accessor.deleteResource({ path: `${base}resource` })).resolves
|
||||
.toEqual([{ path: 'http://test.com/resource' }]);
|
||||
await expect(accessor.deleteResource({ path: `${base}container/` })).resolves
|
||||
.toEqual([{ path: 'http://test.com/container/' }]);
|
||||
await expect(accessor.deleteResource({ path: `${base}resource` })).resolves.toBeUndefined();
|
||||
await expect(accessor.deleteResource({ path: `${base}container/` })).resolves.toBeUndefined();
|
||||
await expect(accessor.getMetadata({ path: `${base}resource` })).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> => {
|
||||
await expect(accessor.deleteResource({ path: base })).resolves
|
||||
.toEqual([{ path: base }]);
|
||||
await expect(accessor.getMetadata({ path: base })).rejects.toThrow(NotFoundHttpError);
|
||||
await expect(accessor.deleteResource({ path: `${base}` })).resolves.toBeUndefined();
|
||||
await expect(accessor.getMetadata({ path: `${base}` })).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}` });
|
||||
expect(resultMetadata.quads()).toBeRdfIsomorphic(metadata.quads());
|
||||
});
|
||||
|
@ -210,8 +210,7 @@ describe('A SparqlDataAccessor', (): void => {
|
||||
it('removes all references when deleting a resource.', async(): Promise<void> => {
|
||||
metadata = new RepresentationMetadata({ path: 'http://test.com/container/' },
|
||||
{ [RDF.type]: [ LDP.terms.Resource, LDP.terms.Container ]});
|
||||
await expect(accessor.deleteResource({ path: 'http://test.com/container/' })).resolves
|
||||
.toEqual([{ path: 'http://test.com/container/' }]);
|
||||
await expect(accessor.deleteResource({ path: 'http://test.com/container/' })).resolves.toBeUndefined();
|
||||
|
||||
expect(fetchUpdate).toHaveBeenCalledTimes(1);
|
||||
expect(fetchUpdate.mock.calls[0][0]).toBe(endpoint);
|
||||
@ -225,8 +224,7 @@ describe('A SparqlDataAccessor', (): void => {
|
||||
it('does not try to remove containment triples when deleting a root container.', async(): Promise<void> => {
|
||||
metadata = new RepresentationMetadata({ path: 'http://test.com/' },
|
||||
{ [RDF.type]: [ LDP.terms.Resource, LDP.terms.Container ]});
|
||||
await expect(accessor.deleteResource({ path: 'http://test.com/' })).resolves
|
||||
.toEqual([{ path: 'http://test.com/' }]);
|
||||
await expect(accessor.deleteResource({ path: 'http://test.com/' })).resolves.toBeUndefined();
|
||||
|
||||
expect(fetchUpdate).toHaveBeenCalledTimes(1);
|
||||
expect(fetchUpdate.mock.calls[0][0]).toBe(endpoint);
|
||||
|
@ -59,7 +59,7 @@ describe('A SparqlUpdatePatchHandler', (): void => {
|
||||
|
||||
async function handle(query: string): Promise<void> {
|
||||
const sparqlPrefix = 'prefix : <http://test.com/>\n';
|
||||
return handler.handle({ identifier: { path: 'path' },
|
||||
await handler.handle({ identifier: { path: 'path' },
|
||||
patch: { algebra: translate(sparqlPrefix.concat(query), { quads: true }) } as SparqlUpdatePatch });
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user