-
Notifications
You must be signed in to change notification settings - Fork 121
/
ExpressHttpServer.test.ts
142 lines (121 loc) · 5.11 KB
/
ExpressHttpServer.test.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import type { Server } from 'http';
import request from 'supertest';
import WebSocket from 'ws';
import { ExpressHttpServer } from '../../../src/server/ExpressHttpServer';
import { HttpHandler } from '../../../src/server/HttpHandler';
import type { HttpRequest } from '../../../src/server/HttpRequest';
import type { HttpResponse } from '../../../src/server/HttpResponse';
import type { WebSocketHandler } from '../../../src/server/WebSocketHandler';
import SpyInstance = jest.SpyInstance;
const handle = async(input: { request: HttpRequest; response: HttpResponse }): Promise<void> => {
input.response.writeHead(200);
input.response.end();
};
class SimpleHttpHandler extends HttpHandler {
public async handle(input: { request: HttpRequest; response: HttpResponse }): Promise<void> {
return handle(input);
}
}
class SimpleWebSocketHandler implements WebSocketHandler {
public host: any;
public handle(socket: WebSocket, upgradeRequest: HttpRequest): void {
socket.send('SimpleWebSocketHandler');
socket.close();
this.host = upgradeRequest.headers.host;
}
}
describe('ExpressHttpServer', (): void => {
let server: Server;
let canHandleJest: jest.Mock<Promise<void>, []>;
let handleJest: jest.Mock<Promise<void>, [any]>;
let httpHandler: SimpleHttpHandler;
let webSocketHandler: SimpleWebSocketHandler;
let mock: SpyInstance;
beforeAll(async(): Promise<void> => {
// Prevent test from writing to stderr
mock = jest.spyOn(process.stderr, 'write').mockImplementation((): boolean => true);
});
beforeEach(async(): Promise<void> => {
httpHandler = new SimpleHttpHandler();
webSocketHandler = new SimpleWebSocketHandler();
canHandleJest = jest.fn(async(): Promise<void> => undefined);
handleJest = jest.fn(async(input): Promise<void> => handle(input));
httpHandler.canHandle = canHandleJest;
httpHandler.handle = handleJest;
const expressServer = new ExpressHttpServer(httpHandler, webSocketHandler);
server = expressServer.listen(5555);
});
afterEach(async(): Promise<void> => {
// Close server
server.close();
});
afterAll(async(): Promise<void> => {
mock.mockReset();
});
it('sends server identification in the X-Powered-By header.', async(): Promise<void> => {
const res = await request(server).get('/');
expect(res.header).toEqual(expect.objectContaining({
'x-powered-by': 'Community Solid Server',
}));
});
it('returns CORS headers for an OPTIONS request.', async(): Promise<void> => {
const res = await request(server)
.options('/')
.set('Access-Control-Request-Headers', 'content-type')
.set('Access-Control-Request-Method', 'POST')
.set('Host', 'test.com')
.expect(204);
expect(res.header).toEqual(expect.objectContaining({
'access-control-allow-origin': '*',
'access-control-allow-headers': 'content-type',
}));
const corsMethods = res.header['access-control-allow-methods'].split(',')
.map((method: string): string => method.trim());
const allowedMethods = [ 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE' ];
expect(corsMethods).toEqual(expect.arrayContaining(allowedMethods));
expect(corsMethods).toHaveLength(allowedMethods.length);
});
it('specifies CORS origin header if an origin was supplied.', async(): Promise<void> => {
const res = await request(server).get('/').set('origin', 'test.com').expect(200);
expect(res.header).toEqual(expect.objectContaining({ 'access-control-allow-origin': 'test.com' }));
});
it('sends incoming requests to the handler.', async(): Promise<void> => {
await request(server).get('/').set('Host', 'test.com').expect(200);
expect(canHandleJest).toHaveBeenCalledTimes(1);
expect(handleJest).toHaveBeenCalledTimes(1);
expect(handleJest).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> => {
httpHandler.handle = async(input): Promise<void> => {
expect(input).toBeDefined();
};
await request(server).get('/').expect(404);
});
it('catches errors thrown by its handler.', async(): Promise<void> => {
httpHandler.handle = async(): Promise<void> => {
throw new Error('dummyError');
};
const res = await request(server).get('/').expect(500);
expect(res.text).toContain('dummyError');
});
it('throws unknown errors if its handler throw non-Error objects.', async(): Promise<void> => {
httpHandler.handle = async(): Promise<void> => {
throw 'apple';
};
const res = await request(server).get('/').expect(500);
expect(res.text).toContain('Unknown error.');
});
it('passes WebSocket requests to the WebSocket handler.', async(): Promise<void> => {
const client = new WebSocket('ws://localhost:5555');
const text = await new Promise((resolve): void => {
client.on('message', resolve);
});
expect(text).toBe('SimpleWebSocketHandler');
expect(webSocketHandler.host).toBe('localhost:5555');
});
});