Skip to content

Commit 9d2afb8

Browse files
committed
fix: koa tests [wip]
Signed-off-by: seven <[email protected]>
1 parent dd420ba commit 9d2afb8

File tree

8 files changed

+318
-45
lines changed

8 files changed

+318
-45
lines changed

package-lock.json

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

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,12 @@
5151
},
5252
"devDependencies": {
5353
"@eslint/js": "^9.12.0",
54+
"@koa/router": "^13.1.0",
5455
"@types/debug": "^4.1.12",
5556
"@types/express": "^5.0.0",
56-
"@types/koa__router": "^12.0.4",
5757
"@types/jest": "^29.5.13",
5858
"@types/koa": "^2.15.0",
59+
"@types/koa__router": "^12.0.4",
5960
"@types/node": "^22.7.4",
6061
"@typescript-eslint/eslint-plugin": "^8.8.0",
6162
"@typescript-eslint/parser": "^8.8.0",
@@ -68,7 +69,7 @@
6869
"husky": "^9.1.6",
6970
"jest": "^29.7.0",
7071
"koa": "^2.15.3",
71-
"@koa/router": "^13.1.0",
72+
"koa-body": "^6.0.1",
7273
"prettier": "^3.3.3",
7374
"ts-jest": "^29.2.5",
7475
"ts-node": "^10.9.2",

src/context.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { Context, Event } from './types';
22
import ServerlessRequest from './serverlessRequest';
33
import url from 'node:url';
4-
import ServerlessResponse from './serverlessResponse';
54
import { debug } from './common';
65

76
// const requestRemoteAddress = (event) => {
@@ -16,6 +15,7 @@ export const constructFrameworkContext = (event: Event, context: Context) => {
1615
const request = new ServerlessRequest({
1716
method: event.httpMethod,
1817
headers: event.headers,
18+
path: event.path,
1919
body:
2020
event.body !== undefined && event.body !== null
2121
? Buffer.from(event.body, event.isBase64Encoded ? 'base64' : 'utf8')
@@ -27,7 +27,6 @@ export const constructFrameworkContext = (event: Event, context: Context) => {
2727
}),
2828
isBase64Encoded: event.isBase64Encoded,
2929
});
30-
const response = new ServerlessResponse(request);
3130

32-
return { request, response };
31+
return { request };
3332
};

src/framework.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Express } from 'express';
2+
import Application from 'koa';
3+
import ServerlessResponse from './serverlessResponse';
4+
import ServerlessRequest from './serverlessRequest';
5+
6+
// eslint-disable-next-line
7+
const callableFn = (callback: (req: any, res: any) => Promise<void>) => {
8+
return async (request: ServerlessRequest) => {
9+
const response = new ServerlessResponse(request);
10+
11+
callback(request, response);
12+
13+
return response;
14+
};
15+
};
16+
17+
export const constructFramework = (app: Express | Application) => {
18+
if (app instanceof Application) {
19+
return callableFn(app.callback());
20+
} else if (typeof app === 'function') {
21+
return callableFn(app);
22+
} else {
23+
throw new Error(`Unsupported framework ${app}`);
24+
}
25+
};

src/index.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
import { Express } from 'express';
22
import Application from 'koa';
33
import { ServerlessAdapter } from './types';
4-
import sendRequest from './sendRequest';
54
import { IncomingHttpHeaders } from 'http';
65
import { constructFrameworkContext } from './context';
76
import { buildResponse, waitForStreamComplete } from './transport';
7+
import { constructFramework } from './framework';
88

99
const serverlessAdapter: ServerlessAdapter = (app: Express | Application) => {
10+
const serverlessFramework = constructFramework(app);
1011
return async (event, context) => {
11-
const { request, response } = constructFrameworkContext(event, context);
12+
const { request } = constructFrameworkContext(event, context);
1213

1314
try {
14-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
15-
// @ts-expect-error
16-
await sendRequest(app, request, response);
15+
const response = await serverlessFramework(request);
1716
await waitForStreamComplete(response);
1817
return buildResponse({ request, response });
1918
} catch (err) {

src/serverlessRequest.ts

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const HTTPS_PORT = 443;
66
interface ServerlessRequestOptions {
77
method: string;
88
url: string;
9+
path: string;
910
headers: { [key: string]: string | number };
1011
body: Buffer | string | undefined;
1112
remoteAddress: string;
@@ -21,46 +22,39 @@ export default class ServerlessRequest extends IncomingMessage {
2122

2223
isBase64Encoded: boolean;
2324

24-
constructor({
25-
method,
26-
url,
27-
headers,
28-
body,
29-
remoteAddress,
30-
isBase64Encoded,
31-
}: ServerlessRequestOptions) {
25+
constructor(request: ServerlessRequestOptions) {
3226
super({
3327
encrypted: true,
3428
readable: false,
35-
remoteAddress,
29+
remoteAddress: request.remoteAddress,
3630
address: () => ({ port: HTTPS_PORT }),
3731
end: NO_OP,
3832
destroy: NO_OP,
33+
path: request.path,
3934
} as unknown as Socket);
4035

4136
const combinedHeaders = Object.fromEntries(
4237
Object.entries({
43-
...headers,
44-
'content-length': Buffer.byteLength(body ?? '').toString(),
38+
...request.headers,
39+
'content-length': Buffer.byteLength(request.body ?? '').toString(),
4540
}).map(([key, value]) => [key.toLowerCase(), value]),
4641
);
4742

4843
Object.assign(this, {
44+
...request,
4945
complete: true,
5046
httpVersion: '1.1',
5147
httpVersionMajor: '1',
5248
httpVersionMinor: '1',
53-
method,
54-
url,
5549
headers: combinedHeaders,
5650
});
5751

58-
this.body = body;
59-
this.ip = remoteAddress;
60-
this.isBase64Encoded = isBase64Encoded;
52+
this.body = request.body;
53+
this.ip = request.remoteAddress;
54+
this.isBase64Encoded = request.isBase64Encoded;
6155

6256
this._read = () => {
63-
this.push(body);
57+
this.push(request.body);
6458
this.push(null);
6559
};
6660
}

src/transport.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@ export const waitForStreamComplete = (stream: Writable): Promise<Writable> => {
88
}
99

1010
return new Promise((resolve, reject) => {
11-
stream.once('error', complete);
12-
stream.once('end', complete);
13-
stream.once('finish', complete);
14-
1511
let isComplete = false;
1612

1713
function complete(err?: Error) {
@@ -31,6 +27,10 @@ export const waitForStreamComplete = (stream: Writable): Promise<Writable> => {
3127
resolve(stream);
3228
}
3329
}
30+
31+
stream.once('error', complete);
32+
stream.once('end', complete);
33+
stream.once('finish', complete);
3434
});
3535
};
3636

tests/index-koa.test.ts

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Koa from 'koa';
22
import Router from '@koa/router';
3+
import koaBody from 'koa-body';
34
import serverlessAdapter from '../src';
45
import { defaultContext, defaultEvent } from './fixtures/fcContext';
56

@@ -8,27 +9,26 @@ describe('koa', () => {
89
let router: Router;
910
beforeEach(() => {
1011
app = new Koa();
12+
app.use(koaBody());
1113
router = new Router();
1214
});
1315

1416
it('basic middleware should set statusCode and default body', async () => {
1517
router.get('/api/test', (ctx) => {
16-
ctx.status = 200;
17-
ctx.body = 'Hello, world!';
18+
ctx.status = 418;
19+
ctx.body = 'Hello, world koa!';
1820
});
1921
app.use(router.routes());
2022

2123
const response = await serverlessAdapter(app)(defaultEvent, defaultContext);
2224

2325
expect(response.statusCode).toEqual(418);
24-
expect(response.body).toEqual(`I'm a teapot`);
26+
expect(response.body).toEqual('Hello, world koa!');
2527
});
2628

2729
it('basic middleware should get text body', async () => {
2830
router.get('/api/test', (ctx) => {
2931
ctx.status = 200;
30-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
31-
// @ts-expect-error
3232
ctx.body = ctx.request.body;
3333
});
3434
app.use(router.routes());
@@ -53,21 +53,15 @@ describe('koa', () => {
5353
it('basic middleware should get json body', async () => {
5454
router.get('/api/test', (ctx) => {
5555
ctx.status = 200;
56-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
57-
// @ts-expect-error
5856
ctx.body = ctx.request.body.hello;
5957
});
6058

6159
const response = await serverlessAdapter(app)(
6260
{
6361
...defaultEvent,
6462
httpMethod: 'GET',
65-
body: JSON.stringify({
66-
hello: 'world',
67-
}),
68-
headers: {
69-
'Content-Type': 'application/json',
70-
},
63+
body: JSON.stringify({ hello: 'world' }),
64+
headers: { 'Content-Type': 'application/json' },
7165
},
7266
defaultContext,
7367
);
@@ -80,7 +74,6 @@ describe('koa', () => {
8074
router.get('/api/test', (ctx) => {
8175
ctx.status = 200;
8276
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
83-
// @ts-expect-error
8477
ctx.body = ctx.request.body.hello;
8578
});
8679

0 commit comments

Comments
 (0)