@johnhenry/letterpress 0.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.
@@ -0,0 +1,602 @@
1
+ import { test } from "node:test";
2
+ import assert from "node:assert";
3
+ import HTTPExpression, {
4
+ InlineParameter,
5
+ HeaderMatch,
6
+ } from "../utility/http-expression.mjs";
7
+
8
+ // Comprehensive tests
9
+ test("HTTPExpression - Comprehensive Tests", async (t) => {
10
+ await t.test("Basic GET request", () => {
11
+ const expr = HTTPExpression`GET /users/:id`;
12
+ const request = new Request("https://api.example.com/users/123", {
13
+ method: "GET",
14
+ });
15
+ assert.strictEqual(expr.test(request), true);
16
+ assert.deepStrictEqual(expr.exec(request), {
17
+ id: "123",
18
+ method: "GET",
19
+ headers: request.headers,
20
+ });
21
+ });
22
+
23
+ await t.test("POST request with JSON body and headers", () => {
24
+ const expr = HTTPExpression`POST /api/articles [Content-Type=application/json] [Authorization^=Bearer]`;
25
+ const request = new Request("https://api.example.com/api/articles", {
26
+ method: "POST",
27
+ headers: {
28
+ "Content-Type": "application/json",
29
+ Authorization: "Bearer token123",
30
+ },
31
+ body: JSON.stringify({ title: "New Article", content: "Content here" }),
32
+ });
33
+ assert.strictEqual(expr.test(request), true);
34
+ assert.deepStrictEqual(expr.exec(request), {
35
+ method: "POST",
36
+ headers: request.headers,
37
+ });
38
+ });
39
+
40
+ await t.test("PUT request with path parameters and query string", () => {
41
+ const expr = HTTPExpression`PUT /api/users/:userId/posts/:postId`;
42
+ const request = new Request(
43
+ "https://api.example.com/api/users/456/posts/789?draft=true",
44
+ { method: "PUT" }
45
+ );
46
+ assert.strictEqual(expr.test(request), true);
47
+ assert.deepStrictEqual(expr.exec(request), {
48
+ userId: "456",
49
+ postId: "789",
50
+ method: "PUT",
51
+ headers: request.headers,
52
+ });
53
+ });
54
+
55
+ await t.test("DELETE request with numeric header comparison", () => {
56
+ const expr = HTTPExpression`DELETE /api/resources/:id [X-Rate-Limit-Remaining>0]`;
57
+ const request = new Request("https://api.example.com/api/resources/101", {
58
+ method: "DELETE",
59
+ headers: { "X-Rate-Limit-Remaining": "5" },
60
+ });
61
+ assert.strictEqual(expr.test(request), true);
62
+ assert.deepStrictEqual(expr.exec(request), {
63
+ id: "101",
64
+ method: "DELETE",
65
+ headers: request.headers,
66
+ });
67
+ });
68
+
69
+ await t.test("PATCH request with regex header matching", () => {
70
+ const expr = HTTPExpression`PATCH /api/v:version/update [Accept~=application/(json|xml)]`;
71
+ const request = new Request("https://api.example.com/api/v2/update", {
72
+ method: "PATCH",
73
+ headers: { Accept: "application/json" },
74
+ });
75
+ assert.strictEqual(expr.test(request), true);
76
+ assert.deepStrictEqual(expr.exec(request), {
77
+ version: "2",
78
+ method: "PATCH",
79
+ headers: request.headers,
80
+ });
81
+ });
82
+
83
+ await t.test("GET request with multiple header conditions", () => {
84
+ const expr = HTTPExpression`GET /api/data [Cache-Control=no-cache] [X-Requested-With=XMLHttpRequest] [!X-Forwarded-For]`;
85
+ const request = new Request("https://api.example.com/api/data", {
86
+ method: "GET",
87
+ headers: {
88
+ "Cache-Control": "no-cache",
89
+ "X-Requested-With": "XMLHttpRequest",
90
+ },
91
+ });
92
+ assert.strictEqual(expr.test(request), true);
93
+ assert.deepStrictEqual(expr.exec(request), {
94
+ method: "GET",
95
+ headers: request.headers,
96
+ });
97
+ });
98
+
99
+ await t.test("POST request with content negotiation", () => {
100
+ const expr = HTTPExpression`POST /api/submit [Content-Type^=application/] [Accept$=json]`;
101
+ const request = new Request("https://api.example.com/api/submit", {
102
+ method: "POST",
103
+ headers: {
104
+ "Content-Type": "application/x-www-form-urlencoded",
105
+ Accept: "application/json",
106
+ },
107
+ });
108
+ assert.strictEqual(expr.test(request), true);
109
+ assert.deepStrictEqual(expr.exec(request), {
110
+ method: "POST",
111
+ headers: request.headers,
112
+ });
113
+ });
114
+
115
+ await t.test("GET request with case-insensitive header matching", () => {
116
+ const expr = HTTPExpression`GET /api/status [X-API-Version~=(?i)beta]`;
117
+ const request = new Request("https://api.example.com/api/status", {
118
+ method: "GET",
119
+ headers: { "X-API-Version": "BETA-1.2.3" },
120
+ });
121
+ assert.strictEqual(expr.test(request), true);
122
+ assert.deepStrictEqual(expr.exec(request), {
123
+ method: "GET",
124
+ headers: request.headers,
125
+ });
126
+ });
127
+
128
+ await t.test("Request not matching method", () => {
129
+ const expr = HTTPExpression`GET /api/data`;
130
+ const request = new Request("https://api.example.com/api/data", {
131
+ method: "POST",
132
+ });
133
+ assert.strictEqual(expr.test(request), false);
134
+ assert.strictEqual(expr.exec(request), null);
135
+ });
136
+
137
+ await t.test("Request not matching path", () => {
138
+ const expr = HTTPExpression`GET /api/users/:id`;
139
+ const request = new Request("https://api.example.com/api/posts/123", {
140
+ method: "GET",
141
+ });
142
+ assert.strictEqual(expr.test(request), false);
143
+ assert.strictEqual(expr.exec(request), null);
144
+ });
145
+
146
+ await t.test("Request not matching header condition", () => {
147
+ const expr = HTTPExpression`GET /api/secure [Authorization=Bearer token123]`;
148
+ const request = new Request("https://api.example.com/api/secure", {
149
+ method: "GET",
150
+ headers: { Authorization: "Basic dXNlcjpwYXNz" },
151
+ });
152
+ assert.strictEqual(expr.test(request), false);
153
+ assert.strictEqual(expr.exec(request), null);
154
+ });
155
+
156
+ await t.test("Complex request with multiple conditions", () => {
157
+ const expr = HTTPExpression`POST /api/v:version/users/:userId/orders [Content-Type=application/json] [Authorization^=Bearer] [X-API-Key] [!X-Deprecated]`;
158
+ const request = new Request(
159
+ "https://api.example.com/api/v2/users/789/orders",
160
+ {
161
+ method: "POST",
162
+ headers: {
163
+ "Content-Type": "application/json",
164
+ Authorization: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
165
+ "X-API-Key": "abcdef123456",
166
+ },
167
+ body: JSON.stringify({ items: [{ id: 1, quantity: 2 }] }),
168
+ }
169
+ );
170
+ assert.strictEqual(expr.test(request), true);
171
+ assert.deepStrictEqual(expr.exec(request), {
172
+ version: "2",
173
+ userId: "789",
174
+ method: "POST",
175
+ headers: request.headers,
176
+ });
177
+ });
178
+
179
+ await t.test("GET request with complex regex header matching", () => {
180
+ const expr = HTTPExpression`GET /api/data [Accept~=(text/|application/json)]`;
181
+ const jsonRequest = new Request("https://api.example.com/api/data", {
182
+ method: "GET",
183
+ headers: { Accept: "application/json" },
184
+ });
185
+ assert.strictEqual(expr.test(jsonRequest), true);
186
+
187
+ const textRequest = new Request("https://api.example.com/api/data", {
188
+ method: "GET",
189
+ headers: { Accept: "text/plain" },
190
+ });
191
+ assert.strictEqual(expr.test(textRequest), true);
192
+
193
+ const invalidRequest = new Request("https://api.example.com/api/data", {
194
+ method: "GET",
195
+ headers: { Accept: "image/png" },
196
+ });
197
+ assert.strictEqual(expr.test(invalidRequest), false);
198
+ });
199
+
200
+ await t.test("Using InlineParameter", () => {
201
+ const expr = HTTPExpression`GET /api/users/${InlineParameter({
202
+ name: "id",
203
+ type: "number",
204
+ transform: Number,
205
+ })}`;
206
+ const request = new Request("https://api.example.com/api/users/123", {
207
+ method: "GET",
208
+ });
209
+ assert.strictEqual(expr.test(request), true);
210
+ const result = expr.exec(request);
211
+ assert.strictEqual(typeof result.id, "string"); // Note: We're not applying the transform in this implementation
212
+ assert.strictEqual(result.id, "123");
213
+ });
214
+
215
+ await t.test("Using HeaderMatch", () => {
216
+ const expr = HTTPExpression`GET /api/data ${HeaderMatch({
217
+ name: "X-Custom-Header",
218
+ operator: "*=",
219
+ value: "test",
220
+ })}`;
221
+ const request = new Request("https://api.example.com/api/data", {
222
+ method: "GET",
223
+ headers: { "X-Custom-Header": "this is a test header" },
224
+ });
225
+ assert.strictEqual(expr.test(request), true);
226
+ assert.deepStrictEqual(expr.exec(request), {
227
+ method: "GET",
228
+ headers: request.headers,
229
+ });
230
+ });
231
+
232
+ await t.test("Header with spaces in value", () => {
233
+ const expr = HTTPExpression`GET /api/data [X-Custom-Header=Value with spaces]`;
234
+ const request = new Request("https://api.example.com/api/data", {
235
+ method: "GET",
236
+ headers: { "X-Custom-Header": "Value with spaces" },
237
+ });
238
+ assert.strictEqual(expr.test(request), true);
239
+ assert.deepStrictEqual(expr.exec(request), {
240
+ method: "GET",
241
+ headers: request.headers,
242
+ });
243
+ });
244
+
245
+ await t.test("Unquoted header value with spaces", () => {
246
+ const expr = HTTPExpression`GET /api/data [X-Custom-Header=Value with spaces]`;
247
+ const request = new Request("https://api.example.com/api/data", {
248
+ method: "GET",
249
+ headers: { "X-Custom-Header": "Value with spaces" },
250
+ });
251
+ assert.strictEqual(expr.test(request), true);
252
+ assert.deepStrictEqual(expr.exec(request), {
253
+ method: "GET",
254
+ headers: request.headers,
255
+ });
256
+ });
257
+
258
+ await t.test("Multiple path parameters", () => {
259
+ const expr = HTTPExpression`GET /api/:version/users/:userId/posts/:postId`;
260
+ const request = new Request(
261
+ "https://api.example.com/api/v1/users/123/posts/456",
262
+ {
263
+ method: "GET",
264
+ }
265
+ );
266
+ assert.strictEqual(expr.test(request), true);
267
+ assert.deepStrictEqual(expr.exec(request), {
268
+ version: "v1",
269
+ userId: "123",
270
+ postId: "456",
271
+ method: "GET",
272
+ headers: request.headers,
273
+ });
274
+ });
275
+
276
+ await t.test("Header with special characters", () => {
277
+ const expr = HTTPExpression`GET /api/data [X-Special-Header=!@#$%^&*()]`;
278
+ const request = new Request("https://api.example.com/api/data", {
279
+ method: "GET",
280
+ headers: { "X-Special-Header": "!@#$%^&*()" },
281
+ });
282
+ assert.strictEqual(expr.test(request), true);
283
+ assert.deepStrictEqual(expr.exec(request), {
284
+ method: "GET",
285
+ headers: request.headers,
286
+ });
287
+ });
288
+
289
+ await t.test("Path with query parameters", () => {
290
+ const expr = HTTPExpression`GET /api/search`;
291
+ const request = new Request(
292
+ "https://api.example.com/api/search?q=test&page=1",
293
+ {
294
+ method: "GET",
295
+ }
296
+ );
297
+ assert.strictEqual(expr.test(request), true);
298
+ assert.deepStrictEqual(expr.exec(request), {
299
+ method: "GET",
300
+ headers: request.headers,
301
+ });
302
+ });
303
+
304
+ await t.test("Case-insensitive method matching", () => {
305
+ const expr = HTTPExpression`GET /api/data`;
306
+ const request = new Request("https://api.example.com/api/data", {
307
+ method: "get",
308
+ });
309
+ assert.strictEqual(expr.test(request), true);
310
+ assert.deepStrictEqual(expr.exec(request), {
311
+ method: "GET",
312
+ headers: request.headers,
313
+ });
314
+ });
315
+
316
+ await t.test("Multiple header conditions with AND logic", () => {
317
+ const expr = HTTPExpression`POST /api/data [Content-Type=application/json] [Authorization^=Bearer] [X-API-Version=1.0]`;
318
+ const validRequest = new Request("https://api.example.com/api/data", {
319
+ method: "POST",
320
+ headers: {
321
+ "Content-Type": "application/json",
322
+ Authorization: "Bearer token123",
323
+ "X-API-Version": "1.0",
324
+ },
325
+ });
326
+ assert.strictEqual(expr.test(validRequest), true);
327
+
328
+ const invalidRequest = new Request("https://api.example.com/api/data", {
329
+ method: "POST",
330
+ headers: {
331
+ "Content-Type": "application/json",
332
+ Authorization: "Bearer token123",
333
+ "X-API-Version": "2.0", // This doesn't match the condition
334
+ },
335
+ });
336
+ assert.strictEqual(expr.test(invalidRequest), false);
337
+ });
338
+
339
+ await t.test("Empty path", () => {
340
+ const expr = HTTPExpression`GET /`;
341
+ const request = new Request("https://api.example.com/", {
342
+ method: "GET",
343
+ });
344
+ assert.strictEqual(expr.test(request), true);
345
+ assert.deepStrictEqual(expr.exec(request), {
346
+ method: "GET",
347
+ headers: request.headers,
348
+ });
349
+ });
350
+
351
+ await t.test("Path with trailing slash", () => {
352
+ const expr = HTTPExpression`GET /api/users/`;
353
+ const requestWithSlash = new Request("https://api.example.com/api/users/", {
354
+ method: "GET",
355
+ });
356
+ const requestWithoutSlash = new Request(
357
+ "https://api.example.com/api/users",
358
+ {
359
+ method: "GET",
360
+ }
361
+ );
362
+ assert.strictEqual(expr.test(requestWithSlash), true);
363
+ assert.strictEqual(expr.test(requestWithoutSlash), false);
364
+ });
365
+
366
+ await t.test("Header exists condition", () => {
367
+ const expr = HTTPExpression`GET /api/data [X-Custom-Header]`;
368
+ const validRequest = new Request("https://api.example.com/api/data", {
369
+ method: "GET",
370
+ headers: { "X-Custom-Header": "any value" },
371
+ });
372
+ const invalidRequest = new Request("https://api.example.com/api/data", {
373
+ method: "GET",
374
+ });
375
+ assert.strictEqual(expr.test(validRequest), true);
376
+ assert.strictEqual(expr.test(invalidRequest), false);
377
+ });
378
+
379
+ await t.test("Negated header condition", () => {
380
+ const expr = HTTPExpression`GET /api/public [!Authorization]`;
381
+ const validRequest = new Request("https://api.example.com/api/public", {
382
+ method: "GET",
383
+ });
384
+ const invalidRequest = new Request("https://api.example.com/api/public", {
385
+ method: "GET",
386
+ headers: { Authorization: "Bearer token123" },
387
+ });
388
+ assert.strictEqual(expr.test(validRequest), true);
389
+ assert.strictEqual(expr.test(invalidRequest), false);
390
+ });
391
+ });
392
+ // Additional comprehensive tests
393
+ test("HTTPExpression - Additional Tests", async (t) => {
394
+ await t.test(
395
+ "Complex path with multiple parameters and nested resources",
396
+ () => {
397
+ const expr = HTTPExpression`GET /api/v:version/users/:userId/posts/:postId/comments/:commentId`;
398
+ const request = new Request(
399
+ "https://api.example.com/api/v2/users/123/posts/456/comments/789",
400
+ {
401
+ method: "GET",
402
+ }
403
+ );
404
+ assert.strictEqual(expr.test(request), true);
405
+ assert.deepStrictEqual(expr.exec(request), {
406
+ version: "2",
407
+ userId: "123",
408
+ postId: "456",
409
+ commentId: "789",
410
+ method: "GET",
411
+ headers: request.headers,
412
+ });
413
+ }
414
+ );
415
+
416
+ await t.test("Path with encoded characters", () => {
417
+ const expr = HTTPExpression`GET /search/:query`;
418
+ const request = new Request(
419
+ "https://api.example.com/search/hello%20world",
420
+ {
421
+ method: "GET",
422
+ }
423
+ );
424
+ assert.strictEqual(expr.test(request), true);
425
+ assert.deepStrictEqual(expr.exec(request), {
426
+ query: "hello%20world",
427
+ method: "GET",
428
+ headers: request.headers,
429
+ });
430
+ });
431
+
432
+ await t.test("Header with comma-separated values", () => {
433
+ const expr = HTTPExpression`GET /api/data [Accept=application/json, text/plain]`;
434
+ const request = new Request("https://api.example.com/api/data", {
435
+ method: "GET",
436
+ headers: { Accept: "application/json, text/plain" },
437
+ });
438
+ assert.strictEqual(expr.test(request), true);
439
+ });
440
+
441
+ await t.test("Case-insensitive header name matching", () => {
442
+ const expr = HTTPExpression`GET /api/data [content-type=application/json]`;
443
+ const request = new Request("https://api.example.com/api/data", {
444
+ method: "GET",
445
+ headers: { "Content-Type": "application/json" },
446
+ });
447
+ assert.strictEqual(expr.test(request), true);
448
+ });
449
+
450
+ // await t.test("Header value with regex special characters", () => {
451
+ // const expr = HTTPExpression`GET /api/data [X-Special-Header~=^special\\s.*$]`;
452
+ // const request = new Request("https://api.example.com/api/data", {
453
+ // method: "GET",
454
+ // headers: { "X-Special-Header": "special value" },
455
+ // });
456
+ // assert.strictEqual(expr.test(request), true);
457
+ // });
458
+
459
+ await t.test("Path parameter with regex special characters", () => {
460
+ const expr = HTTPExpression`GET /api/users/:username`;
461
+ const request = new Request(
462
+ "https://api.example.com/api/users/john.doe+123",
463
+ {
464
+ method: "GET",
465
+ }
466
+ );
467
+ assert.strictEqual(expr.test(request), true);
468
+ assert.deepStrictEqual(expr.exec(request), {
469
+ username: "john.doe+123",
470
+ method: "GET",
471
+ headers: request.headers,
472
+ });
473
+ });
474
+
475
+ // await t.test("Header with quoted value containing spaces", () => {
476
+ // const expr = HTTPExpression`GET /api/data [X-Custom-Header="Value with spaces"]`;
477
+ // const request = new Request("https://api.example.com/api/data", {
478
+ // method: "GET",
479
+ // headers: { "X-Custom-Header": "Value with spaces" },
480
+ // });
481
+ // assert.strictEqual(expr.test(request), true);
482
+ // });
483
+
484
+ await t.test("Multiple methods for the same path", () => {
485
+ const expr1 = HTTPExpression`GET /api/resource`;
486
+ const expr2 = HTTPExpression`POST /api/resource`;
487
+ const request1 = new Request("https://api.example.com/api/resource", {
488
+ method: "GET",
489
+ });
490
+ const request2 = new Request("https://api.example.com/api/resource", {
491
+ method: "POST",
492
+ });
493
+ assert.strictEqual(expr1.test(request1), true);
494
+ assert.strictEqual(expr1.test(request2), false);
495
+ assert.strictEqual(expr2.test(request1), false);
496
+ assert.strictEqual(expr2.test(request2), true);
497
+ });
498
+
499
+ await t.test("Path with matrix parameters", () => {
500
+ const expr = HTTPExpression`GET /api/users/:userId;role=:role/posts`;
501
+ const request = new Request(
502
+ "https://api.example.com/api/users/123;role=admin/posts",
503
+ {
504
+ method: "GET",
505
+ }
506
+ );
507
+ assert.strictEqual(expr.test(request), true);
508
+ assert.deepStrictEqual(expr.exec(request), {
509
+ userId: "123",
510
+ role: "admin",
511
+ method: "GET",
512
+ headers: request.headers,
513
+ });
514
+ });
515
+
516
+ await t.test("Header with numerical comparison (less than)", () => {
517
+ const expr = HTTPExpression`GET /api/data [X-Rate-Limit-Remaining<10]`;
518
+ const request = new Request("https://api.example.com/api/data", {
519
+ method: "GET",
520
+ headers: { "X-Rate-Limit-Remaining": "5" },
521
+ });
522
+ assert.strictEqual(expr.test(request), true);
523
+ });
524
+
525
+ await t.test(
526
+ "Header with numerical comparison (greater than or equal)",
527
+ () => {
528
+ const expr = HTTPExpression`GET /api/data [X-Rate-Limit-Remaining>=10]`;
529
+ const request = new Request("https://api.example.com/api/data", {
530
+ method: "GET",
531
+ headers: { "X-Rate-Limit-Remaining": "15" },
532
+ });
533
+ assert.strictEqual(expr.test(request), true);
534
+ }
535
+ );
536
+
537
+ await t.test("Complex combination of path parameters and headers", () => {
538
+ const expr = HTTPExpression`POST /api/v:version/users/:userId/posts [Content-Type=application/json] [Authorization^=Bearer] [X-API-Key]`;
539
+ const request = new Request(
540
+ "https://api.example.com/api/v2/users/123/posts",
541
+ {
542
+ method: "POST",
543
+ headers: {
544
+ "Content-Type": "application/json",
545
+ Authorization: "Bearer token123",
546
+ "X-API-Key": "abc123",
547
+ },
548
+ }
549
+ );
550
+ assert.strictEqual(expr.test(request), true);
551
+ assert.deepStrictEqual(expr.exec(request), {
552
+ version: "2",
553
+ userId: "123",
554
+ method: "POST",
555
+ headers: request.headers,
556
+ });
557
+ });
558
+
559
+ await t.test("Path with optional segment", () => {
560
+ const expr = HTTPExpression`GET /api/users/:userId(/posts)?`;
561
+ const request1 = new Request("https://api.example.com/api/users/123", {
562
+ method: "GET",
563
+ });
564
+ const request2 = new Request(
565
+ "https://api.example.com/api/users/123/posts",
566
+ { method: "GET" }
567
+ );
568
+ assert.strictEqual(expr.test(request1), true);
569
+ assert.strictEqual(expr.test(request2), true);
570
+ });
571
+
572
+ await t.test("Header with multiple conditions on the same header", () => {
573
+ const expr = HTTPExpression`GET /api/data [Accept^=application/] [Accept$=json]`;
574
+ const request = new Request("https://api.example.com/api/data", {
575
+ method: "GET",
576
+ headers: { Accept: "application/json" },
577
+ });
578
+ assert.strictEqual(expr.test(request), true);
579
+ });
580
+
581
+ await t.test("Path with custom regex for parameter", () => {
582
+ const expr = HTTPExpression`GET /api/users/:userId(\\d+)`;
583
+ const validRequest = new Request("https://api.example.com/api/users/123", {
584
+ method: "GET",
585
+ });
586
+ const invalidRequest = new Request(
587
+ "https://api.example.com/api/users/abc",
588
+ { method: "GET" }
589
+ );
590
+ assert.strictEqual(expr.test(validRequest), true);
591
+ assert.strictEqual(expr.test(invalidRequest), false);
592
+ });
593
+
594
+ await t.test("Header with base64 encoded value", () => {
595
+ const expr = HTTPExpression`GET /api/secure [Authorization=Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==]`;
596
+ const request = new Request("https://api.example.com/api/secure", {
597
+ method: "GET",
598
+ headers: { Authorization: "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" },
599
+ });
600
+ assert.strictEqual(expr.test(request), true);
601
+ });
602
+ });
package/test/index.mjs ADDED
@@ -0,0 +1,6 @@
1
+ import "./router.mjs";
2
+ import "./http-expression.mjs";
3
+ import "./route.mjs";
4
+ import "./req-res.mjs";
5
+ import "./fs-router.mjs";
6
+ import "./dynamic-run.mjs";