mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
fix: Make SimpleResourceStore behaviour closer to expected
This commit is contained in:
parent
4403421c49
commit
011822e859
32
src/ldp/operations/SimplePutOperationHandler.ts
Normal file
32
src/ldp/operations/SimplePutOperationHandler.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Operation } from './Operation';
|
||||
import { OperationHandler } from './OperationHandler';
|
||||
import { ResourceStore } from '../../storage/ResourceStore';
|
||||
import { ResponseDescription } from './ResponseDescription';
|
||||
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
|
||||
|
||||
/**
|
||||
* Handles PUT {@link Operation}s.
|
||||
* Calls the setRepresentation function from a {@link ResourceStore}.
|
||||
*/
|
||||
export class SimplePutOperationHandler extends OperationHandler {
|
||||
private readonly store: ResourceStore;
|
||||
|
||||
public constructor(store: ResourceStore) {
|
||||
super();
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
public async canHandle(input: Operation): Promise<void> {
|
||||
if (input.method !== 'PUT') {
|
||||
throw new UnsupportedHttpError('This handler only supports PUT operations.');
|
||||
}
|
||||
if (typeof input.body !== 'object') {
|
||||
throw new UnsupportedHttpError('PUT operations require a body.');
|
||||
}
|
||||
}
|
||||
|
||||
public async handle(input: Operation): Promise<ResponseDescription> {
|
||||
await this.store.setRepresentation(input.target, input.body!);
|
||||
return { identifier: input.target };
|
||||
}
|
||||
}
|
@ -42,10 +42,12 @@ export class SimpleResourceStore implements ResourceStore {
|
||||
*/
|
||||
public async addResource(container: ResourceIdentifier, representation: Representation): Promise<ResourceIdentifier> {
|
||||
const containerPath = this.parseIdentifier(container);
|
||||
const newPath = `${ensureTrailingSlash(containerPath)}${this.index}`;
|
||||
this.checkPath(containerPath);
|
||||
const newID = { path: `${ensureTrailingSlash(container.path)}${this.index}` };
|
||||
const newPath = this.parseIdentifier(newID);
|
||||
this.index += 1;
|
||||
this.store[newPath] = await this.copyRepresentation(representation);
|
||||
return { path: `${this.base}${newPath}` };
|
||||
return newID;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,6 +56,7 @@ export class SimpleResourceStore implements ResourceStore {
|
||||
*/
|
||||
public async deleteResource(identifier: ResourceIdentifier): Promise<void> {
|
||||
const path = this.parseIdentifier(identifier);
|
||||
this.checkPath(path);
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete this.store[path];
|
||||
}
|
||||
@ -68,6 +71,7 @@ export class SimpleResourceStore implements ResourceStore {
|
||||
*/
|
||||
public async getRepresentation(identifier: ResourceIdentifier): Promise<Representation> {
|
||||
const path = this.parseIdentifier(identifier);
|
||||
this.checkPath(path);
|
||||
return this.generateRepresentation(path);
|
||||
}
|
||||
|
||||
@ -79,7 +83,7 @@ export class SimpleResourceStore implements ResourceStore {
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the stored Representation with the new one for the given identifier.
|
||||
* Puts the given data in the given location.
|
||||
* @param identifier - Identifier to replace.
|
||||
* @param representation - New Representation.
|
||||
*/
|
||||
@ -89,22 +93,35 @@ export class SimpleResourceStore implements ResourceStore {
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the base from the identifier and checks if it is in the store.
|
||||
* Strips the base from the identifier and checks if it is valid.
|
||||
* @param identifier - Incoming identifier.
|
||||
*
|
||||
* @throws {@link NotFoundHttpError}
|
||||
* If the identifier is not in the store.
|
||||
* If the identifier doesn't start with the base ID.
|
||||
*
|
||||
* @returns A string representing the relative path.
|
||||
*/
|
||||
private parseIdentifier(identifier: ResourceIdentifier): string {
|
||||
const path = identifier.path.slice(this.base.length);
|
||||
if (!this.store[path] || !identifier.path.startsWith(this.base)) {
|
||||
if (!identifier.path.startsWith(this.base)) {
|
||||
throw new NotFoundHttpError();
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the relative path is in the store.
|
||||
* @param identifier - Incoming identifier.
|
||||
*
|
||||
* @throws {@link NotFoundHttpError}
|
||||
* If the path is not in the store.
|
||||
*/
|
||||
private checkPath(path: string): void {
|
||||
if (!this.store[path]) {
|
||||
throw new NotFoundHttpError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the Representation by draining the original data stream and creating a new one.
|
||||
*
|
||||
|
@ -11,7 +11,7 @@ describe('A SimpleDeleteOperationHandler', (): void => {
|
||||
store.deleteResource = jest.fn(async(): Promise<void> => {});
|
||||
});
|
||||
|
||||
it('only supports GET operations.', async(): Promise<void> => {
|
||||
it('only supports DELETE operations.', async(): Promise<void> => {
|
||||
await expect(handler.canHandle({ method: 'DELETE' } as Operation)).resolves.toBeUndefined();
|
||||
await expect(handler.canHandle({ method: 'GET' } as Operation)).rejects.toThrow(UnsupportedHttpError);
|
||||
});
|
||||
|
26
test/unit/ldp/operations/SimplePutOperationHandler.test.ts
Normal file
26
test/unit/ldp/operations/SimplePutOperationHandler.test.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { Operation } from '../../../../src/ldp/operations/Operation';
|
||||
import { ResourceStore } from '../../../../src/storage/ResourceStore';
|
||||
import { SimplePutOperationHandler } from '../../../../src/ldp/operations/SimplePutOperationHandler';
|
||||
import { UnsupportedHttpError } from '../../../../src/util/errors/UnsupportedHttpError';
|
||||
|
||||
describe('A SimplePutOperationHandler', (): void => {
|
||||
const store = {} as unknown as ResourceStore;
|
||||
const handler = new SimplePutOperationHandler(store);
|
||||
beforeEach(async(): Promise<void> => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
store.setRepresentation = jest.fn(async(): Promise<void> => {});
|
||||
});
|
||||
|
||||
it('only supports PUT operations with a body.', async(): Promise<void> => {
|
||||
await expect(handler.canHandle({ method: 'PUT' } as Operation)).rejects.toThrow(UnsupportedHttpError);
|
||||
await expect(handler.canHandle({ method: 'GET' } as Operation)).rejects.toThrow(UnsupportedHttpError);
|
||||
await expect(handler.canHandle({ method: 'PUT', body: { dataType: 'test' }} as Operation)).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('sets the representation in the store and returns its identifier.', async(): Promise<void> => {
|
||||
await expect(handler.handle({ target: { path: 'url' }, body: { dataType: 'test' }} as Operation))
|
||||
.resolves.toEqual({ identifier: { path: 'url' }});
|
||||
expect(store.setRepresentation).toHaveBeenCalledTimes(1);
|
||||
expect(store.setRepresentation).toHaveBeenLastCalledWith({ path: 'url' }, { dataType: 'test' });
|
||||
});
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user