mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Store status, body and metadata in ResponseDescription
This commit is contained in:
parent
e8fdcb0ad0
commit
1260c5c14e
27
index.ts
27
index.ts
@ -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';
|
||||||
|
@ -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';
|
||||||
|
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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.
|
||||||
|
15
src/ldp/http/response/CreatedResponseDescription.ts
Normal file
15
src/ldp/http/response/CreatedResponseDescription.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
16
src/ldp/http/response/OkResponseDescription.ts
Normal file
16
src/ldp/http/response/OkResponseDescription.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
10
src/ldp/http/response/ResetResponseDescription.ts
Normal file
10
src/ldp/http/response/ResetResponseDescription.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { ResponseDescription } from './ResponseDescription';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Corresponds to a 205 response.
|
||||||
|
*/
|
||||||
|
export class ResetResponseDescription extends ResponseDescription {
|
||||||
|
public constructor() {
|
||||||
|
super(205);
|
||||||
|
}
|
||||||
|
}
|
22
src/ldp/http/response/ResponseDescription.ts
Normal file
22
src/ldp/http/response/ResponseDescription.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
@ -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'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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([
|
||||||
|
@ -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> => {
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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> => {
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user