@terreno/api 0.20.2 → 0.22.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/.ai/guidelines/core.md +71 -0
- package/.ai/skills/mongoose-schema-safety/SKILL.md +143 -0
- package/README.md +54 -1
- package/bunfig.toml +1 -1
- package/dist/__tests__/versionCheckPlugin.test.js +29 -7
- package/dist/actions.openApi.test.js +13 -11
- package/dist/api.js +98 -11
- package/dist/api.query.test.js +31 -1
- package/dist/api.test.js +211 -0
- package/dist/auth.test.js +418 -43
- package/dist/betterAuth.d.ts +1 -1
- package/dist/consentApp.test.js +1 -0
- package/dist/example.js +4 -4
- package/dist/expressServer.d.ts +0 -22
- package/dist/expressServer.js +1 -125
- package/dist/expressServer.test.js +90 -91
- package/dist/githubAuth.test.js +22 -22
- package/dist/logger.d.ts +154 -0
- package/dist/logger.js +445 -26
- package/dist/logger.test.js +435 -0
- package/dist/middleware.d.ts +7 -0
- package/dist/middleware.js +58 -1
- package/dist/middleware.test.js +159 -0
- package/dist/models/consentForm.js +2 -1
- package/dist/models/consentResponse.js +2 -1
- package/dist/models/versionConfig.js +2 -1
- package/dist/openApi.test.js +10 -17
- package/dist/openApiBuilder.d.ts +18 -0
- package/dist/openApiBuilder.js +21 -0
- package/dist/openApiBuilder.test.js +34 -10
- package/dist/permissions.test.js +10 -43
- package/dist/populate.test.js +10 -42
- package/dist/realtime/changeStreamWatcher.d.ts +4 -4
- package/dist/realtime/changeStreamWatcher.js +2 -4
- package/dist/realtime/queryMatcher.d.ts +1 -1
- package/dist/realtime/queryMatcher.js +39 -14
- package/dist/realtime/types.d.ts +3 -3
- package/dist/requestContext.d.ts +61 -0
- package/dist/requestContext.js +74 -0
- package/dist/secretProviders.test.js +335 -0
- package/dist/syncConsents.test.js +2 -2
- package/dist/terrenoApp.d.ts +27 -15
- package/dist/terrenoApp.js +24 -14
- package/dist/terrenoApp.test.js +52 -0
- package/dist/tests/bunSetup.js +66 -262
- package/dist/tests/createTestData.d.ts +9 -0
- package/dist/tests/createTestData.js +272 -0
- package/dist/tests/models.d.ts +71 -0
- package/dist/tests/models.js +134 -0
- package/dist/tests/mongoTestSetup.d.ts +7 -0
- package/dist/tests/mongoTestSetup.js +150 -0
- package/dist/tests/testEnv.d.ts +0 -0
- package/dist/tests/testEnv.js +6 -0
- package/dist/tests/testHelper.d.ts +22 -0
- package/dist/tests/testHelper.js +115 -0
- package/dist/tests/types.d.ts +29 -0
- package/dist/tests/types.js +2 -0
- package/dist/tests.d.ts +10 -78
- package/dist/tests.js +24 -241
- package/dist/transformers.test.js +14 -50
- package/package.json +18 -4
- package/src/__snapshots__/openApiBuilder.test.ts.snap +1 -0
- package/src/__tests__/versionCheckPlugin.test.ts +43 -15
- package/src/actions.openApi.test.ts +12 -10
- package/src/api.query.test.ts +24 -1
- package/src/api.test.ts +169 -0
- package/src/api.ts +71 -0
- package/src/auth.test.ts +287 -39
- package/src/betterAuth.ts +1 -1
- package/src/consentApp.test.ts +1 -0
- package/src/example.ts +4 -4
- package/src/expressServer.test.ts +82 -85
- package/src/expressServer.ts +1 -213
- package/src/githubAuth.test.ts +22 -22
- package/src/logger.test.ts +466 -1
- package/src/logger.ts +477 -14
- package/src/middleware.test.ts +74 -2
- package/src/middleware.ts +57 -0
- package/src/models/consentForm.ts +3 -4
- package/src/models/consentResponse.ts +6 -4
- package/src/models/versionConfig.ts +3 -4
- package/src/openApi.test.ts +10 -17
- package/src/openApiBuilder.test.ts +27 -10
- package/src/openApiBuilder.ts +24 -0
- package/src/permissions.test.ts +8 -23
- package/src/populate.test.ts +7 -22
- package/src/realtime/changeStreamWatcher.ts +15 -10
- package/src/realtime/queryMatcher.ts +54 -27
- package/src/realtime/types.ts +4 -4
- package/src/requestContext.ts +86 -0
- package/src/secretProviders.test.ts +219 -1
- package/src/syncConsents.test.ts +1 -1
- package/src/terrenoApp.test.ts +38 -0
- package/src/terrenoApp.ts +37 -15
- package/src/tests/bunSetup.ts +22 -236
- package/src/tests/createTestData.ts +176 -0
- package/src/tests/models.ts +164 -0
- package/src/tests/mongoTestSetup.ts +69 -0
- package/src/tests/testEnv.ts +4 -0
- package/src/tests/testHelper.ts +57 -0
- package/src/tests/types.ts +35 -0
- package/src/tests.ts +40 -231
- package/src/transformers.test.ts +11 -30
- package/tsconfig.typedoc.json +4 -0
- package/dist/tests/index.d.ts +0 -1
- package/dist/tests/index.js +0 -17
- package/src/tests/index.ts +0 -1
|
@@ -37,6 +37,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
var bun_test_1 = require("bun:test");
|
|
40
|
+
var errors_1 = require("./errors");
|
|
40
41
|
var secretProviders_1 = require("./secretProviders");
|
|
41
42
|
(0, bun_test_1.describe)("EnvSecretProvider", function () {
|
|
42
43
|
(0, bun_test_1.beforeEach)(function () {
|
|
@@ -389,3 +390,337 @@ var secretProviders_1 = require("./secretProviders");
|
|
|
389
390
|
});
|
|
390
391
|
}); });
|
|
391
392
|
});
|
|
393
|
+
/** Inject a pre-built mock client into a GcpSecretProvider, bypassing getClient(). */
|
|
394
|
+
var injectClient = function (provider, client) {
|
|
395
|
+
// Bypass the private `client` field for testing — avoids the dynamic import of
|
|
396
|
+
// @google-cloud/secret-manager which is an optional peer dependency.
|
|
397
|
+
Object.defineProperty(provider, "client", { configurable: true, value: client, writable: true });
|
|
398
|
+
};
|
|
399
|
+
(0, bun_test_1.describe)("GcpSecretProvider", function () {
|
|
400
|
+
(0, bun_test_1.it)("has the name 'gcp'", function () {
|
|
401
|
+
var provider = new secretProviders_1.GcpSecretProvider({ projectId: "my-project" });
|
|
402
|
+
(0, bun_test_1.expect)(provider.name).toBe("gcp");
|
|
403
|
+
});
|
|
404
|
+
(0, bun_test_1.it)("throws APIError when @google-cloud/secret-manager is not installed", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
405
|
+
var provider, error_1;
|
|
406
|
+
return __generator(this, function (_a) {
|
|
407
|
+
switch (_a.label) {
|
|
408
|
+
case 0:
|
|
409
|
+
provider = new secretProviders_1.GcpSecretProvider({ projectId: "my-project" });
|
|
410
|
+
_a.label = 1;
|
|
411
|
+
case 1:
|
|
412
|
+
_a.trys.push([1, 3, , 4]);
|
|
413
|
+
return [4 /*yield*/, provider.getSecret("some-secret")];
|
|
414
|
+
case 2:
|
|
415
|
+
_a.sent();
|
|
416
|
+
bun_test_1.expect.unreachable("should have thrown");
|
|
417
|
+
return [3 /*break*/, 4];
|
|
418
|
+
case 3:
|
|
419
|
+
error_1 = _a.sent();
|
|
420
|
+
(0, bun_test_1.expect)(error_1).toBeInstanceOf(errors_1.APIError);
|
|
421
|
+
(0, bun_test_1.expect)(error_1.title).toContain("GcpSecretProvider requires @google-cloud/secret-manager");
|
|
422
|
+
return [3 /*break*/, 4];
|
|
423
|
+
case 4: return [2 /*return*/];
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
}); });
|
|
427
|
+
(0, bun_test_1.it)("resolves a short secret name to the full resource path with default version", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
428
|
+
var calls, mockClient, provider, result;
|
|
429
|
+
return __generator(this, function (_a) {
|
|
430
|
+
switch (_a.label) {
|
|
431
|
+
case 0:
|
|
432
|
+
calls = [];
|
|
433
|
+
mockClient = {
|
|
434
|
+
accessSecretVersion: function (req) { return __awaiter(void 0, void 0, void 0, function () {
|
|
435
|
+
return __generator(this, function (_a) {
|
|
436
|
+
calls.push(req.name);
|
|
437
|
+
return [2 /*return*/, [{ payload: { data: "secret-value" } }]];
|
|
438
|
+
});
|
|
439
|
+
}); },
|
|
440
|
+
};
|
|
441
|
+
provider = new secretProviders_1.GcpSecretProvider({ projectId: "my-project" });
|
|
442
|
+
injectClient(provider, mockClient);
|
|
443
|
+
return [4 /*yield*/, provider.getSecret("openai-api-key")];
|
|
444
|
+
case 1:
|
|
445
|
+
result = _a.sent();
|
|
446
|
+
(0, bun_test_1.expect)(result).toBe("secret-value");
|
|
447
|
+
(0, bun_test_1.expect)(calls).toEqual(["projects/my-project/secrets/openai-api-key/versions/latest"]);
|
|
448
|
+
return [2 /*return*/];
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
}); });
|
|
452
|
+
(0, bun_test_1.it)("resolves a short secret name with an explicit version", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
453
|
+
var calls, mockClient, provider, result;
|
|
454
|
+
return __generator(this, function (_a) {
|
|
455
|
+
switch (_a.label) {
|
|
456
|
+
case 0:
|
|
457
|
+
calls = [];
|
|
458
|
+
mockClient = {
|
|
459
|
+
accessSecretVersion: function (req) { return __awaiter(void 0, void 0, void 0, function () {
|
|
460
|
+
return __generator(this, function (_a) {
|
|
461
|
+
calls.push(req.name);
|
|
462
|
+
return [2 /*return*/, [{ payload: { data: "v3-value" } }]];
|
|
463
|
+
});
|
|
464
|
+
}); },
|
|
465
|
+
};
|
|
466
|
+
provider = new secretProviders_1.GcpSecretProvider({ projectId: "p" });
|
|
467
|
+
injectClient(provider, mockClient);
|
|
468
|
+
return [4 /*yield*/, provider.getSecret("my-key", "3")];
|
|
469
|
+
case 1:
|
|
470
|
+
result = _a.sent();
|
|
471
|
+
(0, bun_test_1.expect)(result).toBe("v3-value");
|
|
472
|
+
(0, bun_test_1.expect)(calls).toEqual(["projects/p/secrets/my-key/versions/3"]);
|
|
473
|
+
return [2 /*return*/];
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
}); });
|
|
477
|
+
(0, bun_test_1.it)("honors a full resource path that already contains /versions/", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
478
|
+
var calls, mockClient, provider, result;
|
|
479
|
+
return __generator(this, function (_a) {
|
|
480
|
+
switch (_a.label) {
|
|
481
|
+
case 0:
|
|
482
|
+
calls = [];
|
|
483
|
+
mockClient = {
|
|
484
|
+
accessSecretVersion: function (req) { return __awaiter(void 0, void 0, void 0, function () {
|
|
485
|
+
return __generator(this, function (_a) {
|
|
486
|
+
calls.push(req.name);
|
|
487
|
+
return [2 /*return*/, [{ payload: { data: "pinned" } }]];
|
|
488
|
+
});
|
|
489
|
+
}); },
|
|
490
|
+
};
|
|
491
|
+
provider = new secretProviders_1.GcpSecretProvider({ projectId: "ignored" });
|
|
492
|
+
injectClient(provider, mockClient);
|
|
493
|
+
return [4 /*yield*/, provider.getSecret("projects/p/secrets/s/versions/7")];
|
|
494
|
+
case 1:
|
|
495
|
+
result = _a.sent();
|
|
496
|
+
(0, bun_test_1.expect)(result).toBe("pinned");
|
|
497
|
+
(0, bun_test_1.expect)(calls).toEqual(["projects/p/secrets/s/versions/7"]);
|
|
498
|
+
return [2 /*return*/];
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
}); });
|
|
502
|
+
(0, bun_test_1.it)("appends /versions/latest to a full resource path without a version suffix", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
503
|
+
var calls, mockClient, provider, result;
|
|
504
|
+
return __generator(this, function (_a) {
|
|
505
|
+
switch (_a.label) {
|
|
506
|
+
case 0:
|
|
507
|
+
calls = [];
|
|
508
|
+
mockClient = {
|
|
509
|
+
accessSecretVersion: function (req) { return __awaiter(void 0, void 0, void 0, function () {
|
|
510
|
+
return __generator(this, function (_a) {
|
|
511
|
+
calls.push(req.name);
|
|
512
|
+
return [2 /*return*/, [{ payload: { data: "latest-value" } }]];
|
|
513
|
+
});
|
|
514
|
+
}); },
|
|
515
|
+
};
|
|
516
|
+
provider = new secretProviders_1.GcpSecretProvider({ projectId: "ignored" });
|
|
517
|
+
injectClient(provider, mockClient);
|
|
518
|
+
return [4 /*yield*/, provider.getSecret("projects/p/secrets/s")];
|
|
519
|
+
case 1:
|
|
520
|
+
result = _a.sent();
|
|
521
|
+
(0, bun_test_1.expect)(result).toBe("latest-value");
|
|
522
|
+
(0, bun_test_1.expect)(calls).toEqual(["projects/p/secrets/s/versions/latest"]);
|
|
523
|
+
return [2 /*return*/];
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
}); });
|
|
527
|
+
(0, bun_test_1.it)("appends the explicit version when full path lacks /versions/", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
528
|
+
var calls, mockClient, provider, result;
|
|
529
|
+
return __generator(this, function (_a) {
|
|
530
|
+
switch (_a.label) {
|
|
531
|
+
case 0:
|
|
532
|
+
calls = [];
|
|
533
|
+
mockClient = {
|
|
534
|
+
accessSecretVersion: function (req) { return __awaiter(void 0, void 0, void 0, function () {
|
|
535
|
+
return __generator(this, function (_a) {
|
|
536
|
+
calls.push(req.name);
|
|
537
|
+
return [2 /*return*/, [{ payload: { data: "v5" } }]];
|
|
538
|
+
});
|
|
539
|
+
}); },
|
|
540
|
+
};
|
|
541
|
+
provider = new secretProviders_1.GcpSecretProvider({ projectId: "ignored" });
|
|
542
|
+
injectClient(provider, mockClient);
|
|
543
|
+
return [4 /*yield*/, provider.getSecret("projects/p/secrets/s", "5")];
|
|
544
|
+
case 1:
|
|
545
|
+
result = _a.sent();
|
|
546
|
+
(0, bun_test_1.expect)(result).toBe("v5");
|
|
547
|
+
(0, bun_test_1.expect)(calls).toEqual(["projects/p/secrets/s/versions/5"]);
|
|
548
|
+
return [2 /*return*/];
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
}); });
|
|
552
|
+
(0, bun_test_1.it)("decodes a Uint8Array payload", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
553
|
+
var encoded, mockClient, provider, _a;
|
|
554
|
+
return __generator(this, function (_b) {
|
|
555
|
+
switch (_b.label) {
|
|
556
|
+
case 0:
|
|
557
|
+
encoded = new TextEncoder().encode("binary-secret");
|
|
558
|
+
mockClient = {
|
|
559
|
+
accessSecretVersion: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
560
|
+
return [2 /*return*/, [{ payload: { data: encoded } }]];
|
|
561
|
+
}); }); },
|
|
562
|
+
};
|
|
563
|
+
provider = new secretProviders_1.GcpSecretProvider({ projectId: "p" });
|
|
564
|
+
injectClient(provider, mockClient);
|
|
565
|
+
_a = bun_test_1.expect;
|
|
566
|
+
return [4 /*yield*/, provider.getSecret("bin-key")];
|
|
567
|
+
case 1:
|
|
568
|
+
_a.apply(void 0, [_b.sent()]).toBe("binary-secret");
|
|
569
|
+
return [2 /*return*/];
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
}); });
|
|
573
|
+
(0, bun_test_1.it)("returns null when the payload is empty", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
574
|
+
var mockClient, provider, _a;
|
|
575
|
+
return __generator(this, function (_b) {
|
|
576
|
+
switch (_b.label) {
|
|
577
|
+
case 0:
|
|
578
|
+
mockClient = {
|
|
579
|
+
accessSecretVersion: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
580
|
+
return [2 /*return*/, [{ payload: {} }]];
|
|
581
|
+
}); }); },
|
|
582
|
+
};
|
|
583
|
+
provider = new secretProviders_1.GcpSecretProvider({ projectId: "p" });
|
|
584
|
+
injectClient(provider, mockClient);
|
|
585
|
+
_a = bun_test_1.expect;
|
|
586
|
+
return [4 /*yield*/, provider.getSecret("empty-payload")];
|
|
587
|
+
case 1:
|
|
588
|
+
_a.apply(void 0, [_b.sent()]).toBeNull();
|
|
589
|
+
return [2 /*return*/];
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
}); });
|
|
593
|
+
(0, bun_test_1.it)("returns null when the payload field is missing entirely", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
594
|
+
var mockClient, provider, _a;
|
|
595
|
+
return __generator(this, function (_b) {
|
|
596
|
+
switch (_b.label) {
|
|
597
|
+
case 0:
|
|
598
|
+
mockClient = {
|
|
599
|
+
accessSecretVersion: function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
|
|
600
|
+
return [2 /*return*/, [{}]];
|
|
601
|
+
}); }); },
|
|
602
|
+
};
|
|
603
|
+
provider = new secretProviders_1.GcpSecretProvider({ projectId: "p" });
|
|
604
|
+
injectClient(provider, mockClient);
|
|
605
|
+
_a = bun_test_1.expect;
|
|
606
|
+
return [4 /*yield*/, provider.getSecret("no-payload")];
|
|
607
|
+
case 1:
|
|
608
|
+
_a.apply(void 0, [_b.sent()]).toBeNull();
|
|
609
|
+
return [2 /*return*/];
|
|
610
|
+
}
|
|
611
|
+
});
|
|
612
|
+
}); });
|
|
613
|
+
(0, bun_test_1.it)("returns null on NOT_FOUND (gRPC code 5)", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
614
|
+
var notFound, mockClient, provider, _a;
|
|
615
|
+
return __generator(this, function (_b) {
|
|
616
|
+
switch (_b.label) {
|
|
617
|
+
case 0:
|
|
618
|
+
notFound = Object.assign(new Error("NOT_FOUND"), { code: 5 });
|
|
619
|
+
mockClient = {
|
|
620
|
+
accessSecretVersion: function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
621
|
+
return __generator(this, function (_a) {
|
|
622
|
+
throw notFound;
|
|
623
|
+
});
|
|
624
|
+
}); },
|
|
625
|
+
};
|
|
626
|
+
provider = new secretProviders_1.GcpSecretProvider({ projectId: "p" });
|
|
627
|
+
injectClient(provider, mockClient);
|
|
628
|
+
_a = bun_test_1.expect;
|
|
629
|
+
return [4 /*yield*/, provider.getSecret("missing-secret")];
|
|
630
|
+
case 1:
|
|
631
|
+
_a.apply(void 0, [_b.sent()]).toBeNull();
|
|
632
|
+
return [2 /*return*/];
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
}); });
|
|
636
|
+
(0, bun_test_1.it)("re-throws non-NOT_FOUND errors", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
637
|
+
var permissionDenied, mockClient, provider, error_2;
|
|
638
|
+
return __generator(this, function (_a) {
|
|
639
|
+
switch (_a.label) {
|
|
640
|
+
case 0:
|
|
641
|
+
permissionDenied = Object.assign(new Error("PERMISSION_DENIED"), { code: 7 });
|
|
642
|
+
mockClient = {
|
|
643
|
+
accessSecretVersion: function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
644
|
+
return __generator(this, function (_a) {
|
|
645
|
+
throw permissionDenied;
|
|
646
|
+
});
|
|
647
|
+
}); },
|
|
648
|
+
};
|
|
649
|
+
provider = new secretProviders_1.GcpSecretProvider({ projectId: "p" });
|
|
650
|
+
injectClient(provider, mockClient);
|
|
651
|
+
_a.label = 1;
|
|
652
|
+
case 1:
|
|
653
|
+
_a.trys.push([1, 3, , 4]);
|
|
654
|
+
return [4 /*yield*/, provider.getSecret("forbidden-secret")];
|
|
655
|
+
case 2:
|
|
656
|
+
_a.sent();
|
|
657
|
+
bun_test_1.expect.unreachable("should have thrown");
|
|
658
|
+
return [3 /*break*/, 4];
|
|
659
|
+
case 3:
|
|
660
|
+
error_2 = _a.sent();
|
|
661
|
+
(0, bun_test_1.expect)(error_2).toBe(permissionDenied);
|
|
662
|
+
return [3 /*break*/, 4];
|
|
663
|
+
case 4: return [2 /*return*/];
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
}); });
|
|
667
|
+
(0, bun_test_1.it)("re-throws non-Error throwables", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
668
|
+
var mockClient, provider, error_3;
|
|
669
|
+
return __generator(this, function (_a) {
|
|
670
|
+
switch (_a.label) {
|
|
671
|
+
case 0:
|
|
672
|
+
mockClient = {
|
|
673
|
+
accessSecretVersion: function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
674
|
+
return __generator(this, function (_a) {
|
|
675
|
+
throw "string-error";
|
|
676
|
+
});
|
|
677
|
+
}); },
|
|
678
|
+
};
|
|
679
|
+
provider = new secretProviders_1.GcpSecretProvider({ projectId: "p" });
|
|
680
|
+
injectClient(provider, mockClient);
|
|
681
|
+
_a.label = 1;
|
|
682
|
+
case 1:
|
|
683
|
+
_a.trys.push([1, 3, , 4]);
|
|
684
|
+
return [4 /*yield*/, provider.getSecret("x")];
|
|
685
|
+
case 2:
|
|
686
|
+
_a.sent();
|
|
687
|
+
bun_test_1.expect.unreachable("should have thrown");
|
|
688
|
+
return [3 /*break*/, 4];
|
|
689
|
+
case 3:
|
|
690
|
+
error_3 = _a.sent();
|
|
691
|
+
(0, bun_test_1.expect)(error_3).toBe("string-error");
|
|
692
|
+
return [3 /*break*/, 4];
|
|
693
|
+
case 4: return [2 /*return*/];
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
}); });
|
|
697
|
+
(0, bun_test_1.it)("caches the client across multiple getSecret calls", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
698
|
+
var callCount, mockClient, provider, _a, _b;
|
|
699
|
+
return __generator(this, function (_c) {
|
|
700
|
+
switch (_c.label) {
|
|
701
|
+
case 0:
|
|
702
|
+
callCount = 0;
|
|
703
|
+
mockClient = {
|
|
704
|
+
accessSecretVersion: function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
705
|
+
return __generator(this, function (_a) {
|
|
706
|
+
callCount++;
|
|
707
|
+
return [2 /*return*/, [{ payload: { data: "call-".concat(callCount) } }]];
|
|
708
|
+
});
|
|
709
|
+
}); },
|
|
710
|
+
};
|
|
711
|
+
provider = new secretProviders_1.GcpSecretProvider({ projectId: "p" });
|
|
712
|
+
injectClient(provider, mockClient);
|
|
713
|
+
_a = bun_test_1.expect;
|
|
714
|
+
return [4 /*yield*/, provider.getSecret("a")];
|
|
715
|
+
case 1:
|
|
716
|
+
_a.apply(void 0, [_c.sent()]).toBe("call-1");
|
|
717
|
+
_b = bun_test_1.expect;
|
|
718
|
+
return [4 /*yield*/, provider.getSecret("b")];
|
|
719
|
+
case 2:
|
|
720
|
+
_b.apply(void 0, [_c.sent()]).toBe("call-2");
|
|
721
|
+
(0, bun_test_1.expect)(callCount).toBe(2);
|
|
722
|
+
return [2 /*return*/];
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
}); });
|
|
726
|
+
});
|
|
@@ -50,7 +50,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
50
50
|
var bun_test_1 = require("bun:test");
|
|
51
51
|
var consentForm_1 = require("./models/consentForm");
|
|
52
52
|
var syncConsents_1 = require("./syncConsents");
|
|
53
|
-
var
|
|
53
|
+
var testHelper_1 = require("./tests/testHelper");
|
|
54
54
|
var baseDef = {
|
|
55
55
|
content: { en: "# Terms\nPlease agree." },
|
|
56
56
|
order: 1,
|
|
@@ -62,7 +62,7 @@ var baseDef = {
|
|
|
62
62
|
(0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
63
63
|
return __generator(this, function (_a) {
|
|
64
64
|
switch (_a.label) {
|
|
65
|
-
case 0: return [4 /*yield*/, (0,
|
|
65
|
+
case 0: return [4 /*yield*/, (0, testHelper_1.setupDb)()];
|
|
66
66
|
case 1:
|
|
67
67
|
_a.sent();
|
|
68
68
|
return [4 /*yield*/, consentForm_1.ConsentForm.deleteMany({})];
|
package/dist/terrenoApp.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import express from "express";
|
|
|
3
3
|
import type { ModelRouterRegistration } from "./api";
|
|
4
4
|
import { type UserModel as UserMongooseModel } from "./auth";
|
|
5
5
|
import { type ConfigurationAppOptions } from "./configurationApp";
|
|
6
|
-
import { type AuthOptions } from "./expressServer";
|
|
6
|
+
import { type AddRoutes, type AuthOptions } from "./expressServer";
|
|
7
7
|
import { type GitHubAuthOptions } from "./githubAuth";
|
|
8
8
|
import { type LoggingOptions } from "./logger";
|
|
9
9
|
import type { RealtimeAppOptions } from "./realtime/types";
|
|
@@ -38,27 +38,40 @@ export interface TerrenoAppOptions {
|
|
|
38
38
|
* Set to `true` for defaults, or pass a RealtimeAppOptions object for full control.
|
|
39
39
|
*/
|
|
40
40
|
realtime?: boolean | RealtimeAppOptions;
|
|
41
|
+
/**
|
|
42
|
+
* Runs after CORS and before the `addMiddleware` chain and JSON body parsing.
|
|
43
|
+
* Use to attach early middleware via `app.use(...)` before JSON parsing.
|
|
44
|
+
*/
|
|
45
|
+
beforeJsonSetup?: (app: express.Application) => void;
|
|
46
|
+
/**
|
|
47
|
+
* Invoked after registered plugins/model routers and before `/auth/me`.
|
|
48
|
+
* Receives the Express app and OpenAPI bundle for `modelRouter` / `createOpenApiBuilder` wiring.
|
|
49
|
+
*/
|
|
50
|
+
configureApp?: AddRoutes;
|
|
41
51
|
}
|
|
42
52
|
/**
|
|
43
53
|
* Fluent API for building Express applications with Terreno framework.
|
|
44
54
|
*
|
|
45
|
-
* TerrenoApp
|
|
46
|
-
*
|
|
47
|
-
*
|
|
55
|
+
* TerrenoApp is the supported way to assemble the Terreno Express stack.
|
|
56
|
+
* Build applications by registering model routers and plugins (and/or
|
|
57
|
+
* `configureApp`), then calling `start()` to listen.
|
|
48
58
|
*
|
|
49
59
|
* The middleware stack is configured in this order:
|
|
50
60
|
* 1. CORS
|
|
51
|
-
* 2.
|
|
52
|
-
* 3.
|
|
53
|
-
* 4.
|
|
54
|
-
* 5.
|
|
55
|
-
* 6.
|
|
56
|
-
* 7.
|
|
57
|
-
* 8.
|
|
58
|
-
* 9.
|
|
61
|
+
* 2. Optional `beforeJsonSetup` (configure the app before JSON parsing)
|
|
62
|
+
* 3. Custom middleware (via addMiddleware)
|
|
63
|
+
* 4. JSON body parser
|
|
64
|
+
* 5. Auth routes (/auth/login, /auth/signup, etc.)
|
|
65
|
+
* 6. JWT authentication setup
|
|
66
|
+
* 7. Request logging
|
|
67
|
+
* 8. Sentry scopes
|
|
68
|
+
* 9. OpenAPI middleware (including JSON `requestId` on object responses)
|
|
59
69
|
* 10. GitHub OAuth routes (if enabled)
|
|
60
|
-
* 11.
|
|
61
|
-
* 12.
|
|
70
|
+
* 11. Configuration app (if any)
|
|
71
|
+
* 12. Registered model routers and plugins
|
|
72
|
+
* 13. Optional `configureApp` callback
|
|
73
|
+
* 14. /auth/me routes
|
|
74
|
+
* 15. Error handling middleware
|
|
62
75
|
*
|
|
63
76
|
* @example
|
|
64
77
|
* ```typescript
|
|
@@ -95,7 +108,6 @@ export interface TerrenoAppOptions {
|
|
|
95
108
|
* .start();
|
|
96
109
|
* ```
|
|
97
110
|
*
|
|
98
|
-
* @see setupServer for the callback-based alternative
|
|
99
111
|
* @see TerrenoPlugin for creating reusable plugins
|
|
100
112
|
* @see modelRouter for creating CRUD route registrations
|
|
101
113
|
*/
|
package/dist/terrenoApp.js
CHANGED
|
@@ -70,6 +70,7 @@ var errors_1 = require("./errors");
|
|
|
70
70
|
var expressServer_1 = require("./expressServer");
|
|
71
71
|
var githubAuth_1 = require("./githubAuth");
|
|
72
72
|
var logger_1 = require("./logger");
|
|
73
|
+
var middleware_1 = require("./middleware");
|
|
73
74
|
var openApiCompat_1 = require("./openApiCompat");
|
|
74
75
|
var openApiEtag_1 = require("./openApiEtag");
|
|
75
76
|
var realtimeApp_1 = require("./realtime/realtimeApp");
|
|
@@ -78,23 +79,26 @@ var index_1 = __importDefault(require("./vendor/wesleytodd-openapi/index"));
|
|
|
78
79
|
/**
|
|
79
80
|
* Fluent API for building Express applications with Terreno framework.
|
|
80
81
|
*
|
|
81
|
-
* TerrenoApp
|
|
82
|
-
*
|
|
83
|
-
*
|
|
82
|
+
* TerrenoApp is the supported way to assemble the Terreno Express stack.
|
|
83
|
+
* Build applications by registering model routers and plugins (and/or
|
|
84
|
+
* `configureApp`), then calling `start()` to listen.
|
|
84
85
|
*
|
|
85
86
|
* The middleware stack is configured in this order:
|
|
86
87
|
* 1. CORS
|
|
87
|
-
* 2.
|
|
88
|
-
* 3.
|
|
89
|
-
* 4.
|
|
90
|
-
* 5.
|
|
91
|
-
* 6.
|
|
92
|
-
* 7.
|
|
93
|
-
* 8.
|
|
94
|
-
* 9.
|
|
88
|
+
* 2. Optional `beforeJsonSetup` (configure the app before JSON parsing)
|
|
89
|
+
* 3. Custom middleware (via addMiddleware)
|
|
90
|
+
* 4. JSON body parser
|
|
91
|
+
* 5. Auth routes (/auth/login, /auth/signup, etc.)
|
|
92
|
+
* 6. JWT authentication setup
|
|
93
|
+
* 7. Request logging
|
|
94
|
+
* 8. Sentry scopes
|
|
95
|
+
* 9. OpenAPI middleware (including JSON `requestId` on object responses)
|
|
95
96
|
* 10. GitHub OAuth routes (if enabled)
|
|
96
|
-
* 11.
|
|
97
|
-
* 12.
|
|
97
|
+
* 11. Configuration app (if any)
|
|
98
|
+
* 12. Registered model routers and plugins
|
|
99
|
+
* 13. Optional `configureApp` callback
|
|
100
|
+
* 14. /auth/me routes
|
|
101
|
+
* 15. Error handling middleware
|
|
98
102
|
*
|
|
99
103
|
* @example
|
|
100
104
|
* ```typescript
|
|
@@ -131,7 +135,6 @@ var index_1 = __importDefault(require("./vendor/wesleytodd-openapi/index"));
|
|
|
131
135
|
* .start();
|
|
132
136
|
* ```
|
|
133
137
|
*
|
|
134
|
-
* @see setupServer for the callback-based alternative
|
|
135
138
|
* @see TerrenoPlugin for creating reusable plugins
|
|
136
139
|
* @see modelRouter for creating CRUD route registrations
|
|
137
140
|
*/
|
|
@@ -254,6 +257,9 @@ var TerrenoApp = /** @class */ (function () {
|
|
|
254
257
|
app.set("query parser", function (str) { var _a; return qs_1.default.parse(str, { arrayLimit: (_a = options.arrayLimit) !== null && _a !== void 0 ? _a : 200 }); });
|
|
255
258
|
app.use(requestContext_1.requestContextMiddleware);
|
|
256
259
|
app.use((0, cors_1.default)({ credentials: true, origin: (_c = options.corsOrigin) !== null && _c !== void 0 ? _c : "*" }));
|
|
260
|
+
if (options.beforeJsonSetup) {
|
|
261
|
+
options.beforeJsonSetup(app);
|
|
262
|
+
}
|
|
257
263
|
try {
|
|
258
264
|
// Apply custom middleware before JSON parsing
|
|
259
265
|
for (var _d = __values(this.middlewareFns), _e = _d.next(); !_e.done; _e = _d.next()) {
|
|
@@ -314,6 +320,7 @@ var TerrenoApp = /** @class */ (function () {
|
|
|
314
320
|
// OpenAPI
|
|
315
321
|
app.use(openApiCompat_1.openApiCompatMiddleware);
|
|
316
322
|
app.use(openApiEtag_1.openApiEtagMiddleware);
|
|
323
|
+
app.use(middleware_1.jsonResponseRequestIdMiddleware);
|
|
317
324
|
var oapi = (0, index_1.default)({
|
|
318
325
|
info: {
|
|
319
326
|
description: "Generated docs from an Express api",
|
|
@@ -355,6 +362,9 @@ var TerrenoApp = /** @class */ (function () {
|
|
|
355
362
|
}
|
|
356
363
|
finally { if (e_2) throw e_2.error; }
|
|
357
364
|
}
|
|
365
|
+
if (options.configureApp) {
|
|
366
|
+
options.configureApp(app, { openApi: oapi });
|
|
367
|
+
}
|
|
358
368
|
// /auth/me must be registered after plugins so that session middleware
|
|
359
369
|
// (e.g. Better Auth) has a chance to populate req.user first.
|
|
360
370
|
(0, auth_1.addMeRoutes)(app, options.userModel, options.authOptions);
|
package/dist/terrenoApp.test.js
CHANGED
|
@@ -104,6 +104,7 @@ var mongoose_1 = __importStar(require("mongoose"));
|
|
|
104
104
|
var supertest_1 = __importDefault(require("supertest"));
|
|
105
105
|
var api_1 = require("./api");
|
|
106
106
|
var configurationPlugin_1 = require("./configurationPlugin");
|
|
107
|
+
var errors_1 = require("./errors");
|
|
107
108
|
var permissions_1 = require("./permissions");
|
|
108
109
|
var plugins_1 = require("./plugins");
|
|
109
110
|
var terrenoApp_1 = require("./terrenoApp");
|
|
@@ -125,6 +126,24 @@ var typedUserModel = tests_1.UserModel;
|
|
|
125
126
|
}).build();
|
|
126
127
|
(0, bun_test_1.expect)(app).toBeDefined();
|
|
127
128
|
});
|
|
129
|
+
(0, bun_test_1.it)("does not add requestId to GET /openapi.json document bodies", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
130
|
+
var app, res;
|
|
131
|
+
return __generator(this, function (_d) {
|
|
132
|
+
switch (_d.label) {
|
|
133
|
+
case 0:
|
|
134
|
+
app = new terrenoApp_1.TerrenoApp({
|
|
135
|
+
skipListen: true,
|
|
136
|
+
userModel: typedUserModel,
|
|
137
|
+
}).build();
|
|
138
|
+
return [4 /*yield*/, (0, supertest_1.default)(app).get("/openapi.json").expect(200)];
|
|
139
|
+
case 1:
|
|
140
|
+
res = _d.sent();
|
|
141
|
+
(0, bun_test_1.expect)(res.body.openapi).toBe("3.0.0");
|
|
142
|
+
(0, bun_test_1.expect)(res.body.requestId).toBeUndefined();
|
|
143
|
+
return [2 /*return*/];
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}); });
|
|
128
147
|
(0, bun_test_1.it)("creates server with custom corsOrigin", function () {
|
|
129
148
|
var app = new terrenoApp_1.TerrenoApp({
|
|
130
149
|
corsOrigin: "https://example.com",
|
|
@@ -196,6 +215,7 @@ var typedUserModel = tests_1.UserModel;
|
|
|
196
215
|
res = _d.sent();
|
|
197
216
|
(0, bun_test_1.expect)(res.body.data).toHaveLength(1);
|
|
198
217
|
(0, bun_test_1.expect)(res.body.data[0].name).toBe("Apple");
|
|
218
|
+
(0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
|
|
199
219
|
return [2 /*return*/];
|
|
200
220
|
}
|
|
201
221
|
});
|
|
@@ -301,6 +321,7 @@ var typedUserModel = tests_1.UserModel;
|
|
|
301
321
|
case 2:
|
|
302
322
|
res = _d.sent();
|
|
303
323
|
(0, bun_test_1.expect)(res.status).toBe(200);
|
|
324
|
+
(0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
|
|
304
325
|
return [2 /*return*/];
|
|
305
326
|
}
|
|
306
327
|
});
|
|
@@ -356,6 +377,37 @@ var typedUserModel = tests_1.UserModel;
|
|
|
356
377
|
case 1:
|
|
357
378
|
res = _d.sent();
|
|
358
379
|
(0, bun_test_1.expect)(res.status).toBe(500);
|
|
380
|
+
(0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
|
|
381
|
+
(0, bun_test_1.expect)(res.body.status).toBe(500);
|
|
382
|
+
(0, bun_test_1.expect)(res.body.title).toBe("Internal server error");
|
|
383
|
+
return [2 /*return*/];
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}); });
|
|
387
|
+
(0, bun_test_1.it)("adds requestId to APIError JSON responses", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
388
|
+
var plugin, app, res;
|
|
389
|
+
return __generator(this, function (_d) {
|
|
390
|
+
switch (_d.label) {
|
|
391
|
+
case 0:
|
|
392
|
+
plugin = {
|
|
393
|
+
register: function (pluginApp) {
|
|
394
|
+
pluginApp.get("/api-error-route", function () {
|
|
395
|
+
throw new errors_1.APIError({ status: 400, title: "Bad request test" });
|
|
396
|
+
});
|
|
397
|
+
},
|
|
398
|
+
};
|
|
399
|
+
app = new terrenoApp_1.TerrenoApp({
|
|
400
|
+
skipListen: true,
|
|
401
|
+
userModel: typedUserModel,
|
|
402
|
+
})
|
|
403
|
+
.register(plugin)
|
|
404
|
+
.build();
|
|
405
|
+
return [4 /*yield*/, (0, supertest_1.default)(app).get("/api-error-route").set("X-Request-ID", "api-err-rid")];
|
|
406
|
+
case 1:
|
|
407
|
+
res = _d.sent();
|
|
408
|
+
(0, bun_test_1.expect)(res.status).toBe(400);
|
|
409
|
+
(0, bun_test_1.expect)(res.body.requestId).toBe("api-err-rid");
|
|
410
|
+
(0, bun_test_1.expect)(res.body.title).toBe("Bad request test");
|
|
359
411
|
return [2 /*return*/];
|
|
360
412
|
}
|
|
361
413
|
});
|