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> {
|
public async addResource(container: ResourceIdentifier, representation: Representation): Promise<ResourceIdentifier> {
|
||||||
const containerPath = this.parseIdentifier(container);
|
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.index += 1;
|
||||||
this.store[newPath] = await this.copyRepresentation(representation);
|
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> {
|
public async deleteResource(identifier: ResourceIdentifier): Promise<void> {
|
||||||
const path = this.parseIdentifier(identifier);
|
const path = this.parseIdentifier(identifier);
|
||||||
|
this.checkPath(path);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||||
delete this.store[path];
|
delete this.store[path];
|
||||||
}
|
}
|
||||||
@ -68,6 +71,7 @@ export class SimpleResourceStore implements ResourceStore {
|
|||||||
*/
|
*/
|
||||||
public async getRepresentation(identifier: ResourceIdentifier): Promise<Representation> {
|
public async getRepresentation(identifier: ResourceIdentifier): Promise<Representation> {
|
||||||
const path = this.parseIdentifier(identifier);
|
const path = this.parseIdentifier(identifier);
|
||||||
|
this.checkPath(path);
|
||||||
return this.generateRepresentation(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 identifier - Identifier to replace.
|
||||||
* @param representation - New Representation.
|
* @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.
|
* @param identifier - Incoming identifier.
|
||||||
*
|
*
|
||||||
* @throws {@link NotFoundHttpError}
|
* @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.
|
* @returns A string representing the relative path.
|
||||||
*/
|
*/
|
||||||
private parseIdentifier(identifier: ResourceIdentifier): string {
|
private parseIdentifier(identifier: ResourceIdentifier): string {
|
||||||
const path = identifier.path.slice(this.base.length);
|
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();
|
throw new NotFoundHttpError();
|
||||||
}
|
}
|
||||||
return path;
|
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.
|
* 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> => {});
|
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: 'DELETE' } as Operation)).resolves.toBeUndefined();
|
||||||
await expect(handler.canHandle({ method: 'GET' } as Operation)).rejects.toThrow(UnsupportedHttpError);
|
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