mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Support creation of HTTPS server
This commit is contained in:
parent
afc662ca9a
commit
7faad0aef0
53
config/example-https-file.json
Normal file
53
config/example-https-file.json
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
|
||||||
|
"import": [
|
||||||
|
"files-scs:config/http/handler/default.json",
|
||||||
|
"files-scs:config/http/middleware/websockets.json",
|
||||||
|
|
||||||
|
"files-scs:config/http/static/default.json",
|
||||||
|
"files-scs:config/identity/handler/default.json",
|
||||||
|
"files-scs:config/identity/email/default.json",
|
||||||
|
"files-scs:config/init/handler/default.json",
|
||||||
|
"files-scs:config/ldp/authentication/dpop-bearer.json",
|
||||||
|
"files-scs:config/ldp/authorization/webacl.json",
|
||||||
|
"files-scs:config/ldp/handler/default.json",
|
||||||
|
"files-scs:config/ldp/metadata-parser/default.json",
|
||||||
|
"files-scs:config/ldp/metadata-writer/default.json",
|
||||||
|
"files-scs:config/ldp/permissions/acl.json",
|
||||||
|
"files-scs:config/pod/handler/static.json",
|
||||||
|
"files-scs:config/storage/key-value/memory.json",
|
||||||
|
"files-scs:config/storage/resource-store/file.json",
|
||||||
|
"files-scs:config/util/auxiliary/acl.json",
|
||||||
|
"files-scs:config/util/identifiers/suffix.json",
|
||||||
|
"files-scs:config/util/index/default.json",
|
||||||
|
"files-scs:config/util/logging/winston.json",
|
||||||
|
"files-scs:config/util/representation-conversion/default.json",
|
||||||
|
"files-scs:config/util/resource-locker/memory.json",
|
||||||
|
"files-scs:config/util/variables/default.json"
|
||||||
|
],
|
||||||
|
"@graph": [
|
||||||
|
{
|
||||||
|
"comment": [
|
||||||
|
"An example of what a config could look like if HTTPS is required.",
|
||||||
|
"The http/server-factory import above has been omitted since that feature is set below."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comment": "The key/cert values should be replaces with paths to the correct files. The 'options' block can be removed if not needed.",
|
||||||
|
"@id": "urn:solid-server:default:ServerFactory",
|
||||||
|
"@type": "WebSocketServerFactory",
|
||||||
|
"baseServerFactory": {
|
||||||
|
"@id": "urn:solid-server:default:HttpServerFactory",
|
||||||
|
"@type": "BaseHttpServerFactory",
|
||||||
|
"handler": { "@id": "urn:solid-server:default:HttpHandler" },
|
||||||
|
"options_https": true,
|
||||||
|
"options_key": "/path/to/server.key",
|
||||||
|
"options_cert": "/path/to/server.cert"
|
||||||
|
},
|
||||||
|
"webSocketHandler": {
|
||||||
|
"@type": "UnsecureWebSocketsProtocol",
|
||||||
|
"source": { "@id": "urn:solid-server:default:ResourceStore" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -16,6 +16,7 @@ and then pass the request along.
|
|||||||
The factory used to create the actual server object.
|
The factory used to create the actual server object.
|
||||||
* *no-websockets*: Only HTTP.
|
* *no-websockets*: Only HTTP.
|
||||||
* *websockets*: HTTP and websockets.
|
* *websockets*: HTTP and websockets.
|
||||||
|
* *https-example*: An example configuration to use HTTPS directly at the server (instead of at a reverse proxy).
|
||||||
|
|
||||||
## Static
|
## Static
|
||||||
Support for static files that should be found at a specific path.
|
Support for static files that should be found at a specific path.
|
||||||
|
22
config/http/server-factory/https-example.json
Normal file
22
config/http/server-factory/https-example.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^0.0.0/components/context.jsonld",
|
||||||
|
"@graph": [
|
||||||
|
{
|
||||||
|
"comment": "An example of how to set up a server with HTTPS",
|
||||||
|
"@id": "urn:solid-server:default:ServerFactory",
|
||||||
|
"@type": "WebSocketServerFactory",
|
||||||
|
"baseServerFactory": {
|
||||||
|
"@id": "urn:solid-server:default:HttpServerFactory",
|
||||||
|
"@type": "BaseHttpServerFactory",
|
||||||
|
"handler": { "@id": "urn:solid-server:default:HttpHandler" },
|
||||||
|
"options_https": true,
|
||||||
|
"options_key": "/path/to/server.key",
|
||||||
|
"options_cert": "/path/to/server.cert"
|
||||||
|
},
|
||||||
|
"webSocketHandler": {
|
||||||
|
"@type": "UnsecureWebSocketsProtocol",
|
||||||
|
"source": { "@id": "urn:solid-server:default:ResourceStore" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -5,9 +5,7 @@
|
|||||||
"comment": "Creates a server that supports HTTP requests.",
|
"comment": "Creates a server that supports HTTP requests.",
|
||||||
"@id": "urn:solid-server:default:ServerFactory",
|
"@id": "urn:solid-server:default:ServerFactory",
|
||||||
"@type": "BaseHttpServerFactory",
|
"@type": "BaseHttpServerFactory",
|
||||||
"handler": {
|
"handler": { "@id": "urn:solid-server:default:HttpHandler" }
|
||||||
"@id": "urn:solid-server:default:HttpHandler"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,33 @@
|
|||||||
|
import { readFileSync } from 'fs';
|
||||||
import type { Server, IncomingMessage, ServerResponse } from 'http';
|
import type { Server, IncomingMessage, ServerResponse } from 'http';
|
||||||
import { createServer } from 'http';
|
import { createServer as createHttpServer } from 'http';
|
||||||
|
import { createServer as createHttpsServer } from 'https';
|
||||||
|
import { URL } from 'url';
|
||||||
import { getLoggerFor } from '../logging/LogUtil';
|
import { getLoggerFor } from '../logging/LogUtil';
|
||||||
import { isNativeError } from '../util/errors/ErrorUtil';
|
import { isNativeError } from '../util/errors/ErrorUtil';
|
||||||
import { guardStream } from '../util/GuardedStream';
|
import { guardStream } from '../util/GuardedStream';
|
||||||
import type { HttpHandler } from './HttpHandler';
|
import type { HttpHandler } from './HttpHandler';
|
||||||
import type { HttpServerFactory } from './HttpServerFactory';
|
import type { HttpServerFactory } from './HttpServerFactory';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options to be used when creating the server.
|
||||||
|
* Due to Components.js not supporting external types, this has been simplified (for now?).
|
||||||
|
* The common https keys here (key/cert/pfx) will be interpreted as file paths that need to be read
|
||||||
|
* before passing the options to the `createServer` function.
|
||||||
|
*/
|
||||||
|
export interface BaseHttpServerOptions {
|
||||||
|
/**
|
||||||
|
* If the server should start as an http or https server.
|
||||||
|
*/
|
||||||
|
https?: boolean;
|
||||||
|
|
||||||
|
key?: string;
|
||||||
|
cert?: string;
|
||||||
|
|
||||||
|
pfx?: string;
|
||||||
|
passphrase?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HttpServerFactory based on the native Node.js http module
|
* HttpServerFactory based on the native Node.js http module
|
||||||
*/
|
*/
|
||||||
@ -14,19 +36,26 @@ export class BaseHttpServerFactory implements HttpServerFactory {
|
|||||||
|
|
||||||
/** The main HttpHandler */
|
/** The main HttpHandler */
|
||||||
private readonly handler: HttpHandler;
|
private readonly handler: HttpHandler;
|
||||||
|
private readonly options: BaseHttpServerOptions;
|
||||||
|
|
||||||
public constructor(handler: HttpHandler) {
|
public constructor(handler: HttpHandler, options: BaseHttpServerOptions = { https: false }) {
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
|
this.options = { ...options };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates and starts an HTTP server
|
* Creates and starts an HTTP(S) server
|
||||||
* @param port - Port on which the server listens
|
* @param port - Port on which the server listens
|
||||||
*/
|
*/
|
||||||
public startServer(port: number): Server {
|
public startServer(port: number): Server {
|
||||||
this.logger.info(`Starting server at http://localhost:${port}/`);
|
const protocol = this.options.https ? 'https' : 'http';
|
||||||
|
const url = new URL(`${protocol}://localhost:${port}/`).href;
|
||||||
|
this.logger.info(`Starting server at ${url}`);
|
||||||
|
|
||||||
const server = createServer(
|
const createServer = this.options.https ? createHttpsServer : createHttpServer;
|
||||||
|
const options = this.createServerOptions();
|
||||||
|
|
||||||
|
const server = createServer(options,
|
||||||
async(request: IncomingMessage, response: ServerResponse): Promise<void> => {
|
async(request: IncomingMessage, response: ServerResponse): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
this.logger.info(`Received ${request.method} request for ${request.url}`);
|
this.logger.info(`Received ${request.method} request for ${request.url}`);
|
||||||
@ -45,9 +74,19 @@ export class BaseHttpServerFactory implements HttpServerFactory {
|
|||||||
response.writeHead(404).end();
|
response.writeHead(404).end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
||||||
return server.listen(port);
|
return server.listen(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createServerOptions(): BaseHttpServerOptions {
|
||||||
|
const options = { ...this.options };
|
||||||
|
for (const id of [ 'key', 'cert', 'pfx' ] as const) {
|
||||||
|
const val = options[id];
|
||||||
|
if (val) {
|
||||||
|
options[id] = readFileSync(val, 'utf8');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
21
test/assets/https/server.cert
Normal file
21
test/assets/https/server.cert
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDbTCCAlWgAwIBAgIUFoZojFFFwF4/zB9XlZ3zXgZyvo0wDQYJKoZIhvcNAQEL
|
||||||
|
BQAwRjELMAkGA1UEBhMCQkUxEzARBgNVBAgMClNvbWUtU3RhdGUxDjAMBgNVBAoM
|
||||||
|
BVNvbGlkMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMjEwNTI3MDkwMTQxWhcNMjEw
|
||||||
|
NjI2MDkwMTQxWjBGMQswCQYDVQQGEwJCRTETMBEGA1UECAwKU29tZS1TdGF0ZTEO
|
||||||
|
MAwGA1UECgwFU29saWQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcN
|
||||||
|
AQEBBQADggEPADCCAQoCggEBAK5C/0PCj2If698eYOCvp4z87oY0YZSI5MZnsbv2
|
||||||
|
NWNAA+9ODvycA0oStyqzmGUw4sajqFrJZuYjDZWELnhEgv7bpwGDncHh/KjwPkwl
|
||||||
|
w0RQkELSUfFPO6uAz/JwaF2XiI1XS11I2EvzSJNPlgrqKWNiAOZ59Vm5Seda+16m
|
||||||
|
nhnqFJCSQ+bImdGVdnGQUHzHjidwoyv5QFOW6MZd+1jwY+HZFoWFYy9QMAHMR2/I
|
||||||
|
pKnsfb3zeQYyEhSxwDwwrCiIFfPhUX+wezsVS3KrGZyUeg8Z8/Jb7p3Vk5fXItxP
|
||||||
|
t2IcOKrU/fnXDdcghRylcX93fs6DzCh9p4x/gyUsAazoDVECAwEAAaNTMFEwHQYD
|
||||||
|
VR0OBBYEFAS3dWyE56NTg8K+RabiIwSII6njMB8GA1UdIwQYMBaAFAS3dWyE56NT
|
||||||
|
g8K+RabiIwSII6njMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
|
||||||
|
ADFPYohoV7scihIW+nW2KQuflNYc026CSUBbjIh3CtUQKI4uM8d9zY6+hDX7WtF2
|
||||||
|
asR29a6f1/BMKDWlHCbBbP17x4FXZwpUCnLPqQS9XEVvC4B+s4hc3wgxMFewvClX
|
||||||
|
isAZPIpbrOiqpF1unRHmKzyuGsmQmul22YRfeqj3y6qVry/iSdTtjZQY7oy33prC
|
||||||
|
2MZWxs5C+YSRbpSYmYg6T7jDqpvcuWn4Ta5L04g7lsksE5/hSBML6cLGpCmNbqnx
|
||||||
|
l1ZJDs9o4DAzMoRux2N1p+ndY7Rly19bOazTBeYGZ7WwWh8LH/TBuM8o2w74dlgq
|
||||||
|
tUDcKM3uEHZQrIrscK27ef4=
|
||||||
|
-----END CERTIFICATE-----
|
28
test/assets/https/server.key
Normal file
28
test/assets/https/server.key
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCuQv9Dwo9iH+vf
|
||||||
|
HmDgr6eM/O6GNGGUiOTGZ7G79jVjQAPvTg78nANKErcqs5hlMOLGo6hayWbmIw2V
|
||||||
|
hC54RIL+26cBg53B4fyo8D5MJcNEUJBC0lHxTzurgM/ycGhdl4iNV0tdSNhL80iT
|
||||||
|
T5YK6iljYgDmefVZuUnnWvtepp4Z6hSQkkPmyJnRlXZxkFB8x44ncKMr+UBTlujG
|
||||||
|
XftY8GPh2RaFhWMvUDABzEdvyKSp7H2983kGMhIUscA8MKwoiBXz4VF/sHs7FUty
|
||||||
|
qxmclHoPGfPyW+6d1ZOX1yLcT7diHDiq1P351w3XIIUcpXF/d37Og8wofaeMf4Ml
|
||||||
|
LAGs6A1RAgMBAAECggEBAKlDSEDf9XfBO9Gf50e3No76ixDuRi4JffW9eOTytE6w
|
||||||
|
OmIyNtplC8jiPuoKQidgAZYiFwAACqPVPneRSbXmDjtQzXnqBszxHgJWQJykPXPY
|
||||||
|
sRdGxPMYHARs/Q8m4iiubKOlO/3jKL01FLSJpFr7sbHn2qoDoi5BjKhdNjZsrrrJ
|
||||||
|
ikoTCZwmHtxxOd98Rb1/VOt/pQsNaOTtfshMVu/gsw7Ruz7jWMMjSklNBXC2rcIh
|
||||||
|
GGJWO2f/tRpbA+R5efnuUgLFTCHgopsccHDr8c+QhEZkTuKW/jTItspGkylNL0An
|
||||||
|
n7rlkj7rDztwXMfS/ntNJQYki74s0+5NNYkfQtV+ZQUCgYEA1t0YzCZl4YnRn5tw
|
||||||
|
kubXhWhiiAeUjg5tYy9enXmbvybzB6qEPU3H3gyOC53B38bEm3fhcDfS74qPz1Pk
|
||||||
|
XiVDfBNRhMrrOjacTmrzxy+x6B+x0ZcdNhHYlfOvoVKpGs8mT1rlmg6BeFQ12E5K
|
||||||
|
ho+PekAGZx/Mt9MUcPQGVde66ecCgYEAz5/qlljDawUfnSHflXWl9ql9QGQY0WM4
|
||||||
|
w8KFtbG2K6CAhYduIirJ9U6b20GGBW0NKaFip88zGTelYm8TLUBl/gWDuCKbrV3/
|
||||||
|
CFdgo3MUuC8+GIYt+dh+nX1t59KHLERNtNQx2pwDEK9/S250PoYf+O3b0THafZ7I
|
||||||
|
bpwmQShyGAcCgYEAwFIU0R8JkHBQ/sEeaY9QmCwQDdxjHyhQxzfuQ5xHSTkuzczW
|
||||||
|
Ix1M6jdoqYMitw9uig4q7sw49YqcIKLhxVcraZLNI8SR+oBJNnPLEp5havl7q7PM
|
||||||
|
RMqCh+4gZZDcpo+Gpf8hhty3DKKrs5qYYIt9jJpkYMf48Q1xvYzfYtT/jD8CgYAm
|
||||||
|
5BmZF/9i6I7HbDTpViREU/M2QIm1jxRu9tz879Dj0yi/2mJy2/kAjjz7kQZ9tbOl
|
||||||
|
fKlyLYmwy4+bJJs++rUgJABMWY83pkfDVDqx4ziaV58WEOxDxJ3S+k/AANt5G0JD
|
||||||
|
AQxlmpuoYHdDtejoXU9X3ZYzVVdL+JYqwe0Yf27/uQKBgQDR1WFoSAppR3ocPzn8
|
||||||
|
jP+xF9tNxDJ3Wpohcnw7JAoakbMzP4LQtjwPmRTf9ORgqDjKGanP5m+gTfiPOUos
|
||||||
|
7eWNVFNKg+7X7Ej8Rst9s6JNrEhsKgz0u0vAvjwsZDCsy6WwrlrdZZkvwb5S+3Ls
|
||||||
|
M5aHThIzFI2fJX8HGAcnudkKIw==
|
||||||
|
-----END PRIVATE KEY-----
|
@ -1,8 +1,13 @@
|
|||||||
import type { Server } from 'http';
|
import type { Server } from 'http';
|
||||||
import request from 'supertest';
|
import request from 'supertest';
|
||||||
|
import type { BaseHttpServerOptions } from '../../../src/server/BaseHttpServerFactory';
|
||||||
import { BaseHttpServerFactory } from '../../../src/server/BaseHttpServerFactory';
|
import { BaseHttpServerFactory } from '../../../src/server/BaseHttpServerFactory';
|
||||||
import type { HttpHandler } from '../../../src/server/HttpHandler';
|
import type { HttpHandler } from '../../../src/server/HttpHandler';
|
||||||
import type { HttpResponse } from '../../../src/server/HttpResponse';
|
import type { HttpResponse } from '../../../src/server/HttpResponse';
|
||||||
|
import { joinFilePath } from '../../../src/util/PathUtil';
|
||||||
|
import { getPort } from '../../util/Util';
|
||||||
|
|
||||||
|
const port = getPort('BaseHttpServerFactory');
|
||||||
|
|
||||||
const handler: jest.Mocked<HttpHandler> = {
|
const handler: jest.Mocked<HttpHandler> = {
|
||||||
handleSafe: jest.fn(async(input: { response: HttpResponse }): Promise<void> => {
|
handleSafe: jest.fn(async(input: { response: HttpResponse }): Promise<void> => {
|
||||||
@ -11,58 +16,79 @@ const handler: jest.Mocked<HttpHandler> = {
|
|||||||
}),
|
}),
|
||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
describe('BaseHttpServerFactory', (): void => {
|
describe('A BaseHttpServerFactory', (): void => {
|
||||||
let server: Server;
|
let server: Server;
|
||||||
|
|
||||||
beforeAll(async(): Promise<void> => {
|
const options: [string, BaseHttpServerOptions | undefined][] = [
|
||||||
const factory = new BaseHttpServerFactory(handler);
|
[ 'http', undefined ],
|
||||||
server = factory.startServer(5555);
|
[ 'https', {
|
||||||
});
|
https: true,
|
||||||
|
key: joinFilePath(__dirname, '../../assets/https/server.key'),
|
||||||
|
cert: joinFilePath(__dirname, '../../assets/https/server.cert'),
|
||||||
|
}],
|
||||||
|
];
|
||||||
|
|
||||||
afterAll(async(): Promise<void> => {
|
describe.each(options)('with %s', (protocol, httpOptions): void => {
|
||||||
server.close();
|
let rejectTls: string | undefined;
|
||||||
});
|
beforeAll(async(): Promise<void> => {
|
||||||
|
// Allow self-signed certificate
|
||||||
|
rejectTls = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
|
||||||
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||||
|
|
||||||
it('sends incoming requests to the handler.', async(): Promise<void> => {
|
const factory = new BaseHttpServerFactory(handler, httpOptions);
|
||||||
await request(server).get('/').set('Host', 'test.com').expect(200);
|
server = factory.startServer(port);
|
||||||
|
|
||||||
expect(handler.handleSafe).toHaveBeenCalledTimes(1);
|
|
||||||
expect(handler.handleSafe).toHaveBeenLastCalledWith({
|
|
||||||
request: expect.objectContaining({
|
|
||||||
headers: expect.objectContaining({ host: 'test.com' }),
|
|
||||||
}),
|
|
||||||
response: expect.objectContaining({}),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('returns a 404 when the handler does not do anything.', async(): Promise<void> => {
|
|
||||||
handler.handleSafe.mockResolvedValueOnce(undefined);
|
|
||||||
|
|
||||||
await expect(request(server).get('/').expect(404)).resolves.toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('writes an error to the HTTP response.', async(): Promise<void> => {
|
|
||||||
handler.handleSafe.mockRejectedValueOnce(new Error('dummyError'));
|
|
||||||
|
|
||||||
const res = await request(server).get('/').expect(500);
|
|
||||||
expect(res.headers['content-type']).toBe('text/plain; charset=utf-8');
|
|
||||||
expect(res.text).toContain('dummyError');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not write an error if the response had been started.', async(): Promise<void> => {
|
|
||||||
handler.handleSafe.mockImplementationOnce(async(input: { response: HttpResponse }): Promise<void> => {
|
|
||||||
input.response.write('content');
|
|
||||||
throw new Error('dummyError');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const res = await request(server).get('/');
|
beforeEach(async(): Promise<void> => {
|
||||||
expect(res.text).not.toContain('dummyError');
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws unknown errors if its handler throw non-Error objects.', async(): Promise<void> => {
|
afterAll(async(): Promise<void> => {
|
||||||
handler.handleSafe.mockRejectedValueOnce('apple');
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = rejectTls;
|
||||||
|
server.close();
|
||||||
|
});
|
||||||
|
|
||||||
const res = await request(server).get('/').expect(500);
|
it('sends incoming requests to the handler.', async(): Promise<void> => {
|
||||||
expect(res.text).toContain('Unknown error.');
|
await request(server).get('/').set('Host', 'test.com').expect(200);
|
||||||
|
|
||||||
|
expect(handler.handleSafe).toHaveBeenCalledTimes(1);
|
||||||
|
expect(handler.handleSafe).toHaveBeenLastCalledWith({
|
||||||
|
request: expect.objectContaining({
|
||||||
|
headers: expect.objectContaining({ host: 'test.com' }),
|
||||||
|
}),
|
||||||
|
response: expect.objectContaining({}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns a 404 when the handler does not do anything.', async(): Promise<void> => {
|
||||||
|
handler.handleSafe.mockResolvedValueOnce(undefined);
|
||||||
|
|
||||||
|
await expect(request(server).get('/').expect(404)).resolves.toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('writes an error to the HTTP response.', async(): Promise<void> => {
|
||||||
|
handler.handleSafe.mockRejectedValueOnce(new Error('dummyError'));
|
||||||
|
|
||||||
|
const res = await request(server).get('/').expect(500);
|
||||||
|
expect(res.headers['content-type']).toBe('text/plain; charset=utf-8');
|
||||||
|
expect(res.text).toContain('dummyError');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not write an error if the response had been started.', async(): Promise<void> => {
|
||||||
|
handler.handleSafe.mockImplementationOnce(async(input: { response: HttpResponse }): Promise<void> => {
|
||||||
|
input.response.write('content');
|
||||||
|
throw new Error('dummyError');
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await request(server).get('/');
|
||||||
|
expect(res.text).not.toContain('dummyError');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws unknown errors if its handler throw non-Error objects.', async(): Promise<void> => {
|
||||||
|
handler.handleSafe.mockRejectedValueOnce('apple');
|
||||||
|
|
||||||
|
const res = await request(server).get('/').expect(500);
|
||||||
|
expect(res.text).toContain('Unknown error.');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -6,6 +6,7 @@ import type { SystemError } from '../../src/util/errors/SystemError';
|
|||||||
|
|
||||||
/* eslint-disable @typescript-eslint/naming-convention */
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
const portNames = [
|
const portNames = [
|
||||||
|
// Integration
|
||||||
'DynamicPods',
|
'DynamicPods',
|
||||||
'Identity',
|
'Identity',
|
||||||
'LpdHandlerWithAuth',
|
'LpdHandlerWithAuth',
|
||||||
@ -17,6 +18,8 @@ const portNames = [
|
|||||||
'SparqlStorage',
|
'SparqlStorage',
|
||||||
'Subdomains',
|
'Subdomains',
|
||||||
'WebSocketsProtocol',
|
'WebSocketsProtocol',
|
||||||
|
// Unit
|
||||||
|
'BaseHttpServerFactory',
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export function getPort(name: typeof portNames[number]): number {
|
export function getPort(name: typeof portNames[number]): number {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user