Skip to content

Commit 5774eec

Browse files
committed
Switch from express to web-listener
1 parent a83e948 commit 5774eec

37 files changed

Lines changed: 577 additions & 1702 deletions

backend/package-lock.json

Lines changed: 43 additions & 667 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/package.json

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,19 @@
66
"lint": "tsc && prettier --check .",
77
"build": "rm -rf build && rollup --config rollup.config.mjs && cp -r src/static build && chmod +x build/index.js",
88
"build-watch": "rollup --watch --config rollup.config.mjs",
9-
"start": "npm run build && NODE_ENV=development build/index.js",
109
"test": "lean-test --preprocess tsc --parallel"
1110
},
1211
"dependencies": {
1312
"authentication-backend": "1.3.x",
1413
"collection-storage": "3.x",
15-
"express": "5.x",
16-
"express-static-gzip": "3.x",
1714
"ioredis": "5.x",
1815
"json-immutability-helper": "4.0.x",
1916
"jwt-simple": "0.5.x",
2017
"mongodb": "6.x",
2118
"pg": "8.x",
2219
"pwd-hasher": "2.x",
23-
"shared-reducer": "5.x",
24-
"websocket-express": "4.x",
20+
"shared-reducer": "6.x",
21+
"web-listener": "0.13.2",
2522
"ws": "8.x"
2623
},
2724
"devDependencies": {

backend/src/api-tests/giphy.test.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1+
import { getAddressURL, Router, sendJSON, WebListener } from 'web-listener';
12
import request from 'superwstest';
2-
import { WebSocketExpress } from 'websocket-express';
33
import { TestLogger } from './TestLogger';
44
import { testConfig } from './testConfig';
5-
import { addressToString, testServerRunner } from './testServerRunner';
5+
import { testServerRunner } from './testServerRunner';
66
import type { GiphyResponse } from '../services/GiphyService';
77
import { appFactory } from '../app';
88

99
describe('API giphy', () => {
1010
const MOCK_GIPHY = testServerRunner(async () => {
11-
const giphyApp = new WebSocketExpress();
1211
const response: GiphyResponse = {
1312
meta: { status: 200 },
1413
data: [
@@ -34,20 +33,19 @@ describe('API giphy', () => {
3433
],
3534
pagination: {},
3635
};
37-
giphyApp.use(WebSocketExpress.urlencoded({ extended: false }));
38-
giphyApp.get('/gifs/search', (_, res) => {
39-
res.json(response);
40-
});
36+
const giphyApp = new WebListener(
37+
new Router().get('/gifs/search', (_, res) => sendJSON(res, response)),
38+
);
4139

42-
return { run: giphyApp.createServer() };
40+
return { run: giphyApp };
4341
});
4442

4543
const PROPS = testServerRunner(async (getTyped) => {
4644
const app = await appFactory(
4745
new TestLogger(),
4846
testConfig({
4947
giphy: {
50-
baseUrl: addressToString(getTyped(MOCK_GIPHY).server.address()),
48+
baseUrl: getAddressURL(getTyped(MOCK_GIPHY).server.address()),
5149
apiKey: 'my-giphy-key',
5250
},
5351
}),

backend/src/api-tests/retros.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ describe('API retros', () => {
9494
expect(response.body.token).toBeTruthy();
9595
});
9696

97-
it('responds HTTP Bad Request if data is missing', async (props) => {
97+
it('responds HTTP Unprocessable Entity if data is missing', async (props) => {
9898
const { server, hooks } = props.getTyped(PROPS);
9999

100100
const userToken = getUserToken(hooks, 'me');
@@ -103,9 +103,9 @@ describe('API retros', () => {
103103
.post('/api/retros')
104104
.send({ slug: 'new-retro', name: 'Meh' })
105105
.set('Authorization', `Bearer ${userToken}`)
106-
.expect(400);
106+
.expect(422);
107107

108-
expect(response.body.error).toEqual('Expected string');
108+
expect(response.body.error).toEqual('Expected string at .password');
109109
});
110110

111111
it('responds HTTP Bad Request if data is blank', async (props) => {

backend/src/api-tests/sso.test.ts

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,27 @@
1-
import { WebSocketExpress } from 'websocket-express';
1+
import {
2+
getAddressURL,
3+
requestHandler,
4+
sendJSON,
5+
WebListener,
6+
} from 'web-listener';
27
import request from 'superwstest';
38
import jwt from 'jwt-simple';
49
import { TestLogger } from './TestLogger';
510
import { testConfig } from './testConfig';
6-
import { testServerRunner, addressToString } from './testServerRunner';
11+
import { testServerRunner } from './testServerRunner';
712
import { appFactory } from '../app';
813

914
describe('/api/sso/service', () => {
1015
const MOCK_SSO = testServerRunner(() => {
11-
const ssoApp = new WebSocketExpress();
12-
ssoApp.use(WebSocketExpress.urlencoded({ extended: false }));
13-
ssoApp.get('/', (_, res) => {
14-
res.json({
15-
aud: 'my-client-id',
16-
sub: 'my-external-id',
17-
});
18-
});
19-
return { run: ssoApp.createServer() };
16+
const ssoApp = new WebListener(
17+
requestHandler((_, res) =>
18+
sendJSON(res, {
19+
aud: 'my-client-id',
20+
sub: 'my-external-id',
21+
}),
22+
),
23+
);
24+
return { run: ssoApp };
2025
});
2126

2227
const APP = testServerRunner(async (getTyped) => ({
@@ -27,7 +32,7 @@ describe('/api/sso/service', () => {
2732
google: {
2833
clientId: 'my-client-id',
2934
authUrl: 'foo',
30-
tokenInfoUrl: addressToString(getTyped(MOCK_SSO).server.address()),
35+
tokenInfoUrl: getAddressURL(getTyped(MOCK_SSO).server.address()),
3136
},
3237
},
3338
}),

backend/src/api-tests/static.test.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { WebSocketExpress } from 'websocket-express';
21
import request from 'superwstest';
32
import { TestLogger } from './TestLogger';
43
import { testConfig } from './testConfig';
5-
import { testServerRunner, addressToString } from './testServerRunner';
4+
import { testServerRunner } from './testServerRunner';
65
import { appFactory } from '../app';
6+
import { getAddressURL, requestHandler, WebListener } from 'web-listener';
77

88
describe('API static content', () => {
99
describe('Embedded', () => {
@@ -18,7 +18,7 @@ describe('API static content', () => {
1818
.get('/')
1919
.expect(200)
2020
.expect('Content-Type', /text\/html/)
21-
.expect('Vary', 'Accept-Encoding');
21+
.expect('Vary', 'accept-encoding');
2222

2323
expect(response.text).toContain('<title>Example Static Resource</title>');
2424
});
@@ -43,7 +43,7 @@ describe('API static content', () => {
4343
.expect(200)
4444
.expect('Content-Type', /text\/html/)
4545
.expect('Content-Encoding', 'gzip')
46-
.expect('Vary', 'Accept-Encoding');
46+
.expect('Vary', 'accept-encoding');
4747

4848
expect(response.text).toContain(
4949
'<title>Example Compressed Static Resource</title>',
@@ -57,7 +57,7 @@ describe('API static content', () => {
5757
.get('/foobar')
5858
.expect(200)
5959
.expect('Content-Type', /text\/html/)
60-
.expect('Vary', 'Accept-Encoding');
60+
.expect('Vary', 'accept-encoding');
6161

6262
expect(response.text).toContain('<title>Example Static Resource</title>');
6363
});
@@ -71,7 +71,7 @@ describe('API static content', () => {
7171
.expect(200)
7272
.expect('Content-Type', /text\/html/)
7373
.expect('Content-Encoding', 'gzip')
74-
.expect('Vary', 'Accept-Encoding');
74+
.expect('Vary', 'accept-encoding');
7575

7676
expect(response.text).toContain('<title>Example Static Resource</title>');
7777
});
@@ -84,7 +84,7 @@ describe('API static content', () => {
8484
.expect(200);
8585

8686
const vary = response.header['vary'] || '';
87-
expect(vary).not(toContain('Content-Type'));
87+
expect(vary).not(toContain('content-type'));
8888
});
8989

9090
it('adds common headers', async (props) => {
@@ -134,18 +134,17 @@ describe('API static content', () => {
134134

135135
describe('Proxy', () => {
136136
const PROXY = testServerRunner(() => {
137-
const proxyApp = new WebSocketExpress();
138-
proxyApp.get('/', (_, res) => {
139-
res.send('proxied content here');
140-
});
141-
return { run: proxyApp.createServer() };
137+
const proxyApp = new WebListener(
138+
requestHandler((_, res) => res.end('proxied content here')),
139+
);
140+
return { run: proxyApp };
142141
});
143142

144143
const PROPS = testServerRunner(async (getTyped) => ({
145144
run: await appFactory(
146145
new TestLogger(),
147146
testConfig({
148-
forwardHost: addressToString(getTyped(PROXY).server.address()),
147+
forwardHost: getAddressURL(getTyped(PROXY).server.address()),
149148
}),
150149
),
151150
}));

backend/src/api-tests/testConfig.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const baseTestConfig: ConfigT = {
1616
serverBindAddress: '',
1717
trustProxy: false,
1818
log: { file: '' },
19-
analytics: { eventDetail: 'none', clientErrorDetail: 'none' },
19+
analytics: { eventDetail: 'none', clientErrorDetail: 'message' },
2020
password: { workFactor: 1, secretPepper: '' },
2121
passwordCheck: { baseUrl: '' },
2222
token: { secretPassphrase: '' },
Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,32 @@
11
import type { Server } from 'node:http';
2-
import type { AddressInfo } from 'node:net';
2+
import { type WebListener } from 'web-listener';
33
import type { TypedParameter } from 'lean-test';
44
import { App } from '../app';
5+
import type { TestLogger } from './TestLogger';
56

6-
type Runnable = Server | App;
7+
type Runnable = WebListener | App;
78
type MaybePromise<T> = T | Promise<T>;
89

9-
export function addressToString(addr: AddressInfo | string | null): string {
10-
if (!addr) {
11-
throw new Error('Test server is not running');
12-
}
13-
if (typeof addr === 'string') {
14-
return addr;
15-
}
16-
const { address, family, port } = addr;
17-
const host = family === 'IPv6' ? `[${address}]` : address;
18-
return `http://${host}:${port}`;
19-
}
20-
21-
export const testServerRunner = <T extends { run: Runnable }>(
10+
export const testServerRunner = <
11+
T extends { run: Runnable; logger?: TestLogger },
12+
>(
2213
serverFn: (getTyped: <T>(key: TypedParameter<T>) => T) => MaybePromise<T>,
2314
) =>
2415
beforeEach<{ server: Server } & Omit<T, 'run'>>(
2516
async ({ setParameter, getTyped }) => {
26-
let server: Server;
2717
const { run, ...extras } = await serverFn(getTyped);
28-
if (run instanceof App) {
29-
server = run.express.createServer();
30-
} else {
31-
server = run;
32-
}
18+
const listener = run instanceof App ? run.listener : run;
19+
const server = await listener.listen(0, '127.0.0.1');
3320
setParameter({ ...extras, server });
34-
await new Promise<void>((resolve) =>
35-
server.listen(0, '127.0.0.1', resolve),
36-
);
3721

3822
return async () => {
39-
await new Promise((resolve) => server.close(resolve));
23+
await server.closeWithTimeout('end of test', 0);
4024
if (run instanceof App) {
4125
await run.close();
4226
}
27+
for (const log of extras.logger?.logs ?? []) {
28+
console.log(log);
29+
}
4330
};
4431
},
4532
);

0 commit comments

Comments
 (0)