lambder 2.0.18 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Readme.md +162 -41
- package/dist/Lambder.d.ts +154 -46
- package/dist/Lambder.js +312 -166
- package/dist/LambderCaller.js +6 -3
- package/dist/LambderContext.d.ts +20 -9
- package/dist/LambderContext.js +57 -17
- package/dist/LambderCors.d.ts +12 -0
- package/dist/LambderCors.js +30 -0
- package/dist/LambderHtml.d.ts +33 -0
- package/dist/LambderHtml.js +62 -0
- package/dist/LambderMSW.d.ts +16 -1
- package/dist/LambderMSW.js +5 -9
- package/dist/LambderPublicFiles.d.ts +47 -0
- package/dist/LambderPublicFiles.js +108 -0
- package/dist/LambderResolver.d.ts +30 -31
- package/dist/LambderResolver.js +29 -43
- package/dist/LambderResponse.d.ts +71 -0
- package/dist/LambderResponse.js +196 -0
- package/dist/LambderResponseBuilder.d.ts +58 -33
- package/dist/LambderResponseBuilder.js +114 -167
- package/dist/LambderRouting.d.ts +23 -0
- package/dist/LambderRouting.js +67 -0
- package/dist/LambderSessionController.d.ts +13 -1
- package/dist/LambderSessionController.js +33 -10
- package/dist/LambderSessionManager.d.ts +3 -1
- package/dist/LambderSessionManager.js +15 -6
- package/dist/LambderTemplatingEngine.d.ts +87 -0
- package/dist/LambderTemplatingEngine.js +156 -0
- package/dist/index.d.ts +14 -2
- package/dist/index.js +10 -1
- package/dist/node-polyfills.d.ts +4 -2
- package/dist/node-polyfills.js +28 -0
- package/package.json +7 -5
- package/.eslintrc.cjs +0 -26
- package/.vscode/settings.json +0 -26
- package/deploy +0 -22
- package/dist/LambderUtils.d.ts +0 -10
- package/dist/LambderUtils.js +0 -70
- package/docs/DYNAMODB_SETUP.md +0 -96
- package/docs/LAMBDER_MSW.md +0 -409
- package/docs/TYPE_SAFE_QUICK_START.md +0 -77
- package/examples/msw-testing-example.ts +0 -280
- package/examples/secure-session-example.ts +0 -207
- package/examples/zod-chained-api-example.ts +0 -63
- package/src/Lambder.ts +0 -430
- package/src/LambderApiContract.ts +0 -20
- package/src/LambderCaller.ts +0 -238
- package/src/LambderContext.ts +0 -78
- package/src/LambderMSW.ts +0 -180
- package/src/LambderResolver.ts +0 -101
- package/src/LambderResponseBuilder.ts +0 -332
- package/src/LambderSessionController.ts +0 -114
- package/src/LambderSessionManager.ts +0 -217
- package/src/LambderUtils.ts +0 -75
- package/src/index.ts +0 -17
- package/src/node-polyfills.ts +0 -27
- package/tests/error-handling.test.ts +0 -585
- package/tests/file-serving.test.ts +0 -194
- package/tests/fixtures/public/index.html +0 -1
- package/tests/fixtures/public/main.css +0 -1
- package/tests/hooks.test.ts +0 -561
- package/tests/output-type-runtime.test.ts +0 -381
- package/tests/redirect.test.ts +0 -88
- package/tests/routes.test.ts +0 -543
- package/tests/session.test.ts +0 -1083
- package/tests/use-plugin.test.ts +0 -460
- package/tsconfig.json +0 -24
package/tests/routes.test.ts
DELETED
|
@@ -1,543 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Routes Tests
|
|
3
|
-
*
|
|
4
|
-
* Tests for addRoute and addSessionRoute functionality including:
|
|
5
|
-
* - String path matching
|
|
6
|
-
* - Path parameter extraction
|
|
7
|
-
* - RegExp route matching
|
|
8
|
-
* - Function-based conditional routing
|
|
9
|
-
* - Session-protected routes
|
|
10
|
-
* - Route priority/ordering
|
|
11
|
-
* - Wildcard routes
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
15
|
-
import { mockClient } from 'aws-sdk-client-mock';
|
|
16
|
-
import { DynamoDBDocumentClient, GetCommand, PutCommand } from '@aws-sdk/lib-dynamodb';
|
|
17
|
-
import Lambder from '../src/Lambder.js';
|
|
18
|
-
import type { APIGatewayProxyEvent, Context } from 'aws-lambda';
|
|
19
|
-
|
|
20
|
-
// Mock DynamoDB
|
|
21
|
-
const ddbMock = mockClient(DynamoDBDocumentClient);
|
|
22
|
-
|
|
23
|
-
const createMockEvent = (path: string, method: string = 'GET', sessionToken?: string): APIGatewayProxyEvent => ({
|
|
24
|
-
body: null,
|
|
25
|
-
headers: {
|
|
26
|
-
Host: 'localhost',
|
|
27
|
-
Cookie: sessionToken ? `LMDRSESSIONTKID=${sessionToken}` : ''
|
|
28
|
-
},
|
|
29
|
-
multiValueHeaders: {},
|
|
30
|
-
httpMethod: method,
|
|
31
|
-
isBase64Encoded: false,
|
|
32
|
-
path,
|
|
33
|
-
pathParameters: null,
|
|
34
|
-
queryStringParameters: null,
|
|
35
|
-
multiValueQueryStringParameters: null,
|
|
36
|
-
stageVariables: null,
|
|
37
|
-
requestContext: {} as any,
|
|
38
|
-
resource: '',
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const createMockContext = (): Context => ({
|
|
42
|
-
callbackWaitsForEmptyEventLoop: false,
|
|
43
|
-
functionName: 'test',
|
|
44
|
-
functionVersion: '1',
|
|
45
|
-
invokedFunctionArn: 'arn',
|
|
46
|
-
memoryLimitInMB: '128',
|
|
47
|
-
awsRequestId: '123',
|
|
48
|
-
logGroupName: 'group',
|
|
49
|
-
logStreamName: 'stream',
|
|
50
|
-
getRemainingTimeInMillis: () => 1000,
|
|
51
|
-
done: () => {},
|
|
52
|
-
fail: () => {},
|
|
53
|
-
succeed: () => {},
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe('Routes - Basic Path Matching', () => {
|
|
57
|
-
it('should match simple string paths', async () => {
|
|
58
|
-
const lambder = new Lambder({
|
|
59
|
-
publicPath: './public',
|
|
60
|
-
apiPath: '/api'
|
|
61
|
-
})
|
|
62
|
-
.addRoute('/hello', (ctx, res) => {
|
|
63
|
-
return res.html('Hello World');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
const handler = lambder.getHandler();
|
|
67
|
-
const event = createMockEvent('/hello');
|
|
68
|
-
const result = await handler(event, createMockContext());
|
|
69
|
-
|
|
70
|
-
expect(result.statusCode).toBe(200);
|
|
71
|
-
const body = Buffer.from(result.body || '', 'base64').toString();
|
|
72
|
-
expect(body).toBe('Hello World');
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should not match wrong paths', async () => {
|
|
76
|
-
const lambder = new Lambder({
|
|
77
|
-
publicPath: './public',
|
|
78
|
-
apiPath: '/api'
|
|
79
|
-
})
|
|
80
|
-
.addRoute('/hello', (ctx, res) => {
|
|
81
|
-
return res.html('Hello');
|
|
82
|
-
})
|
|
83
|
-
.setRouteFallbackHandler((ctx, res) => {
|
|
84
|
-
return res.status404('Not Found');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
const handler = lambder.getHandler();
|
|
88
|
-
const event = createMockEvent('/goodbye');
|
|
89
|
-
const result = await handler(event, createMockContext());
|
|
90
|
-
|
|
91
|
-
expect(result.statusCode).toBe(404);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('should match multiple routes', async () => {
|
|
95
|
-
const lambder = new Lambder({
|
|
96
|
-
publicPath: './public',
|
|
97
|
-
apiPath: '/api'
|
|
98
|
-
})
|
|
99
|
-
.addRoute('/home', (ctx, res) => {
|
|
100
|
-
return res.html('Home Page');
|
|
101
|
-
})
|
|
102
|
-
.addRoute('/about', (ctx, res) => {
|
|
103
|
-
return res.html('About Page');
|
|
104
|
-
})
|
|
105
|
-
.addRoute('/contact', (ctx, res) => {
|
|
106
|
-
return res.html('Contact Page');
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
const handler = lambder.getHandler();
|
|
110
|
-
|
|
111
|
-
const homeResult = await handler(createMockEvent('/home'), createMockContext());
|
|
112
|
-
expect(Buffer.from(homeResult.body || '', 'base64').toString()).toBe('Home Page');
|
|
113
|
-
|
|
114
|
-
const aboutResult = await handler(createMockEvent('/about'), createMockContext());
|
|
115
|
-
expect(Buffer.from(aboutResult.body || '', 'base64').toString()).toBe('About Page');
|
|
116
|
-
|
|
117
|
-
const contactResult = await handler(createMockEvent('/contact'), createMockContext());
|
|
118
|
-
expect(Buffer.from(contactResult.body || '', 'base64').toString()).toBe('Contact Page');
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
describe('Routes - Path Parameters', () => {
|
|
123
|
-
it('should extract path parameters from string patterns', async () => {
|
|
124
|
-
const lambder = new Lambder({
|
|
125
|
-
publicPath: './public',
|
|
126
|
-
apiPath: '/api'
|
|
127
|
-
})
|
|
128
|
-
.addRoute('/user/:userId', (ctx, res) => {
|
|
129
|
-
return res.json({ userId: ctx.pathParams?.userId });
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
const handler = lambder.getHandler();
|
|
133
|
-
const event = createMockEvent('/user/123');
|
|
134
|
-
const result = await handler(event, createMockContext());
|
|
135
|
-
|
|
136
|
-
expect(result.statusCode).toBe(200);
|
|
137
|
-
const body = JSON.parse(result.body || '{}');
|
|
138
|
-
expect(body.userId).toBe('123');
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('should extract multiple path parameters', async () => {
|
|
142
|
-
const lambder = new Lambder({
|
|
143
|
-
publicPath: './public',
|
|
144
|
-
apiPath: '/api'
|
|
145
|
-
})
|
|
146
|
-
.addRoute('/users/:userId/posts/:postId', (ctx, res) => {
|
|
147
|
-
return res.json({
|
|
148
|
-
userId: ctx.pathParams?.userId,
|
|
149
|
-
postId: ctx.pathParams?.postId
|
|
150
|
-
});
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
const handler = lambder.getHandler();
|
|
154
|
-
const event = createMockEvent('/users/456/posts/789');
|
|
155
|
-
const result = await handler(event, createMockContext());
|
|
156
|
-
|
|
157
|
-
const body = JSON.parse(result.body || '{}');
|
|
158
|
-
expect(body.userId).toBe('456');
|
|
159
|
-
expect(body.postId).toBe('789');
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('should handle optional parameters', async () => {
|
|
163
|
-
const lambder = new Lambder({
|
|
164
|
-
publicPath: './public',
|
|
165
|
-
apiPath: '/api'
|
|
166
|
-
})
|
|
167
|
-
.addRoute('/files/:path*', (ctx, res) => {
|
|
168
|
-
return res.json({ path: ctx.pathParams?.path });
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
const handler = lambder.getHandler();
|
|
172
|
-
const event = createMockEvent('/files/documents/report.pdf');
|
|
173
|
-
const result = await handler(event, createMockContext());
|
|
174
|
-
|
|
175
|
-
const body = JSON.parse(result.body || '{}');
|
|
176
|
-
expect(body.path).toBeTruthy();
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
describe('Routes - RegExp Matching', () => {
|
|
181
|
-
it('should match routes using RegExp', async () => {
|
|
182
|
-
const lambder = new Lambder({
|
|
183
|
-
publicPath: './public',
|
|
184
|
-
apiPath: '/api'
|
|
185
|
-
})
|
|
186
|
-
.addRoute(/^\/admin/, (ctx, res) => {
|
|
187
|
-
return res.html('Admin Area');
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
const handler = lambder.getHandler();
|
|
191
|
-
|
|
192
|
-
const adminResult = await handler(createMockEvent('/admin'), createMockContext());
|
|
193
|
-
expect(Buffer.from(adminResult.body || '', 'base64').toString()).toBe('Admin Area');
|
|
194
|
-
|
|
195
|
-
const adminDashResult = await handler(createMockEvent('/admin/dashboard'), createMockContext());
|
|
196
|
-
expect(Buffer.from(adminDashResult.body || '', 'base64').toString()).toBe('Admin Area');
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
it('should extract regex match groups', async () => {
|
|
200
|
-
const lambder = new Lambder({
|
|
201
|
-
publicPath: './public',
|
|
202
|
-
apiPath: '/api'
|
|
203
|
-
})
|
|
204
|
-
.addRoute(/^\/products\/(\d+)$/, (ctx, res) => {
|
|
205
|
-
const productId = ctx.pathParams?.[1];
|
|
206
|
-
return res.json({ productId });
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
const handler = lambder.getHandler();
|
|
210
|
-
const event = createMockEvent('/products/999');
|
|
211
|
-
const result = await handler(event, createMockContext());
|
|
212
|
-
|
|
213
|
-
const body = JSON.parse(result.body || '{}');
|
|
214
|
-
expect(body.productId).toBe('999');
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
it('should support complex regex patterns', async () => {
|
|
218
|
-
const lambder = new Lambder({
|
|
219
|
-
publicPath: './public',
|
|
220
|
-
apiPath: '/api'
|
|
221
|
-
})
|
|
222
|
-
.addRoute(/^\/api\/v\d+/, (ctx, res) => {
|
|
223
|
-
return res.json({ matched: true });
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
const handler = lambder.getHandler();
|
|
227
|
-
|
|
228
|
-
const v1Result = await handler(createMockEvent('/api/v1'), createMockContext());
|
|
229
|
-
expect(JSON.parse(v1Result.body || '{}').matched).toBe(true);
|
|
230
|
-
|
|
231
|
-
const v2Result = await handler(createMockEvent('/api/v2'), createMockContext());
|
|
232
|
-
expect(JSON.parse(v2Result.body || '{}').matched).toBe(true);
|
|
233
|
-
});
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
describe('Routes - Function-based Conditional Routing', () => {
|
|
237
|
-
it('should match routes using custom functions', async () => {
|
|
238
|
-
const lambder = new Lambder({
|
|
239
|
-
publicPath: './public',
|
|
240
|
-
apiPath: '/api'
|
|
241
|
-
})
|
|
242
|
-
.addRoute((ctx) => ctx.path.startsWith('/custom'), (ctx, res) => {
|
|
243
|
-
return res.html('Custom Route');
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
const handler = lambder.getHandler();
|
|
247
|
-
const event = createMockEvent('/custom/anything');
|
|
248
|
-
const result = await handler(event, createMockContext());
|
|
249
|
-
|
|
250
|
-
expect(Buffer.from(result.body || '', 'base64').toString()).toBe('Custom Route');
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
it('should support complex conditional logic', async () => {
|
|
254
|
-
const lambder = new Lambder({
|
|
255
|
-
publicPath: './public',
|
|
256
|
-
apiPath: '/api'
|
|
257
|
-
})
|
|
258
|
-
.addRoute(
|
|
259
|
-
(ctx) => ctx.path === '/special' && ctx.get.key === 'secret',
|
|
260
|
-
(ctx, res) => {
|
|
261
|
-
return res.html('Special Access');
|
|
262
|
-
}
|
|
263
|
-
);
|
|
264
|
-
|
|
265
|
-
const handler = lambder.getHandler();
|
|
266
|
-
|
|
267
|
-
// Without query param
|
|
268
|
-
const event1 = createMockEvent('/special');
|
|
269
|
-
const result1 = await handler(event1, createMockContext());
|
|
270
|
-
expect(result1.statusCode).toBe(204); // Fallback
|
|
271
|
-
|
|
272
|
-
// With query param
|
|
273
|
-
const event2: APIGatewayProxyEvent = {
|
|
274
|
-
...createMockEvent('/special'),
|
|
275
|
-
queryStringParameters: { key: 'secret' }
|
|
276
|
-
};
|
|
277
|
-
const result2 = await handler(event2, createMockContext());
|
|
278
|
-
expect(Buffer.from(result2.body || '', 'base64').toString()).toBe('Special Access');
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it('should access context variables in condition', async () => {
|
|
282
|
-
const lambder = new Lambder({
|
|
283
|
-
publicPath: './public',
|
|
284
|
-
apiPath: '/api'
|
|
285
|
-
})
|
|
286
|
-
.addRoute(
|
|
287
|
-
(ctx) => ctx.host === 'admin.example.com' && ctx.path === '/dashboard',
|
|
288
|
-
(ctx, res) => {
|
|
289
|
-
return res.html('Admin Dashboard');
|
|
290
|
-
}
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
const handler = lambder.getHandler();
|
|
294
|
-
|
|
295
|
-
const event: APIGatewayProxyEvent = {
|
|
296
|
-
...createMockEvent('/dashboard'),
|
|
297
|
-
headers: { Host: 'admin.example.com' }
|
|
298
|
-
};
|
|
299
|
-
const result = await handler(event, createMockContext());
|
|
300
|
-
|
|
301
|
-
expect(Buffer.from(result.body || '', 'base64').toString()).toBe('Admin Dashboard');
|
|
302
|
-
});
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
describe('Routes - Session Protected Routes', () => {
|
|
306
|
-
beforeEach(() => {
|
|
307
|
-
ddbMock.reset();
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
it('should protect routes with addSessionRoute', async () => {
|
|
311
|
-
const mockSession = {
|
|
312
|
-
pk: 'hash',
|
|
313
|
-
sk: 'sortkey',
|
|
314
|
-
sessionToken: 'hash:sortkey',
|
|
315
|
-
csrfToken: 'csrf-token',
|
|
316
|
-
sessionKey: 'user-123',
|
|
317
|
-
data: { userId: '123', role: 'user' },
|
|
318
|
-
createdAt: Math.floor(Date.now() / 1000),
|
|
319
|
-
expiresAt: Math.floor(Date.now() / 1000) + 3600,
|
|
320
|
-
lastAccessedAt: Math.floor(Date.now() / 1000),
|
|
321
|
-
ttlInSeconds: 3600,
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
ddbMock.on(GetCommand).resolves({ Item: mockSession });
|
|
325
|
-
ddbMock.on(PutCommand).resolves({});
|
|
326
|
-
|
|
327
|
-
const lambder = new Lambder({
|
|
328
|
-
publicPath: './public',
|
|
329
|
-
apiPath: '/api'
|
|
330
|
-
})
|
|
331
|
-
.enableDdbSession(
|
|
332
|
-
{
|
|
333
|
-
tableName: 'test-sessions',
|
|
334
|
-
tableRegion: 'us-east-1',
|
|
335
|
-
sessionSalt: 'test-salt',
|
|
336
|
-
},
|
|
337
|
-
{ partitionKey: 'pk', sortKey: 'sk' }
|
|
338
|
-
)
|
|
339
|
-
.setGlobalErrorHandler((err, ctx, res) => {
|
|
340
|
-
return res.html(`<h1>Error: ${err.message}</h1>`);
|
|
341
|
-
})
|
|
342
|
-
.addSessionRoute('/protected', (ctx, res) => {
|
|
343
|
-
return res.html(`Welcome ${ctx.session.data.userId}`);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
const handler = lambder.getHandler();
|
|
347
|
-
const event = createMockEvent('/protected', 'GET', 'hash:sortkey');
|
|
348
|
-
const result = await handler(event, createMockContext());
|
|
349
|
-
|
|
350
|
-
expect(result.statusCode).toBe(200);
|
|
351
|
-
expect(Buffer.from(result.body || '', 'base64').toString()).toContain('Welcome 123');
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
it('should reject access without valid session', async () => {
|
|
355
|
-
ddbMock.on(GetCommand).resolves({}); // No session found
|
|
356
|
-
|
|
357
|
-
const lambder = new Lambder({
|
|
358
|
-
publicPath: './public',
|
|
359
|
-
apiPath: '/api'
|
|
360
|
-
})
|
|
361
|
-
.enableDdbSession(
|
|
362
|
-
{
|
|
363
|
-
tableName: 'test-sessions',
|
|
364
|
-
tableRegion: 'us-east-1',
|
|
365
|
-
sessionSalt: 'test-salt',
|
|
366
|
-
},
|
|
367
|
-
{ partitionKey: 'pk', sortKey: 'sk' }
|
|
368
|
-
)
|
|
369
|
-
.addSessionRoute('/protected', (ctx, res) => {
|
|
370
|
-
return res.html('Protected');
|
|
371
|
-
})
|
|
372
|
-
.setGlobalErrorHandler((err, ctx, res) => {
|
|
373
|
-
return res.raw({ statusCode: 401, body: 'Unauthorized' });
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
const handler = lambder.getHandler();
|
|
377
|
-
const event = createMockEvent('/protected', 'GET', 'invalid-token');
|
|
378
|
-
const result = await handler(event, createMockContext());
|
|
379
|
-
|
|
380
|
-
expect(result.statusCode).toBe(401);
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
it('should access session data in session routes', async () => {
|
|
384
|
-
const mockSession = {
|
|
385
|
-
pk: 'hash',
|
|
386
|
-
sk: 'sortkey',
|
|
387
|
-
sessionToken: 'hash:sortkey',
|
|
388
|
-
csrfToken: 'csrf-token',
|
|
389
|
-
sessionKey: 'user-456',
|
|
390
|
-
data: { userId: '456', username: 'testuser', role: 'admin' },
|
|
391
|
-
createdAt: Math.floor(Date.now() / 1000),
|
|
392
|
-
expiresAt: Math.floor(Date.now() / 1000) + 3600,
|
|
393
|
-
lastAccessedAt: Math.floor(Date.now() / 1000),
|
|
394
|
-
ttlInSeconds: 3600,
|
|
395
|
-
};
|
|
396
|
-
|
|
397
|
-
ddbMock.on(GetCommand).resolves({ Item: mockSession });
|
|
398
|
-
ddbMock.on(PutCommand).resolves({});
|
|
399
|
-
|
|
400
|
-
const lambder = new Lambder({
|
|
401
|
-
publicPath: './public',
|
|
402
|
-
apiPath: '/api'
|
|
403
|
-
})
|
|
404
|
-
.enableDdbSession(
|
|
405
|
-
{
|
|
406
|
-
tableName: 'test-sessions',
|
|
407
|
-
tableRegion: 'us-east-1',
|
|
408
|
-
sessionSalt: 'test-salt',
|
|
409
|
-
},
|
|
410
|
-
{ partitionKey: 'pk', sortKey: 'sk' }
|
|
411
|
-
)
|
|
412
|
-
.setGlobalErrorHandler((err, ctx, res) => {
|
|
413
|
-
return res.json({ error: err.message });
|
|
414
|
-
})
|
|
415
|
-
.addSessionRoute('/profile', (ctx, res) => {
|
|
416
|
-
return res.json({
|
|
417
|
-
sessionKey: ctx.session.sessionKey,
|
|
418
|
-
username: ctx.session.data.username,
|
|
419
|
-
role: ctx.session.data.role
|
|
420
|
-
});
|
|
421
|
-
});
|
|
422
|
-
|
|
423
|
-
const handler = lambder.getHandler();
|
|
424
|
-
const event = createMockEvent('/profile', 'GET', 'hash:sortkey');
|
|
425
|
-
const result = await handler(event, createMockContext());
|
|
426
|
-
|
|
427
|
-
const body = JSON.parse(result.body || '{}');
|
|
428
|
-
expect(body.sessionKey).toBe('user-456');
|
|
429
|
-
expect(body.username).toBe('testuser');
|
|
430
|
-
expect(body.role).toBe('admin');
|
|
431
|
-
});
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
describe('Routes - Priority and Ordering', () => {
|
|
435
|
-
it('should match first defined route when multiple routes match', async () => {
|
|
436
|
-
const lambder = new Lambder({
|
|
437
|
-
publicPath: './public',
|
|
438
|
-
apiPath: '/api'
|
|
439
|
-
})
|
|
440
|
-
.addRoute('/item', (ctx, res) => {
|
|
441
|
-
return res.html('Exact Match');
|
|
442
|
-
})
|
|
443
|
-
.addRoute(/^\/item/, (ctx, res) => {
|
|
444
|
-
return res.html('Regex Match');
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
const handler = lambder.getHandler();
|
|
448
|
-
const event = createMockEvent('/item');
|
|
449
|
-
const result = await handler(event, createMockContext());
|
|
450
|
-
|
|
451
|
-
// First route should win
|
|
452
|
-
expect(Buffer.from(result.body || '', 'base64').toString()).toBe('Exact Match');
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
it('should respect route definition order', async () => {
|
|
456
|
-
const lambder = new Lambder({
|
|
457
|
-
publicPath: './public',
|
|
458
|
-
apiPath: '/api'
|
|
459
|
-
})
|
|
460
|
-
.addRoute('/users/admin', (ctx, res) => {
|
|
461
|
-
return res.html('Admin User');
|
|
462
|
-
})
|
|
463
|
-
.addRoute('/users/:userId', (ctx, res) => {
|
|
464
|
-
return res.html(`User ${ctx.pathParams?.userId}`);
|
|
465
|
-
});
|
|
466
|
-
|
|
467
|
-
const handler = lambder.getHandler();
|
|
468
|
-
|
|
469
|
-
// Should match specific route first
|
|
470
|
-
const adminResult = await handler(createMockEvent('/users/admin'), createMockContext());
|
|
471
|
-
expect(Buffer.from(adminResult.body || '', 'base64').toString()).toBe('Admin User');
|
|
472
|
-
|
|
473
|
-
// Should match parameterized route
|
|
474
|
-
const userResult = await handler(createMockEvent('/users/123'), createMockContext());
|
|
475
|
-
expect(Buffer.from(userResult.body || '', 'base64').toString()).toContain('User 123');
|
|
476
|
-
});
|
|
477
|
-
});
|
|
478
|
-
|
|
479
|
-
describe('Routes - Wildcard and Catch-all Routes', () => {
|
|
480
|
-
it('should support wildcard routes', async () => {
|
|
481
|
-
const lambder = new Lambder({
|
|
482
|
-
publicPath: './public',
|
|
483
|
-
apiPath: '/api'
|
|
484
|
-
})
|
|
485
|
-
.addRoute('/(.*)', (ctx, res) => {
|
|
486
|
-
return res.html('Catch All');
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
const handler = lambder.getHandler();
|
|
490
|
-
|
|
491
|
-
const result1 = await handler(createMockEvent('/anything'), createMockContext());
|
|
492
|
-
expect(Buffer.from(result1.body || '', 'base64').toString()).toBe('Catch All');
|
|
493
|
-
|
|
494
|
-
const result2 = await handler(createMockEvent('/deeply/nested/path'), createMockContext());
|
|
495
|
-
expect(Buffer.from(result2.body || '', 'base64').toString()).toBe('Catch All');
|
|
496
|
-
});
|
|
497
|
-
|
|
498
|
-
it('should use wildcard as final fallback', async () => {
|
|
499
|
-
const lambder = new Lambder({
|
|
500
|
-
publicPath: './public',
|
|
501
|
-
apiPath: '/api'
|
|
502
|
-
})
|
|
503
|
-
.addRoute('/specific', (ctx, res) => {
|
|
504
|
-
return res.html('Specific');
|
|
505
|
-
})
|
|
506
|
-
.addRoute('/(.*)', (ctx, res) => {
|
|
507
|
-
return res.html('Fallback');
|
|
508
|
-
});
|
|
509
|
-
|
|
510
|
-
const handler = lambder.getHandler();
|
|
511
|
-
|
|
512
|
-
const specificResult = await handler(createMockEvent('/specific'), createMockContext());
|
|
513
|
-
expect(Buffer.from(specificResult.body || '', 'base64').toString()).toBe('Specific');
|
|
514
|
-
|
|
515
|
-
const fallbackResult = await handler(createMockEvent('/anything-else'), createMockContext());
|
|
516
|
-
expect(Buffer.from(fallbackResult.body || '', 'base64').toString()).toBe('Fallback');
|
|
517
|
-
});
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
describe('Routes - Method Filtering', () => {
|
|
521
|
-
it('should match all HTTP methods when no method is specified', async () => {
|
|
522
|
-
const lambder = new Lambder({
|
|
523
|
-
publicPath: './public',
|
|
524
|
-
apiPath: '/api'
|
|
525
|
-
})
|
|
526
|
-
.addRoute('/resource', (ctx, res) => {
|
|
527
|
-
return res.html(`${ctx.method} Response`);
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
const handler = lambder.getHandler();
|
|
531
|
-
|
|
532
|
-
// GET should work
|
|
533
|
-
const getEvent = createMockEvent('/resource', 'GET');
|
|
534
|
-
const getResult = await handler(getEvent, createMockContext());
|
|
535
|
-
expect(Buffer.from(getResult.body || '', 'base64').toString()).toBe('GET Response');
|
|
536
|
-
|
|
537
|
-
// POST should also work (no method restriction)
|
|
538
|
-
const postEvent = createMockEvent('/resource', 'POST');
|
|
539
|
-
const postResult = await handler(postEvent, createMockContext());
|
|
540
|
-
expect(postResult.statusCode).toBe(200);
|
|
541
|
-
expect(Buffer.from(postResult.body || '', 'base64').toString()).toBe('POST Response');
|
|
542
|
-
});
|
|
543
|
-
});
|