feat: Store status, body and metadata in ResponseDescription

This commit is contained in:
Joachim Van Herwegen 2020-10-27 12:16:44 +01:00
parent e8fdcb0ad0
commit 1260c5c14e
31 changed files with 209 additions and 184 deletions

View File

@ -22,6 +22,12 @@ export * from './src/ldp/http/metadata/MetadataExtractor';
export * from './src/ldp/http/metadata/MetadataParser'; export * from './src/ldp/http/metadata/MetadataParser';
export * from './src/ldp/http/metadata/SlugParser'; export * from './src/ldp/http/metadata/SlugParser';
// LDP/HTTP/Response
export * from './src/ldp/http/response/CreatedResponseDescription';
export * from './src/ldp/http/response/OkResponseDescription';
export * from './src/ldp/http/response/ResetResponseDescription';
export * from './src/ldp/http/response/ResponseDescription';
// LDP/HTTP // LDP/HTTP
export * from './src/ldp/http/AcceptPreferenceParser'; export * from './src/ldp/http/AcceptPreferenceParser';
export * from './src/ldp/http/BasicRequestParser'; export * from './src/ldp/http/BasicRequestParser';
@ -38,16 +44,6 @@ export * from './src/ldp/http/SparqlUpdateBodyParser';
export * from './src/ldp/http/SparqlUpdatePatch'; export * from './src/ldp/http/SparqlUpdatePatch';
export * from './src/ldp/http/TargetExtractor'; export * from './src/ldp/http/TargetExtractor';
// Logging
export * from './src/logging/LazyLogger';
export * from './src/logging/LazyLoggerFactory';
export * from './src/logging/Logger';
export * from './src/logging/LoggerFactory';
export * from './src/logging/LogLevel';
export * from './src/logging/LogUtil';
export * from './src/logging/VoidLoggerFactory';
export * from './src/logging/WinstonLoggerFactory';
// LDP/Operations // LDP/Operations
export * from './src/ldp/operations/DeleteOperationHandler'; export * from './src/ldp/operations/DeleteOperationHandler';
export * from './src/ldp/operations/GetOperationHandler'; export * from './src/ldp/operations/GetOperationHandler';
@ -57,7 +53,6 @@ export * from './src/ldp/operations/OperationHandler';
export * from './src/ldp/operations/PatchOperationHandler'; export * from './src/ldp/operations/PatchOperationHandler';
export * from './src/ldp/operations/PostOperationHandler'; export * from './src/ldp/operations/PostOperationHandler';
export * from './src/ldp/operations/PutOperationHandler'; export * from './src/ldp/operations/PutOperationHandler';
export * from './src/ldp/operations/ResponseDescription';
// LDP/Permissions // LDP/Permissions
export * from './src/ldp/permissions/PermissionSet'; export * from './src/ldp/permissions/PermissionSet';
@ -75,6 +70,16 @@ export * from './src/ldp/representation/ResourceIdentifier';
// LDP // LDP
export * from './src/ldp/AuthenticatedLdpHandler'; export * from './src/ldp/AuthenticatedLdpHandler';
// Logging
export * from './src/logging/LazyLogger';
export * from './src/logging/LazyLoggerFactory';
export * from './src/logging/Logger';
export * from './src/logging/LoggerFactory';
export * from './src/logging/LogLevel';
export * from './src/logging/LogUtil';
export * from './src/logging/VoidLoggerFactory';
export * from './src/logging/WinstonLoggerFactory';
// Server // Server
export * from './src/server/ExpressHttpServer'; export * from './src/server/ExpressHttpServer';
export * from './src/server/HttpHandler'; export * from './src/server/HttpHandler';

View File

@ -5,10 +5,10 @@ import { HttpHandler } from '../server/HttpHandler';
import type { HttpRequest } from '../server/HttpRequest'; import type { HttpRequest } from '../server/HttpRequest';
import type { HttpResponse } from '../server/HttpResponse'; import type { HttpResponse } from '../server/HttpResponse';
import type { RequestParser } from './http/RequestParser'; import type { RequestParser } from './http/RequestParser';
import type { ResponseDescription } from './http/response/ResponseDescription';
import type { ResponseWriter } from './http/ResponseWriter'; import type { ResponseWriter } from './http/ResponseWriter';
import type { Operation } from './operations/Operation'; import type { Operation } from './operations/Operation';
import type { OperationHandler } from './operations/OperationHandler'; import type { OperationHandler } from './operations/OperationHandler';
import type { ResponseDescription } from './operations/ResponseDescription';
import type { PermissionSet } from './permissions/PermissionSet'; import type { PermissionSet } from './permissions/PermissionSet';
import type { PermissionsExtractor } from './permissions/PermissionsExtractor'; import type { PermissionsExtractor } from './permissions/PermissionsExtractor';

View File

@ -1,36 +1,40 @@
import { getLoggerFor } from '../../logging/LogUtil'; import { getLoggerFor } from '../../logging/LogUtil';
import type { HttpResponse } from '../../server/HttpResponse'; import type { HttpResponse } from '../../server/HttpResponse';
import { INTERNAL_QUADS } from '../../util/ContentTypes';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError'; import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import type { ResponseDescription } from '../operations/ResponseDescription'; import { HTTP } from '../../util/UriConstants';
import type { ResponseDescription } from './response/ResponseDescription';
import { ResponseWriter } from './ResponseWriter'; import { ResponseWriter } from './ResponseWriter';
/** /**
* Writes to an {@link HttpResponse} based on the incoming {@link ResponseDescription}. * Writes to an {@link HttpResponse} based on the incoming {@link ResponseDescription}.
* Still needs a way to write correct status codes for successful operations.
*/ */
export class BasicResponseWriter extends ResponseWriter { export class BasicResponseWriter extends ResponseWriter {
protected readonly logger = getLoggerFor(this); protected readonly logger = getLoggerFor(this);
public async canHandle(input: { response: HttpResponse; result: ResponseDescription | Error }): Promise<void> { public async canHandle(input: { response: HttpResponse; result: ResponseDescription | Error }): Promise<void> {
if ((input.result instanceof Error) || (input.result.body && !input.result.body.binary)) { if (input.result instanceof Error || input.result.metadata?.contentType === INTERNAL_QUADS) {
this.logger.warn('This writer can only write binary bodies'); this.logger.warn('This writer only supports binary ResponseDescriptions');
throw new UnsupportedHttpError('Only binary results are supported'); throw new UnsupportedHttpError('Only successful binary responses are supported');
} }
} }
public async handle(input: { response: HttpResponse; result: ResponseDescription }): Promise<void> { public async handle(input: { response: HttpResponse; result: ResponseDescription }): Promise<void> {
input.response.setHeader('location', input.result.identifier.path); const location = input.result.metadata?.get(HTTP.location);
if (input.result.body) { if (location) {
const contentType = input.result.body.metadata.contentType ?? 'text/plain'; input.response.setHeader('location', location.value);
}
if (input.result.data) {
const contentType = input.result.metadata?.contentType ?? 'text/plain';
input.response.setHeader('content-type', contentType); input.response.setHeader('content-type', contentType);
} }
input.response.writeHead(200); input.response.writeHead(input.result.statusCode);
if (input.result.body) { if (input.result.data) {
input.result.body.data.pipe(input.response); input.result.data.pipe(input.response);
} else { } else {
// If there is an input body the response will end once the input stream ends // If there is input data the response will end once the input stream ends
input.response.end(); input.response.end();
} }
} }

View File

@ -2,7 +2,7 @@ import { getLoggerFor } from '../../logging/LogUtil';
import type { HttpResponse } from '../../server/HttpResponse'; import type { HttpResponse } from '../../server/HttpResponse';
import { HttpError } from '../../util/errors/HttpError'; import { HttpError } from '../../util/errors/HttpError';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError'; import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import type { ResponseDescription } from '../operations/ResponseDescription'; import type { ResponseDescription } from './response/ResponseDescription';
import { ResponseWriter } from './ResponseWriter'; import { ResponseWriter } from './ResponseWriter';
/** /**

View File

@ -1,6 +1,6 @@
import type { HttpResponse } from '../../server/HttpResponse'; import type { HttpResponse } from '../../server/HttpResponse';
import { AsyncHandler } from '../../util/AsyncHandler'; import { AsyncHandler } from '../../util/AsyncHandler';
import type { ResponseDescription } from '../operations/ResponseDescription'; import type { ResponseDescription } from './response/ResponseDescription';
/** /**
* Writes to the HttpResponse. * Writes to the HttpResponse.

View File

@ -0,0 +1,15 @@
import { DataFactory } from 'n3';
import { HTTP } from '../../../util/UriConstants';
import { RepresentationMetadata } from '../../representation/RepresentationMetadata';
import type { ResourceIdentifier } from '../../representation/ResourceIdentifier';
import { ResponseDescription } from './ResponseDescription';
/**
* Corresponds to a 201 response, containing the relevant link metadata.
*/
export class CreatedResponseDescription extends ResponseDescription {
public constructor(location: ResourceIdentifier) {
const metadata = new RepresentationMetadata({ [HTTP.location]: DataFactory.namedNode(location.path) });
super(201, metadata);
}
}

View File

@ -0,0 +1,16 @@
import type { Readable } from 'stream';
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
import { ResponseDescription } from './ResponseDescription';
/**
* Corresponds to a 200 response, containing relevant metadata and potentially data.
*/
export class OkResponseDescription extends ResponseDescription {
/**
* @param metadata - Metadata concerning the response.
* @param data - Potential data. @ignored
*/
public constructor(metadata: RepresentationMetadata, data?: Readable) {
super(200, metadata, data);
}
}

View File

@ -0,0 +1,10 @@
import { ResponseDescription } from './ResponseDescription';
/**
* Corresponds to a 205 response.
*/
export class ResetResponseDescription extends ResponseDescription {
public constructor() {
super(205);
}
}

View File

@ -0,0 +1,22 @@
import type { Readable } from 'stream';
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
/**
* The result of executing an operation.
*/
export class ResponseDescription {
public readonly statusCode: number;
public readonly metadata?: RepresentationMetadata;
public readonly data?: Readable;
/**
* @param statusCode - Status code to return.
* @param metadata - Metadata corresponding to the response (and data potentially).
* @param data - Data that needs to be returned. @ignored
*/
public constructor(statusCode: number, metadata?: RepresentationMetadata, data?: Readable) {
this.statusCode = statusCode;
this.metadata = metadata;
this.data = data;
}
}

View File

@ -1,8 +1,9 @@
import type { ResourceStore } from '../../storage/ResourceStore'; import type { ResourceStore } from '../../storage/ResourceStore';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError'; import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import { ResetResponseDescription } from '../http/response/ResetResponseDescription';
import type { ResponseDescription } from '../http/response/ResponseDescription';
import type { Operation } from './Operation'; import type { Operation } from './Operation';
import { OperationHandler } from './OperationHandler'; import { OperationHandler } from './OperationHandler';
import type { ResponseDescription } from './ResponseDescription';
/** /**
* Handles DELETE {@link Operation}s. * Handles DELETE {@link Operation}s.
@ -24,6 +25,6 @@ export class DeleteOperationHandler extends OperationHandler {
public async handle(input: Operation): Promise<ResponseDescription> { public async handle(input: Operation): Promise<ResponseDescription> {
await this.store.deleteResource(input.target); await this.store.deleteResource(input.target);
return { identifier: input.target }; return new ResetResponseDescription();
} }
} }

View File

@ -1,8 +1,9 @@
import type { ResourceStore } from '../../storage/ResourceStore'; import type { ResourceStore } from '../../storage/ResourceStore';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError'; import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import { OkResponseDescription } from '../http/response/OkResponseDescription';
import type { ResponseDescription } from '../http/response/ResponseDescription';
import type { Operation } from './Operation'; import type { Operation } from './Operation';
import { OperationHandler } from './OperationHandler'; import { OperationHandler } from './OperationHandler';
import type { ResponseDescription } from './ResponseDescription';
/** /**
* Handles GET {@link Operation}s. * Handles GET {@link Operation}s.
@ -24,6 +25,6 @@ export class GetOperationHandler extends OperationHandler {
public async handle(input: Operation): Promise<ResponseDescription> { public async handle(input: Operation): Promise<ResponseDescription> {
const body = await this.store.getRepresentation(input.target, input.preferences); const body = await this.store.getRepresentation(input.target, input.preferences);
return { identifier: input.target, body }; return new OkResponseDescription(body.metadata, body.data);
} }
} }

View File

@ -1,9 +1,9 @@
import { Readable } from 'stream';
import type { ResourceStore } from '../../storage/ResourceStore'; import type { ResourceStore } from '../../storage/ResourceStore';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError'; import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import { OkResponseDescription } from '../http/response/OkResponseDescription';
import type { ResponseDescription } from '../http/response/ResponseDescription';
import type { Operation } from './Operation'; import type { Operation } from './Operation';
import { OperationHandler } from './OperationHandler'; import { OperationHandler } from './OperationHandler';
import type { ResponseDescription } from './ResponseDescription';
/** /**
* Handles HEAD {@link Operation}s. * Handles HEAD {@link Operation}s.
@ -28,10 +28,7 @@ export class HeadOperationHandler extends OperationHandler {
// Close the Readable as we will not return it. // Close the Readable as we will not return it.
body.data.destroy(); body.data.destroy();
body.data = new Readable();
body.data._read = function(): void { return new OkResponseDescription(body.metadata);
body.data.push(null);
};
return { identifier: input.target, body };
} }
} }

View File

@ -1,6 +1,6 @@
import { AsyncHandler } from '../../util/AsyncHandler'; import { AsyncHandler } from '../../util/AsyncHandler';
import type { ResponseDescription } from '../http/response/ResponseDescription';
import type { Operation } from './Operation'; import type { Operation } from './Operation';
import type { ResponseDescription } from './ResponseDescription';
/** /**
* Handler for a specific operation type. * Handler for a specific operation type.

View File

@ -1,9 +1,10 @@
import type { ResourceStore } from '../../storage/ResourceStore'; import type { ResourceStore } from '../../storage/ResourceStore';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError'; import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import type { Patch } from '../http/Patch'; import type { Patch } from '../http/Patch';
import { ResetResponseDescription } from '../http/response/ResetResponseDescription';
import type { ResponseDescription } from '../http/response/ResponseDescription';
import type { Operation } from './Operation'; import type { Operation } from './Operation';
import { OperationHandler } from './OperationHandler'; import { OperationHandler } from './OperationHandler';
import type { ResponseDescription } from './ResponseDescription';
export class PatchOperationHandler extends OperationHandler { export class PatchOperationHandler extends OperationHandler {
private readonly store: ResourceStore; private readonly store: ResourceStore;
@ -21,6 +22,6 @@ export class PatchOperationHandler extends OperationHandler {
public async handle(input: Operation): Promise<ResponseDescription> { public async handle(input: Operation): Promise<ResponseDescription> {
await this.store.modifyResource(input.target, input.body as Patch); await this.store.modifyResource(input.target, input.body as Patch);
return { identifier: input.target }; return new ResetResponseDescription();
} }
} }

View File

@ -1,9 +1,10 @@
import { getLoggerFor } from '../../logging/LogUtil'; import { getLoggerFor } from '../../logging/LogUtil';
import type { ResourceStore } from '../../storage/ResourceStore'; import type { ResourceStore } from '../../storage/ResourceStore';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError'; import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import { CreatedResponseDescription } from '../http/response/CreatedResponseDescription';
import type { ResponseDescription } from '../http/response/ResponseDescription';
import type { Operation } from './Operation'; import type { Operation } from './Operation';
import { OperationHandler } from './OperationHandler'; import { OperationHandler } from './OperationHandler';
import type { ResponseDescription } from './ResponseDescription';
/** /**
* Handles POST {@link Operation}s. * Handles POST {@link Operation}s.
@ -31,6 +32,6 @@ export class PostOperationHandler extends OperationHandler {
throw new UnsupportedHttpError('POST operations require a body'); throw new UnsupportedHttpError('POST operations require a body');
} }
const identifier = await this.store.addResource(input.target, input.body); const identifier = await this.store.addResource(input.target, input.body);
return { identifier }; return new CreatedResponseDescription(identifier);
} }
} }

View File

@ -1,9 +1,10 @@
import { getLoggerFor } from '../../logging/LogUtil'; import { getLoggerFor } from '../../logging/LogUtil';
import type { ResourceStore } from '../../storage/ResourceStore'; import type { ResourceStore } from '../../storage/ResourceStore';
import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError'; import { UnsupportedHttpError } from '../../util/errors/UnsupportedHttpError';
import { ResetResponseDescription } from '../http/response/ResetResponseDescription';
import type { ResponseDescription } from '../http/response/ResponseDescription';
import type { Operation } from './Operation'; import type { Operation } from './Operation';
import { OperationHandler } from './OperationHandler'; import { OperationHandler } from './OperationHandler';
import type { ResponseDescription } from './ResponseDescription';
/** /**
* Handles PUT {@link Operation}s. * Handles PUT {@link Operation}s.
@ -31,6 +32,6 @@ export class PutOperationHandler extends OperationHandler {
throw new UnsupportedHttpError('PUT operations require a body'); throw new UnsupportedHttpError('PUT operations require a body');
} }
await this.store.setRepresentation(input.target, input.body); await this.store.setRepresentation(input.target, input.body);
return { identifier: input.target }; return new ResetResponseDescription();
} }
} }

View File

@ -1,10 +0,0 @@
import type { Representation } from '../representation/Representation';
import type { ResourceIdentifier } from '../representation/ResourceIdentifier';
/**
* The result of executing an operation.
*/
export interface ResponseDescription {
identifier: ResourceIdentifier;
body?: Representation;
}

View File

@ -28,6 +28,7 @@ export const FOAF = {
const HTTP_PREFIX = createNamespace('urn:solid:http:'); const HTTP_PREFIX = createNamespace('urn:solid:http:');
export const HTTP = { export const HTTP = {
location: HTTP_PREFIX('location'),
slug: HTTP_PREFIX('slug'), slug: HTTP_PREFIX('slug'),
}; };

View File

@ -20,7 +20,7 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
{ 'content-type': 'text/turtle', 'transfer-encoding': 'chunked' }, { 'content-type': 'text/turtle', 'transfer-encoding': 'chunked' },
[ '<http://test.com/s> <http://test.com/p> <http://test.com/o>.' ], [ '<http://test.com/s> <http://test.com/p> <http://test.com/o>.' ],
); );
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(201);
expect(response._getData()).toHaveLength(0); expect(response._getData()).toHaveLength(0);
const id = response._getHeaders().location; const id = response._getHeaders().location;
expect(id).toContain(url.format(requestUrl)); expect(id).toContain(url.format(requestUrl));
@ -38,13 +38,11 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
expect(response._getData()).toContain( expect(response._getData()).toContain(
'<http://test.com/s> <http://test.com/p> <http://test.com/o>.', '<http://test.com/s> <http://test.com/p> <http://test.com/o>.',
); );
expect(response._getHeaders().location).toBe(id);
// DELETE // DELETE
response = await call(handler, requestUrl, 'DELETE', {}, []); response = await call(handler, requestUrl, 'DELETE', {}, []);
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(205);
expect(response._getData()).toHaveLength(0); expect(response._getData()).toHaveLength(0);
expect(response._getHeaders().location).toBe(url.format(requestUrl));
// GET // GET
response = await call( response = await call(
@ -73,7 +71,7 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
[ '<http://test.com/s1> <http://test.com/p1> <http://test.com/o1>.', [ '<http://test.com/s1> <http://test.com/p1> <http://test.com/o1>.',
'<http://test.com/s2> <http://test.com/p2> <http://test.com/o2>.' ], '<http://test.com/s2> <http://test.com/p2> <http://test.com/o2>.' ],
); );
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(201);
expect(response._getData()).toHaveLength(0); expect(response._getData()).toHaveLength(0);
const id = response._getHeaders().location; const id = response._getHeaders().location;
expect(id).toContain(url.format(requestUrl)); expect(id).toContain(url.format(requestUrl));
@ -90,9 +88,8 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
'WHERE {}', 'WHERE {}',
], ],
); );
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(205);
expect(response._getData()).toHaveLength(0); expect(response._getData()).toHaveLength(0);
expect(response._getHeaders().location).toBe(id);
// GET // GET
requestUrl = new URL(id); requestUrl = new URL(id);
@ -107,7 +104,6 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
expect(response._getBuffer().toString()).toContain( expect(response._getBuffer().toString()).toContain(
'<http://test.com/s2> <http://test.com/p2> <http://test.com/o2>.', '<http://test.com/s2> <http://test.com/p2> <http://test.com/o2>.',
); );
expect(response._getHeaders().location).toBe(id);
const parser = new Parser(); const parser = new Parser();
const triples = parser.parse(response._getBuffer().toString()); const triples = parser.parse(response._getBuffer().toString());
expect(triples).toBeRdfIsomorphic([ expect(triples).toBeRdfIsomorphic([
@ -141,7 +137,7 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
'<http://test.com/s2> <http://test.com/p2> <http://test.com/o2>.', '<http://test.com/s2> <http://test.com/p2> <http://test.com/o2>.',
], ],
); );
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(201);
expect(response._getData()).toHaveLength(0); expect(response._getData()).toHaveLength(0);
const id = response._getHeaders().location; const id = response._getHeaders().location;
expect(id).toContain(url.format(requestUrl)); expect(id).toContain(url.format(requestUrl));
@ -155,9 +151,8 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
{ 'content-type': 'text/turtle', 'transfer-encoding': 'chunked' }, { 'content-type': 'text/turtle', 'transfer-encoding': 'chunked' },
[ '<http://test.com/s3> <http://test.com/p3> <http://test.com/o3>.' ], [ '<http://test.com/s3> <http://test.com/p3> <http://test.com/o3>.' ],
); );
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(205);
expect(response._getData()).toHaveLength(0); expect(response._getData()).toHaveLength(0);
expect(response._getHeaders().location).toBe(id);
// GET // GET
requestUrl = new URL(id); requestUrl = new URL(id);
@ -169,7 +164,6 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
[], [],
); );
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
expect(response._getHeaders().location).toBe(id);
const parser = new Parser(); const parser = new Parser();
const triples = parser.parse(response._getData()); const triples = parser.parse(response._getData());
expect(triples).toBeRdfIsomorphic([ expect(triples).toBeRdfIsomorphic([

View File

@ -21,7 +21,7 @@ describe('A server with authorization', (): void => {
{ 'content-type': 'text/turtle', 'transfer-encoding': 'chunked' }, { 'content-type': 'text/turtle', 'transfer-encoding': 'chunked' },
[ '<http://test.com/s> <http://test.com/p> <http://test.com/o>.' ], [ '<http://test.com/s> <http://test.com/p> <http://test.com/o>.' ],
); );
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(201);
// PUT // PUT
requestUrl = new URL('http://test.com/foo/bar'); requestUrl = new URL('http://test.com/foo/bar');
@ -32,7 +32,7 @@ describe('A server with authorization', (): void => {
{ 'content-type': 'text/turtle', 'transfer-encoding': 'chunked' }, { 'content-type': 'text/turtle', 'transfer-encoding': 'chunked' },
[ '<http://test.com/s> <http://test.com/p> <http://test.com/o>.' ], [ '<http://test.com/s> <http://test.com/p> <http://test.com/o>.' ],
); );
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(205);
}); });
it('can not create new entries if not allowed.', async(): Promise<void> => { it('can not create new entries if not allowed.', async(): Promise<void> => {

View File

@ -66,11 +66,10 @@ describe.each([ dataAccessorStore, inMemoryDataAccessorStore ])('A server using
// Get file // Get file
response = await fileHelper.getFile(id); response = await fileHelper.getFile(id);
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
expect(response._getHeaders().location).toBe(id);
expect(response._getBuffer().toString()).toContain('TESTFILE2'); expect(response._getBuffer().toString()).toContain('TESTFILE2');
// DELETE file // DELETE file
await fileHelper.deleteFile(id); await fileHelper.deleteResource(id);
await fileHelper.shouldNotExist(id); await fileHelper.shouldNotExist(id);
}); });
@ -95,11 +94,10 @@ describe.each([ dataAccessorStore, inMemoryDataAccessorStore ])('A server using
// GET permanent file // GET permanent file
response = await fileHelper.getFile('http://test.com/permanent.txt'); response = await fileHelper.getFile('http://test.com/permanent.txt');
expect(response._getHeaders().location).toBe('http://test.com/permanent.txt');
expect(response._getBuffer().toString()).toContain('TEST'); expect(response._getBuffer().toString()).toContain('TEST');
// Try to delete permanent file // Try to delete permanent file
response = await fileHelper.deleteFile('http://test.com/permanent.txt', true); response = await fileHelper.deleteResource('http://test.com/permanent.txt', true);
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}); });
}); });

View File

@ -51,11 +51,10 @@ describe.each(configs)('A server using a %s', (name, configFn): void => {
// GET // GET
response = await fileHelper.getFile(id); response = await fileHelper.getFile(id);
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
expect(response._getHeaders().location).toBe(id);
expect(response._getBuffer().toString()).toContain('TESTFILE0'); expect(response._getBuffer().toString()).toContain('TESTFILE0');
// DELETE // DELETE
await fileHelper.deleteFile(id); await fileHelper.deleteResource(id);
await fileHelper.shouldNotExist(id); await fileHelper.shouldNotExist(id);
}); });
@ -66,7 +65,6 @@ describe.each(configs)('A server using a %s', (name, configFn): void => {
// GET // GET
response = await fileHelper.getFile(id); response = await fileHelper.getFile(id);
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
expect(response._getHeaders().location).toBe(id);
expect(response._getBuffer().toString()).toContain('TESTFILE0'); expect(response._getBuffer().toString()).toContain('TESTFILE0');
// PUT // PUT
@ -75,11 +73,10 @@ describe.each(configs)('A server using a %s', (name, configFn): void => {
// GET // GET
response = await fileHelper.getFile(id); response = await fileHelper.getFile(id);
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
expect(response._getHeaders().location).toBe(id);
expect(response._getBuffer().toString()).toContain('TESTFILE1'); expect(response._getBuffer().toString()).toContain('TESTFILE1');
// DELETE // DELETE
await fileHelper.deleteFile(id); await fileHelper.deleteResource(id);
await fileHelper.shouldNotExist(id); await fileHelper.shouldNotExist(id);
}); });
@ -91,10 +88,9 @@ describe.each(configs)('A server using a %s', (name, configFn): void => {
// GET // GET
response = await fileHelper.getFolder(id); response = await fileHelper.getFolder(id);
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
expect(response._getHeaders().location).toBe(id);
// DELETE // DELETE
await fileHelper.deleteFolder(id); await fileHelper.deleteResource(id);
await fileHelper.shouldNotExist(id); await fileHelper.shouldNotExist(id);
}); });
@ -109,12 +105,11 @@ describe.each(configs)('A server using a %s', (name, configFn): void => {
// GET File // GET File
response = await fileHelper.getFile(id); response = await fileHelper.getFile(id);
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
expect(response._getHeaders().location).toBe(id);
// DELETE // DELETE
await fileHelper.deleteFile(id); await fileHelper.deleteResource(id);
await fileHelper.shouldNotExist(id); await fileHelper.shouldNotExist(id);
await fileHelper.deleteFolder('http://test.com/testfolder0/'); await fileHelper.deleteResource('http://test.com/testfolder0/');
await fileHelper.shouldNotExist('http://test.com/testfolder0/'); await fileHelper.shouldNotExist('http://test.com/testfolder0/');
}); });
@ -132,9 +127,9 @@ describe.each(configs)('A server using a %s', (name, configFn): void => {
expect(response._getData()).toContain('ConflictHttpError: Can only delete empty containers.'); expect(response._getData()).toContain('ConflictHttpError: Can only delete empty containers.');
// DELETE // DELETE
await fileHelper.deleteFile('http://test.com/testfolder1/testfile0.txt'); await fileHelper.deleteResource('http://test.com/testfolder1/testfile0.txt');
await fileHelper.shouldNotExist('http://test.com/testfolder1/testfile0.txt'); await fileHelper.shouldNotExist('http://test.com/testfolder1/testfile0.txt');
await fileHelper.deleteFolder(folderId); await fileHelper.deleteResource(folderId);
await fileHelper.shouldNotExist(folderId); await fileHelper.shouldNotExist(folderId);
}); });
@ -153,9 +148,9 @@ describe.each(configs)('A server using a %s', (name, configFn): void => {
expect(response._getData()).toContain('ConflictHttpError: Can only delete empty containers.'); expect(response._getData()).toContain('ConflictHttpError: Can only delete empty containers.');
// DELETE // DELETE
await fileHelper.deleteFolder(subFolderId); await fileHelper.deleteResource(subFolderId);
await fileHelper.shouldNotExist(subFolderId); await fileHelper.shouldNotExist(subFolderId);
await fileHelper.deleteFolder(folderId); await fileHelper.deleteResource(folderId);
await fileHelper.shouldNotExist(folderId); await fileHelper.shouldNotExist(folderId);
}); });
@ -174,16 +169,15 @@ describe.each(configs)('A server using a %s', (name, configFn): void => {
response = await fileHelper.getFolder(folderId); response = await fileHelper.getFolder(folderId);
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
expect(response._getHeaders().location).toBe(folderId);
expect(response._getBuffer().toString()).toContain('<http://www.w3.org/ns/ldp#contains> <http://test.com/testfolder3/subfolder0/> .'); expect(response._getBuffer().toString()).toContain('<http://www.w3.org/ns/ldp#contains> <http://test.com/testfolder3/subfolder0/> .');
expect(response._getBuffer().toString()).toContain('<http://www.w3.org/ns/ldp#contains> <http://test.com/testfolder3/testfile0.txt> .'); expect(response._getBuffer().toString()).toContain('<http://www.w3.org/ns/ldp#contains> <http://test.com/testfolder3/testfile0.txt> .');
// DELETE // DELETE
await fileHelper.deleteFile(fileId); await fileHelper.deleteResource(fileId);
await fileHelper.shouldNotExist(fileId); await fileHelper.shouldNotExist(fileId);
await fileHelper.deleteFolder(subFolderId); await fileHelper.deleteResource(subFolderId);
await fileHelper.shouldNotExist(subFolderId); await fileHelper.shouldNotExist(subFolderId);
await fileHelper.deleteFolder(folderId); await fileHelper.deleteResource(folderId);
await fileHelper.shouldNotExist(folderId); await fileHelper.shouldNotExist(folderId);
}); });
@ -194,11 +188,10 @@ describe.each(configs)('A server using a %s', (name, configFn): void => {
// GET // GET
response = await fileHelper.getFile(fileId); response = await fileHelper.getFile(fileId);
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
expect(response._getHeaders().location).toBe(fileId);
expect(response._getHeaders()['content-type']).toBe('image/png'); expect(response._getHeaders()['content-type']).toBe('image/png');
// DELETE // DELETE
await fileHelper.deleteFile(fileId); await fileHelper.deleteResource(fileId);
await fileHelper.shouldNotExist(fileId); await fileHelper.shouldNotExist(fileId);
}); });
}); });

View File

@ -3,75 +3,46 @@ import type { MockResponse } from 'node-mocks-http';
import { createResponse } from 'node-mocks-http'; import { createResponse } from 'node-mocks-http';
import streamifyArray from 'streamify-array'; import streamifyArray from 'streamify-array';
import { BasicResponseWriter } from '../../../../src/ldp/http/BasicResponseWriter'; import { BasicResponseWriter } from '../../../../src/ldp/http/BasicResponseWriter';
import type { ResponseDescription } from '../../../../src/ldp/operations/ResponseDescription'; import type { ResponseDescription } from '../../../../src/ldp/http/response/ResponseDescription';
import { RepresentationMetadata } from '../../../../src/ldp/representation/RepresentationMetadata';
import { UnsupportedHttpError } from '../../../../src/util/errors/UnsupportedHttpError'; import { UnsupportedHttpError } from '../../../../src/util/errors/UnsupportedHttpError';
import { CONTENT_TYPE } from '../../../../src/util/UriConstants';
describe('A BasicResponseWriter', (): void => { describe('A BasicResponseWriter', (): void => {
const writer = new BasicResponseWriter(); const writer = new BasicResponseWriter();
let response: MockResponse<any>; let response: MockResponse<any>;
let result: ResponseDescription;
beforeEach(async(): Promise<void> => { beforeEach(async(): Promise<void> => {
response = createResponse({ eventEmitter: EventEmitter }); response = createResponse({ eventEmitter: EventEmitter });
result = { statusCode: 201 };
}); });
it('requires a description body, with a binary stream if there is data.', async(): Promise<void> => { it('requires the input to be a ResponseDescription.', async(): Promise<void> => {
await expect(writer.canHandle({ response, result: new Error('error') })) await expect(writer.canHandle({ response, result: new Error('error') }))
.rejects.toThrow(UnsupportedHttpError); .rejects.toThrow(UnsupportedHttpError);
await expect(writer.canHandle({ response, result: { body: { binary: false }} as ResponseDescription })) await expect(writer.canHandle({ response, result }))
.rejects.toThrow(UnsupportedHttpError);
await expect(writer.canHandle({ response, result: { body: { binary: true }} as ResponseDescription }))
.resolves.toBeUndefined(); .resolves.toBeUndefined();
}); });
it('responds with status code 200 and a location header if there is a description.', async(): Promise<void> => { it('responds with the status code of the ResponseDescription.', async(): Promise<void> => {
await writer.handle({ response, result: { identifier: { path: 'path' }}}); await writer.handle({ response, result });
expect(response._isEndCalled()).toBeTruthy(); expect(response._isEndCalled()).toBeTruthy();
expect(response._getStatusCode()).toBe(200); expect(response._getStatusCode()).toBe(201);
expect(response._getHeaders()).toMatchObject({ location: 'path' });
}); });
it('responds with a body if the description has a body.', async(): Promise<void> => { it('responds with a body if the description has a body.', async(): Promise<void> => {
const body = { const data = streamifyArray([ '<http://test.com/s> <http://test.com/p> <http://test.com/o>.' ]);
binary: true, result = { statusCode: 201, data };
data: streamifyArray([ '<http://test.com/s> <http://test.com/p> <http://test.com/o>.' ]),
metadata: new RepresentationMetadata(),
};
const end = new Promise((resolve): void => { const end = new Promise((resolve): void => {
response.on('end', (): void => { response.on('end', (): void => {
expect(response._isEndCalled()).toBeTruthy(); expect(response._isEndCalled()).toBeTruthy();
expect(response._getStatusCode()).toBe(200); expect(response._getStatusCode()).toBe(201);
expect(response._getHeaders()).toMatchObject({ location: 'path' });
expect(response._getData()).toEqual('<http://test.com/s> <http://test.com/p> <http://test.com/o>.'); expect(response._getData()).toEqual('<http://test.com/s> <http://test.com/p> <http://test.com/o>.');
resolve(); resolve();
}); });
}); });
await writer.handle({ response, result: { identifier: { path: 'path' }, body }}); await writer.handle({ response, result });
await end;
});
it('responds with a content-type if the metadata has it.', async(): Promise<void> => {
const metadata = new RepresentationMetadata({ [CONTENT_TYPE]: 'text/turtle' });
const body = {
binary: true,
data: streamifyArray([ '<http://test.com/s> <http://test.com/p> <http://test.com/o>.' ]),
metadata,
};
const end = new Promise((resolve): void => {
response.on('end', (): void => {
expect(response._isEndCalled()).toBeTruthy();
expect(response._getStatusCode()).toBe(200);
expect(response._getHeaders()).toMatchObject({ location: 'path', 'content-type': 'text/turtle' });
expect(response._getData()).toEqual('<http://test.com/s> <http://test.com/p> <http://test.com/o>.');
resolve();
});
});
await writer.handle({ response, result: { identifier: { path: 'path' }, body }});
await end; await end;
}); });
}); });

View File

@ -2,7 +2,6 @@ import { EventEmitter } from 'events';
import type { MockResponse } from 'node-mocks-http'; import type { MockResponse } from 'node-mocks-http';
import { createResponse } from 'node-mocks-http'; import { createResponse } from 'node-mocks-http';
import { ErrorResponseWriter } from '../../../../src/ldp/http/ErrorResponseWriter'; import { ErrorResponseWriter } from '../../../../src/ldp/http/ErrorResponseWriter';
import type { ResponseDescription } from '../../../../src/ldp/operations/ResponseDescription';
import { UnsupportedHttpError } from '../../../../src/util/errors/UnsupportedHttpError'; import { UnsupportedHttpError } from '../../../../src/util/errors/UnsupportedHttpError';
describe('An ErrorResponseWriter', (): void => { describe('An ErrorResponseWriter', (): void => {
@ -16,9 +15,7 @@ describe('An ErrorResponseWriter', (): void => {
it('requires the input to be an error.', async(): Promise<void> => { it('requires the input to be an error.', async(): Promise<void> => {
await expect(writer.canHandle({ response, result: new Error('error') })) await expect(writer.canHandle({ response, result: new Error('error') }))
.resolves.toBeUndefined(); .resolves.toBeUndefined();
await expect(writer.canHandle({ response, result: { body: { binary: false }} as ResponseDescription })) await expect(writer.canHandle({ response, result: { statusCode: 200 }}))
.rejects.toThrow(UnsupportedHttpError);
await expect(writer.canHandle({ response, result: { body: { binary: true }} as ResponseDescription }))
.rejects.toThrow(UnsupportedHttpError); .rejects.toThrow(UnsupportedHttpError);
}); });

View File

@ -7,8 +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> => {
// eslint-disable-next-line @typescript-eslint/no-empty-function store.deleteResource = jest.fn(async(): Promise<void> => undefined);
store.deleteResource = jest.fn(async(): Promise<void> => {});
}); });
it('only supports DELETE operations.', async(): Promise<void> => { it('only supports DELETE operations.', async(): Promise<void> => {
@ -16,10 +15,12 @@ describe('A DeleteOperationHandler', (): void => {
await expect(handler.canHandle({ method: 'GET' } as Operation)).rejects.toThrow(UnsupportedHttpError); await expect(handler.canHandle({ method: 'GET' } as Operation)).rejects.toThrow(UnsupportedHttpError);
}); });
it('deletes the resource from the store and returns its identifier.', async(): Promise<void> => { it('deletes the resource from the store and returns the correct response.', async(): Promise<void> => {
await expect(handler.handle({ target: { path: 'url' }} as Operation)) const result = await handler.handle({ target: { path: 'url' }} as Operation);
.resolves.toEqual({ identifier: { path: 'url' }});
expect(store.deleteResource).toHaveBeenCalledTimes(1); expect(store.deleteResource).toHaveBeenCalledTimes(1);
expect(store.deleteResource).toHaveBeenLastCalledWith({ path: 'url' }); expect(store.deleteResource).toHaveBeenLastCalledWith({ path: 'url' });
expect(result.statusCode).toBe(205);
expect(result.metadata).toBeUndefined();
expect(result.data).toBeUndefined();
}); });
}); });

View File

@ -6,7 +6,8 @@ import { UnsupportedHttpError } from '../../../../src/util/errors/UnsupportedHtt
describe('A GetOperationHandler', (): void => { describe('A GetOperationHandler', (): void => {
const store = { const store = {
getRepresentation: async(): Promise<Representation> => ({ binary: false } as Representation), getRepresentation: async(): Promise<Representation> =>
({ binary: false, data: 'data', metadata: 'metadata' } as any),
} as unknown as ResourceStore; } as unknown as ResourceStore;
const handler = new GetOperationHandler(store); const handler = new GetOperationHandler(store);
@ -15,9 +16,10 @@ describe('A GetOperationHandler', (): void => {
await expect(handler.canHandle({ method: 'POST' } as Operation)).rejects.toThrow(UnsupportedHttpError); await expect(handler.canHandle({ method: 'POST' } as Operation)).rejects.toThrow(UnsupportedHttpError);
}); });
it('returns the representation from the store with the input identifier.', async(): Promise<void> => { it('returns the representation from the store with the correct response.', async(): Promise<void> => {
await expect(handler.handle({ target: { path: 'url' }} as Operation)).resolves.toEqual( const result = await handler.handle({ target: { path: 'url' }} as Operation);
{ identifier: { path: 'url' }, body: { binary: false }}, expect(result.statusCode).toBe(200);
); expect(result.metadata).toBe('metadata');
expect(result.data).toBe('data');
}); });
}); });

View File

@ -1,5 +1,4 @@
import arrayifyStream from 'arrayify-stream'; import type { Readable } from 'stream';
import streamifyArray from 'streamify-array';
import { HeadOperationHandler } from '../../../../src/ldp/operations/HeadOperationHandler'; import { HeadOperationHandler } from '../../../../src/ldp/operations/HeadOperationHandler';
import type { Operation } from '../../../../src/ldp/operations/Operation'; import type { Operation } from '../../../../src/ldp/operations/Operation';
import type { Representation } from '../../../../src/ldp/representation/Representation'; import type { Representation } from '../../../../src/ldp/representation/Representation';
@ -7,11 +6,18 @@ import type { ResourceStore } from '../../../../src/storage/ResourceStore';
import { UnsupportedHttpError } from '../../../../src/util/errors/UnsupportedHttpError'; import { UnsupportedHttpError } from '../../../../src/util/errors/UnsupportedHttpError';
describe('A HeadOperationHandler', (): void => { describe('A HeadOperationHandler', (): void => {
const store = { let store: ResourceStore;
getRepresentation: async(): Promise<Representation> => ({ binary: false, data: streamifyArray([ 1, 2, 3 ]) } as let handler: HeadOperationHandler;
Representation), let data: Readable;
} as unknown as ResourceStore;
const handler = new HeadOperationHandler(store); beforeEach(async(): Promise<void> => {
data = { destroy: jest.fn() } as any;
store = {
getRepresentation: async(): Promise<Representation> =>
({ binary: false, data, metadata: 'metadata' } as any),
} as any;
handler = new HeadOperationHandler(store);
});
it('only supports HEAD operations.', async(): Promise<void> => { it('only supports HEAD operations.', async(): Promise<void> => {
await expect(handler.canHandle({ method: 'HEAD' } as Operation)).resolves.toBeUndefined(); await expect(handler.canHandle({ method: 'HEAD' } as Operation)).resolves.toBeUndefined();
@ -19,10 +25,11 @@ describe('A HeadOperationHandler', (): void => {
await expect(handler.canHandle({ method: 'POST' } as Operation)).rejects.toThrow(UnsupportedHttpError); await expect(handler.canHandle({ method: 'POST' } as Operation)).rejects.toThrow(UnsupportedHttpError);
}); });
it('returns the representation from the store with the input identifier and empty data.', async(): Promise<void> => { it('returns the representation from the store with the correct response.', async(): Promise<void> => {
const result = await handler.handle({ target: { path: 'url' }} as Operation); const result = await handler.handle({ target: { path: 'url' }} as Operation);
expect(result.identifier.path).toBe('url'); expect(result.statusCode).toBe(200);
expect(result.body?.binary).toBe(false); expect(result.metadata).toBe('metadata');
await expect(arrayifyStream(result.body!.data)).resolves.toEqual([]); expect(result.data).toBeUndefined();
expect(data.destroy).toHaveBeenCalledTimes(1);
}); });
}); });

View File

@ -15,10 +15,12 @@ describe('A PatchOperationHandler', (): void => {
await expect(handler.canHandle({ method: 'GET' } as Operation)).rejects.toThrow(UnsupportedHttpError); await expect(handler.canHandle({ method: 'GET' } as Operation)).rejects.toThrow(UnsupportedHttpError);
}); });
it('deletes the resource from the store and returns its identifier.', async(): Promise<void> => { it('deletes the resource from the store and returns the correct response.', async(): Promise<void> => {
await expect(handler.handle({ target: { path: 'url' }, body: { binary: false }} as Operation)) const result = await handler.handle({ target: { path: 'url' }, body: { binary: false }} as Operation);
.resolves.toEqual({ identifier: { path: 'url' }});
expect(store.modifyResource).toHaveBeenCalledTimes(1); expect(store.modifyResource).toHaveBeenCalledTimes(1);
expect(store.modifyResource).toHaveBeenLastCalledWith({ path: 'url' }, { binary: false }); expect(store.modifyResource).toHaveBeenLastCalledWith({ path: 'url' }, { binary: false });
expect(result.statusCode).toBe(205);
expect(result.metadata).toBeUndefined();
expect(result.data).toBeUndefined();
}); });
}); });

View File

@ -1,8 +1,10 @@
import type { Operation } from '../../../../src/ldp/operations/Operation'; import type { Operation } from '../../../../src/ldp/operations/Operation';
import { PostOperationHandler } from '../../../../src/ldp/operations/PostOperationHandler'; import { PostOperationHandler } from '../../../../src/ldp/operations/PostOperationHandler';
import { RepresentationMetadata } from '../../../../src/ldp/representation/RepresentationMetadata';
import type { ResourceIdentifier } from '../../../../src/ldp/representation/ResourceIdentifier'; import type { ResourceIdentifier } from '../../../../src/ldp/representation/ResourceIdentifier';
import type { ResourceStore } from '../../../../src/storage/ResourceStore'; import type { ResourceStore } from '../../../../src/storage/ResourceStore';
import { UnsupportedHttpError } from '../../../../src/util/errors/UnsupportedHttpError'; import { UnsupportedHttpError } from '../../../../src/util/errors/UnsupportedHttpError';
import { HTTP } from '../../../../src/util/UriConstants';
describe('A PostOperationHandler', (): void => { describe('A PostOperationHandler', (): void => {
const store = { const store = {
@ -21,8 +23,11 @@ describe('A PostOperationHandler', (): void => {
await expect(handler.handle({ method: 'POST' } as Operation)).rejects.toThrow(UnsupportedHttpError); await expect(handler.handle({ method: 'POST' } as Operation)).rejects.toThrow(UnsupportedHttpError);
}); });
it('adds the given representation to the store and returns the new identifier.', async(): Promise<void> => { it('adds the given representation to the store and returns the correct response.', async(): Promise<void> => {
await expect(handler.handle({ method: 'POST', body: { }} as Operation)) const result = await handler.handle({ method: 'POST', body: { }} as Operation);
.resolves.toEqual({ identifier: { path: 'newPath' }}); expect(result.statusCode).toBe(201);
expect(result.metadata).toBeInstanceOf(RepresentationMetadata);
expect(result.metadata?.get(HTTP.location)?.value).toBe('newPath');
expect(result.data).toBeUndefined();
}); });
}); });

View File

@ -16,11 +16,13 @@ describe('A PutOperationHandler', (): void => {
await expect(handler.canHandle({ method: 'PUT' } as Operation)).resolves.toBeUndefined(); await expect(handler.canHandle({ method: 'PUT' } as Operation)).resolves.toBeUndefined();
}); });
it('sets the representation in the store and returns its identifier.', async(): Promise<void> => { it('sets the representation in the store and returns the correct response.', async(): Promise<void> => {
await expect(handler.handle({ target: { path: 'url' }, body: {}} as Operation)) const result = await handler.handle({ target: { path: 'url' }, body: {}} as Operation);
.resolves.toEqual({ identifier: { path: 'url' }});
expect(store.setRepresentation).toHaveBeenCalledTimes(1); expect(store.setRepresentation).toHaveBeenCalledTimes(1);
expect(store.setRepresentation).toHaveBeenLastCalledWith({ path: 'url' }, {}); expect(store.setRepresentation).toHaveBeenLastCalledWith({ path: 'url' }, {});
expect(result.statusCode).toBe(205);
expect(result.metadata).toBeUndefined();
expect(result.data).toBeUndefined();
}); });
it('errors when there is no body.', async(): Promise<void> => { it('errors when there is no body.', async(): Promise<void> => {

View File

@ -124,7 +124,7 @@ export class FileTestHelper {
fileData, fileData,
); );
if (!mayFail) { if (!mayFail) {
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(201);
expect(response._getData()).toHaveLength(0); expect(response._getData()).toHaveLength(0);
expect(response._getHeaders().location).toContain(url.format(this.baseUrl)); expect(response._getHeaders().location).toContain(url.format(this.baseUrl));
} }
@ -145,9 +145,8 @@ export class FileTestHelper {
{ 'content-type': contentType, 'transfer-encoding': 'chunked' }, { 'content-type': contentType, 'transfer-encoding': 'chunked' },
fileData, fileData,
); );
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(205);
expect(response._getData()).toHaveLength(0); expect(response._getData()).toHaveLength(0);
expect(response._getHeaders().location).toContain(url.format(putUrl));
return response; return response;
} }
@ -159,14 +158,13 @@ export class FileTestHelper {
return response; return response;
} }
public async deleteFile(requestUrl: string, mayFail = false): Promise<MockResponse<any>> { public async deleteResource(requestUrl: string, mayFail = false): Promise<MockResponse<any>> {
const deleteUrl = new URL(requestUrl); const deleteUrl = new URL(requestUrl);
const response = await this.simpleCall(deleteUrl, 'DELETE', {}); const response = await this.simpleCall(deleteUrl, 'DELETE', {});
if (!mayFail) { if (!mayFail) {
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(205);
expect(response._getData()).toHaveLength(0); expect(response._getData()).toHaveLength(0);
expect(response._getHeaders().location).toBe(url.format(requestUrl));
} }
return response; return response;
} }
@ -182,7 +180,7 @@ export class FileTestHelper {
'transfer-encoding': 'chunked', 'transfer-encoding': 'chunked',
}, },
); );
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(201);
expect(response._getData()).toHaveLength(0); expect(response._getData()).toHaveLength(0);
expect(response._getHeaders().location).toContain(url.format(this.baseUrl)); expect(response._getHeaders().location).toContain(url.format(this.baseUrl));
return response; return response;
@ -195,16 +193,6 @@ export class FileTestHelper {
return await this.simpleCall(getUrl, 'GET', { accept: 'application/n-quads' }); return await this.simpleCall(getUrl, 'GET', { accept: 'application/n-quads' });
} }
public async deleteFolder(requestUrl: string): Promise<MockResponse<any>> {
const deleteUrl = new URL(requestUrl);
const response = await this.simpleCall(deleteUrl, 'DELETE', {});
expect(response.statusCode).toBe(200);
expect(response._getData()).toHaveLength(0);
expect(response._getHeaders().location).toBe(url.format(requestUrl));
return response;
}
public async shouldNotExist(requestUrl: string): Promise<MockResponse<any>> { public async shouldNotExist(requestUrl: string): Promise<MockResponse<any>> {
const getUrl = new URL(requestUrl); const getUrl = new URL(requestUrl);