@terreno/api 0.9.3 → 0.11.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.
Files changed (52) hide show
  1. package/bunfig.toml +5 -2
  2. package/bunfig.unit.toml +3 -0
  3. package/dist/auth.test.js +257 -0
  4. package/dist/consentApp.test.js +245 -0
  5. package/dist/expressServer.js +3 -9
  6. package/dist/expressServer.test.js +4 -7
  7. package/dist/githubAuth.test.js +380 -0
  8. package/dist/logger.test.d.ts +1 -0
  9. package/dist/logger.test.js +143 -0
  10. package/dist/notifiers/googleChatNotifier.test.js +37 -0
  11. package/dist/openApi.js +2 -2
  12. package/dist/openApi.test.js +125 -0
  13. package/dist/openApiBuilder.d.ts +1 -0
  14. package/dist/openApiBuilder.js +13 -2
  15. package/dist/openApiBuilder.test.js +66 -0
  16. package/dist/openApiEtag.test.js +8 -0
  17. package/dist/openApiValidator.test.js +309 -0
  18. package/dist/permissions.middleware.test.d.ts +1 -0
  19. package/dist/permissions.middleware.test.js +341 -0
  20. package/dist/plugins.d.ts +8 -8
  21. package/dist/plugins.js +38 -32
  22. package/dist/populate.test.js +99 -0
  23. package/dist/syncConsents.js +2 -2
  24. package/dist/syncConsents.test.js +273 -0
  25. package/dist/tests/bunSetup.js +27 -22
  26. package/dist/tests.d.ts +3 -3
  27. package/dist/tests.js +78 -82
  28. package/dist/utils.d.ts +2 -2
  29. package/dist/utils.js +7 -7
  30. package/package.json +2 -1
  31. package/src/__snapshots__/openApi.test.ts.snap +48 -0
  32. package/src/auth.test.ts +147 -0
  33. package/src/consentApp.test.ts +162 -0
  34. package/src/expressServer.test.ts +4 -11
  35. package/src/expressServer.ts +4 -8
  36. package/src/githubAuth.test.ts +307 -1
  37. package/src/logger.test.ts +149 -0
  38. package/src/notifiers/googleChatNotifier.test.ts +24 -0
  39. package/src/openApi.test.ts +157 -1
  40. package/src/openApi.ts +6 -2
  41. package/src/openApiBuilder.test.ts +81 -0
  42. package/src/openApiBuilder.ts +17 -2
  43. package/src/openApiEtag.test.ts +11 -0
  44. package/src/openApiValidator.test.ts +410 -0
  45. package/src/permissions.middleware.test.ts +197 -0
  46. package/src/plugins.ts +32 -23
  47. package/src/populate.test.ts +78 -2
  48. package/src/syncConsents.test.ts +145 -0
  49. package/src/syncConsents.ts +1 -1
  50. package/src/tests/bunSetup.ts +14 -8
  51. package/src/tests.ts +8 -8
  52. package/src/utils.ts +4 -4
package/bunfig.toml CHANGED
@@ -2,6 +2,9 @@
2
2
  preload = ["./src/tests/bunSetup.ts"]
3
3
  root = "./src"
4
4
  coverage = true
5
- coverageExclude = ["dist/**", "**/*.test.ts", "**/tests/**"]
6
- coverageThreshold = { line = 80, function = 80 }
5
+ coveragePathIgnorePatterns = ["dist/**", "**/*.test.ts", "**/tests/**", "**/vendor/**", "../**"]
6
+ # Enforced by scripts/check-coverage.ts; Bun parses this key but does not
7
+ # fail the run when coverage is below threshold. See
8
+ # https://github.com/oven-sh/bun/issues/7367 and https://github.com/oven-sh/bun/pull/27933.
9
+ coverageThreshold = { line = 95, function = 95 }
7
10
 
@@ -0,0 +1,3 @@
1
+ [test]
2
+ root = "./src"
3
+ preload = []
package/dist/auth.test.js CHANGED
@@ -910,4 +910,261 @@ var utils_1 = require("./utils");
910
910
  }
911
911
  });
912
912
  }); });
913
+ (0, bun_test_1.it)("throws when TOKEN_SECRET is not set", function () { return __awaiter(void 0, void 0, void 0, function () {
914
+ var caught, error_1;
915
+ return __generator(this, function (_a) {
916
+ switch (_a.label) {
917
+ case 0:
918
+ process.env.TOKEN_SECRET = "";
919
+ _a.label = 1;
920
+ case 1:
921
+ _a.trys.push([1, 3, , 4]);
922
+ return [4 /*yield*/, (0, auth_1.generateTokens)({ _id: "user-123" })];
923
+ case 2:
924
+ _a.sent();
925
+ return [3 /*break*/, 4];
926
+ case 3:
927
+ error_1 = _a.sent();
928
+ caught = error_1;
929
+ return [3 /*break*/, 4];
930
+ case 4:
931
+ (0, bun_test_1.expect)(caught).toBeDefined();
932
+ (0, bun_test_1.expect)(caught.message).toBe("TOKEN_SECRET must be set in env.");
933
+ return [2 /*return*/];
934
+ }
935
+ });
936
+ }); });
937
+ (0, bun_test_1.it)("uses TOKEN_EXPIRES_IN from env when valid", function () { return __awaiter(void 0, void 0, void 0, function () {
938
+ var jwtLib, result, decoded, expectedExp;
939
+ return __generator(this, function (_a) {
940
+ switch (_a.label) {
941
+ case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("jsonwebtoken")); })];
942
+ case 1:
943
+ jwtLib = _a.sent();
944
+ process.env.TOKEN_EXPIRES_IN = "2h";
945
+ return [4 /*yield*/, (0, auth_1.generateTokens)({ _id: "user-123" })];
946
+ case 2:
947
+ result = _a.sent();
948
+ decoded = jwtLib.decode(result.token);
949
+ expectedExp = Math.floor(Date.now() / 1000) + 2 * 3600;
950
+ (0, bun_test_1.expect)(decoded.exp).toBeGreaterThan(expectedExp - 10);
951
+ (0, bun_test_1.expect)(decoded.exp).toBeLessThan(expectedExp + 10);
952
+ return [2 /*return*/];
953
+ }
954
+ });
955
+ }); });
956
+ (0, bun_test_1.it)("uses REFRESH_TOKEN_EXPIRES_IN from env when valid", function () { return __awaiter(void 0, void 0, void 0, function () {
957
+ var jwtLib, result, decoded, expectedExp;
958
+ return __generator(this, function (_a) {
959
+ switch (_a.label) {
960
+ case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("jsonwebtoken")); })];
961
+ case 1:
962
+ jwtLib = _a.sent();
963
+ process.env.REFRESH_TOKEN_EXPIRES_IN = "1h";
964
+ return [4 /*yield*/, (0, auth_1.generateTokens)({ _id: "user-123" })];
965
+ case 2:
966
+ result = _a.sent();
967
+ decoded = jwtLib.decode(result.refreshToken);
968
+ expectedExp = Math.floor(Date.now() / 1000) + 3600;
969
+ (0, bun_test_1.expect)(decoded.exp).toBeGreaterThan(expectedExp - 10);
970
+ (0, bun_test_1.expect)(decoded.exp).toBeLessThan(expectedExp + 10);
971
+ return [2 /*return*/];
972
+ }
973
+ });
974
+ }); });
975
+ (0, bun_test_1.it)("does not issue refresh token when REFRESH_TOKEN_SECRET is not set", function () { return __awaiter(void 0, void 0, void 0, function () {
976
+ var result;
977
+ return __generator(this, function (_a) {
978
+ switch (_a.label) {
979
+ case 0:
980
+ process.env.REFRESH_TOKEN_SECRET = "";
981
+ return [4 /*yield*/, (0, auth_1.generateTokens)({ _id: "user-123" })];
982
+ case 1:
983
+ result = _a.sent();
984
+ (0, bun_test_1.expect)(result.token).toBeDefined();
985
+ (0, bun_test_1.expect)(result.refreshToken).toBeUndefined();
986
+ return [2 /*return*/];
987
+ }
988
+ });
989
+ }); });
990
+ });
991
+ (0, bun_test_1.describe)("addAuthRoutes /refresh_token error paths", function () {
992
+ var app;
993
+ var agent;
994
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
995
+ return __generator(this, function (_a) {
996
+ switch (_a.label) {
997
+ case 0:
998
+ (0, bun_test_1.setSystemTime)();
999
+ return [4 /*yield*/, (0, tests_1.setupDb)()];
1000
+ case 1:
1001
+ _a.sent();
1002
+ app = (0, expressServer_1.setupServer)({
1003
+ addRoutes: function () { },
1004
+ skipListen: true,
1005
+ userModel: tests_1.UserModel,
1006
+ });
1007
+ agent = supertest_1.default.agent(app);
1008
+ return [2 /*return*/];
1009
+ }
1010
+ });
1011
+ }); });
1012
+ (0, bun_test_1.afterEach)(function () {
1013
+ (0, bun_test_1.setSystemTime)();
1014
+ });
1015
+ (0, bun_test_1.it)("returns 401 when no refreshToken in body", function () { return __awaiter(void 0, void 0, void 0, function () {
1016
+ var res;
1017
+ return __generator(this, function (_a) {
1018
+ switch (_a.label) {
1019
+ case 0: return [4 /*yield*/, agent.post("/auth/refresh_token").send({}).expect(401)];
1020
+ case 1:
1021
+ res = _a.sent();
1022
+ (0, bun_test_1.expect)(res.body.message).toContain("No refresh token provided");
1023
+ return [2 /*return*/];
1024
+ }
1025
+ });
1026
+ }); });
1027
+ (0, bun_test_1.it)("returns 401 when refresh token is invalid", function () { return __awaiter(void 0, void 0, void 0, function () {
1028
+ var res;
1029
+ return __generator(this, function (_a) {
1030
+ switch (_a.label) {
1031
+ case 0: return [4 /*yield*/, agent
1032
+ .post("/auth/refresh_token")
1033
+ .send({ refreshToken: "not-a-valid-jwt" })
1034
+ .expect(401)];
1035
+ case 1:
1036
+ res = _a.sent();
1037
+ (0, bun_test_1.expect)(res.body.message).toBeDefined();
1038
+ return [2 /*return*/];
1039
+ }
1040
+ });
1041
+ }); });
1042
+ (0, bun_test_1.it)("returns 401 when refresh token is signed with wrong secret", function () { return __awaiter(void 0, void 0, void 0, function () {
1043
+ var jwtLib, bogusToken, res;
1044
+ return __generator(this, function (_a) {
1045
+ switch (_a.label) {
1046
+ case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("jsonwebtoken")); })];
1047
+ case 1:
1048
+ jwtLib = (_a.sent()).default;
1049
+ bogusToken = jwtLib.sign({ id: "abc" }, "different-secret");
1050
+ return [4 /*yield*/, agent
1051
+ .post("/auth/refresh_token")
1052
+ .send({ refreshToken: bogusToken })
1053
+ .expect(401)];
1054
+ case 2:
1055
+ res = _a.sent();
1056
+ (0, bun_test_1.expect)(res.body.message).toBeDefined();
1057
+ return [2 /*return*/];
1058
+ }
1059
+ });
1060
+ }); });
1061
+ (0, bun_test_1.it)("returns 401 when refresh token has no id", function () { return __awaiter(void 0, void 0, void 0, function () {
1062
+ var jwtLib, tokenNoId, res;
1063
+ return __generator(this, function (_a) {
1064
+ switch (_a.label) {
1065
+ case 0: return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("jsonwebtoken")); })];
1066
+ case 1:
1067
+ jwtLib = (_a.sent()).default;
1068
+ tokenNoId = jwtLib.sign({ foo: "bar" }, process.env.REFRESH_TOKEN_SECRET);
1069
+ return [4 /*yield*/, agent.post("/auth/refresh_token").send({ refreshToken: tokenNoId }).expect(401)];
1070
+ case 2:
1071
+ res = _a.sent();
1072
+ (0, bun_test_1.expect)(res.body.message).toBe("Invalid refresh token");
1073
+ return [2 /*return*/];
1074
+ }
1075
+ });
1076
+ }); });
1077
+ (0, bun_test_1.it)("issues new tokens on valid refresh", function () { return __awaiter(void 0, void 0, void 0, function () {
1078
+ var _a, adminUser, jwtLib, validToken, res;
1079
+ return __generator(this, function (_b) {
1080
+ switch (_b.label) {
1081
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
1082
+ case 1:
1083
+ _a = __read.apply(void 0, [_b.sent(), 1]), adminUser = _a[0];
1084
+ return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("jsonwebtoken")); })];
1085
+ case 2:
1086
+ jwtLib = (_b.sent()).default;
1087
+ validToken = jwtLib.sign({ id: adminUser._id.toString() }, process.env.REFRESH_TOKEN_SECRET);
1088
+ return [4 /*yield*/, agent
1089
+ .post("/auth/refresh_token")
1090
+ .send({ refreshToken: validToken })
1091
+ .expect(200)];
1092
+ case 3:
1093
+ res = _b.sent();
1094
+ (0, bun_test_1.expect)(res.body.data.token).toBeDefined();
1095
+ (0, bun_test_1.expect)(res.body.data.refreshToken).toBeDefined();
1096
+ return [2 /*return*/];
1097
+ }
1098
+ });
1099
+ }); });
1100
+ });
1101
+ (0, bun_test_1.describe)("addMeRoutes edge cases", function () {
1102
+ var app;
1103
+ var agent;
1104
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
1105
+ return __generator(this, function (_a) {
1106
+ switch (_a.label) {
1107
+ case 0:
1108
+ (0, bun_test_1.setSystemTime)();
1109
+ return [4 /*yield*/, (0, tests_1.setupDb)()];
1110
+ case 1:
1111
+ _a.sent();
1112
+ app = (0, expressServer_1.setupServer)({
1113
+ addRoutes: function () { },
1114
+ skipListen: true,
1115
+ userModel: tests_1.UserModel,
1116
+ });
1117
+ agent = supertest_1.default.agent(app);
1118
+ return [2 /*return*/];
1119
+ }
1120
+ });
1121
+ }); });
1122
+ (0, bun_test_1.afterEach)(function () {
1123
+ (0, bun_test_1.setSystemTime)();
1124
+ });
1125
+ (0, bun_test_1.it)("GET /auth/me returns 401 without auth", function () { return __awaiter(void 0, void 0, void 0, function () {
1126
+ return __generator(this, function (_a) {
1127
+ switch (_a.label) {
1128
+ case 0: return [4 /*yield*/, agent.get("/auth/me").expect(401)];
1129
+ case 1:
1130
+ _a.sent();
1131
+ return [2 /*return*/];
1132
+ }
1133
+ });
1134
+ }); });
1135
+ (0, bun_test_1.it)("PATCH /auth/me returns 401 without auth", function () { return __awaiter(void 0, void 0, void 0, function () {
1136
+ return __generator(this, function (_a) {
1137
+ switch (_a.label) {
1138
+ case 0: return [4 /*yield*/, agent.patch("/auth/me").send({ email: "x@x.com" }).expect(401)];
1139
+ case 1:
1140
+ _a.sent();
1141
+ return [2 /*return*/];
1142
+ }
1143
+ });
1144
+ }); });
1145
+ (0, bun_test_1.it)("GET /auth/me returns 404 when user is deleted after auth", function () { return __awaiter(void 0, void 0, void 0, function () {
1146
+ var _a, _admin, notAdmin, jwtLib, token, res;
1147
+ return __generator(this, function (_b) {
1148
+ switch (_b.label) {
1149
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
1150
+ case 1:
1151
+ _a = __read.apply(void 0, [_b.sent(), 2]), _admin = _a[0], notAdmin = _a[1];
1152
+ return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("jsonwebtoken")); })];
1153
+ case 2:
1154
+ jwtLib = (_b.sent()).default;
1155
+ token = jwtLib.sign({ id: notAdmin._id.toString() }, process.env.TOKEN_SECRET, { issuer: process.env.TOKEN_ISSUER });
1156
+ // Delete the user so findById returns null
1157
+ return [4 /*yield*/, tests_1.UserModel.deleteOne({ _id: notAdmin._id })];
1158
+ case 3:
1159
+ // Delete the user so findById returns null
1160
+ _b.sent();
1161
+ return [4 /*yield*/, agent.get("/auth/me").set("authorization", "Bearer ".concat(token))];
1162
+ case 4:
1163
+ res = _b.sent();
1164
+ // Either 404 (user not found in /me handler) or 401 (auth middleware rejects)
1165
+ (0, bun_test_1.expect)([401, 404]).toContain(res.status);
1166
+ return [2 /*return*/];
1167
+ }
1168
+ });
1169
+ }); });
913
1170
  });
@@ -1065,6 +1065,251 @@ var buildApp = function (consentAppOptions) {
1065
1065
  });
1066
1066
  }); });
1067
1067
  });
1068
+ (0, bun_test_1.describe)("POST /consent-forms/generate (aiConfig)", function () {
1069
+ var aiConfig = {
1070
+ generateContent: function (_a) { return __awaiter(void 0, [_a], void 0, function (_b) {
1071
+ var type = _b.type, description = _b.description, locale = _b.locale;
1072
+ return __generator(this, function (_c) {
1073
+ return [2 /*return*/, "Generated ".concat(type, " content (").concat(locale, ") for: ").concat(description)];
1074
+ });
1075
+ }); },
1076
+ translateContent: function (_a) { return __awaiter(void 0, [_a], void 0, function (_b) {
1077
+ var content = _b.content, fromLocale = _b.fromLocale, toLocale = _b.toLocale;
1078
+ return __generator(this, function (_c) {
1079
+ return [2 /*return*/, "[".concat(fromLocale, "->").concat(toLocale, "] ").concat(content)];
1080
+ });
1081
+ }); },
1082
+ };
1083
+ (0, bun_test_1.it)("generates consent form content for admins", function () { return __awaiter(void 0, void 0, void 0, function () {
1084
+ var aiApp, aiAdmin, res;
1085
+ return __generator(this, function (_a) {
1086
+ switch (_a.label) {
1087
+ case 0:
1088
+ aiApp = buildApp({ aiConfig: aiConfig });
1089
+ return [4 /*yield*/, (0, tests_1.authAsUser)(aiApp, "admin")];
1090
+ case 1:
1091
+ aiAdmin = _a.sent();
1092
+ return [4 /*yield*/, aiAdmin
1093
+ .post("/consent-forms/generate")
1094
+ .send({ description: "Privacy policy for a health app", locale: "es", type: "privacy" })
1095
+ .expect(200)];
1096
+ case 2:
1097
+ res = _a.sent();
1098
+ (0, bun_test_1.expect)(res.body.data.content).toBe("Generated privacy content (es) for: Privacy policy for a health app");
1099
+ return [2 /*return*/];
1100
+ }
1101
+ });
1102
+ }); });
1103
+ (0, bun_test_1.it)("defaults locale to en when not provided", function () { return __awaiter(void 0, void 0, void 0, function () {
1104
+ var aiApp, aiAdmin, res;
1105
+ return __generator(this, function (_a) {
1106
+ switch (_a.label) {
1107
+ case 0:
1108
+ aiApp = buildApp({ aiConfig: aiConfig });
1109
+ return [4 /*yield*/, (0, tests_1.authAsUser)(aiApp, "admin")];
1110
+ case 1:
1111
+ aiAdmin = _a.sent();
1112
+ return [4 /*yield*/, aiAdmin
1113
+ .post("/consent-forms/generate")
1114
+ .send({ description: "Terms", type: "terms" })
1115
+ .expect(200)];
1116
+ case 2:
1117
+ res = _a.sent();
1118
+ (0, bun_test_1.expect)(res.body.data.content).toContain("(en)");
1119
+ return [2 /*return*/];
1120
+ }
1121
+ });
1122
+ }); });
1123
+ (0, bun_test_1.it)("returns 403 for non-admin users", function () { return __awaiter(void 0, void 0, void 0, function () {
1124
+ var aiApp, aiUser;
1125
+ return __generator(this, function (_a) {
1126
+ switch (_a.label) {
1127
+ case 0:
1128
+ aiApp = buildApp({ aiConfig: aiConfig });
1129
+ return [4 /*yield*/, (0, tests_1.authAsUser)(aiApp, "notAdmin")];
1130
+ case 1:
1131
+ aiUser = _a.sent();
1132
+ return [4 /*yield*/, aiUser
1133
+ .post("/consent-forms/generate")
1134
+ .send({ description: "Privacy", type: "privacy" })
1135
+ .expect(403)];
1136
+ case 2:
1137
+ _a.sent();
1138
+ return [2 /*return*/];
1139
+ }
1140
+ });
1141
+ }); });
1142
+ (0, bun_test_1.it)("returns 400 when type is missing", function () { return __awaiter(void 0, void 0, void 0, function () {
1143
+ var aiApp, aiAdmin;
1144
+ return __generator(this, function (_a) {
1145
+ switch (_a.label) {
1146
+ case 0:
1147
+ aiApp = buildApp({ aiConfig: aiConfig });
1148
+ return [4 /*yield*/, (0, tests_1.authAsUser)(aiApp, "admin")];
1149
+ case 1:
1150
+ aiAdmin = _a.sent();
1151
+ return [4 /*yield*/, aiAdmin.post("/consent-forms/generate").send({ description: "Privacy" }).expect(400)];
1152
+ case 2:
1153
+ _a.sent();
1154
+ return [2 /*return*/];
1155
+ }
1156
+ });
1157
+ }); });
1158
+ (0, bun_test_1.it)("returns 400 when description is missing", function () { return __awaiter(void 0, void 0, void 0, function () {
1159
+ var aiApp, aiAdmin;
1160
+ return __generator(this, function (_a) {
1161
+ switch (_a.label) {
1162
+ case 0:
1163
+ aiApp = buildApp({ aiConfig: aiConfig });
1164
+ return [4 /*yield*/, (0, tests_1.authAsUser)(aiApp, "admin")];
1165
+ case 1:
1166
+ aiAdmin = _a.sent();
1167
+ return [4 /*yield*/, aiAdmin.post("/consent-forms/generate").send({ type: "privacy" }).expect(400)];
1168
+ case 2:
1169
+ _a.sent();
1170
+ return [2 /*return*/];
1171
+ }
1172
+ });
1173
+ }); });
1174
+ (0, bun_test_1.it)("returns 404 when aiConfig is not provided", function () { return __awaiter(void 0, void 0, void 0, function () {
1175
+ return __generator(this, function (_a) {
1176
+ switch (_a.label) {
1177
+ case 0: return [4 /*yield*/, adminAgent
1178
+ .post("/consent-forms/generate")
1179
+ .send({ description: "Privacy", type: "privacy" })
1180
+ .expect(404)];
1181
+ case 1:
1182
+ _a.sent();
1183
+ return [2 /*return*/];
1184
+ }
1185
+ });
1186
+ }); });
1187
+ });
1188
+ (0, bun_test_1.describe)("POST /consent-forms/translate (aiConfig)", function () {
1189
+ var aiConfig = {
1190
+ generateContent: function (_a) { return __awaiter(void 0, [_a], void 0, function (_b) {
1191
+ var type = _b.type, description = _b.description, locale = _b.locale;
1192
+ return __generator(this, function (_c) {
1193
+ return [2 /*return*/, "Generated ".concat(type, " content (").concat(locale, ") for: ").concat(description)];
1194
+ });
1195
+ }); },
1196
+ translateContent: function (_a) { return __awaiter(void 0, [_a], void 0, function (_b) {
1197
+ var content = _b.content, fromLocale = _b.fromLocale, toLocale = _b.toLocale;
1198
+ return __generator(this, function (_c) {
1199
+ return [2 /*return*/, "[".concat(fromLocale, "->").concat(toLocale, "] ").concat(content)];
1200
+ });
1201
+ }); },
1202
+ };
1203
+ (0, bun_test_1.it)("translates consent form content for admins", function () { return __awaiter(void 0, void 0, void 0, function () {
1204
+ var aiApp, aiAdmin, res;
1205
+ return __generator(this, function (_a) {
1206
+ switch (_a.label) {
1207
+ case 0:
1208
+ aiApp = buildApp({ aiConfig: aiConfig });
1209
+ return [4 /*yield*/, (0, tests_1.authAsUser)(aiApp, "admin")];
1210
+ case 1:
1211
+ aiAdmin = _a.sent();
1212
+ return [4 /*yield*/, aiAdmin
1213
+ .post("/consent-forms/translate")
1214
+ .send({ content: "Hello world", fromLocale: "en", toLocale: "es" })
1215
+ .expect(200)];
1216
+ case 2:
1217
+ res = _a.sent();
1218
+ (0, bun_test_1.expect)(res.body.data.content).toBe("[en->es] Hello world");
1219
+ return [2 /*return*/];
1220
+ }
1221
+ });
1222
+ }); });
1223
+ (0, bun_test_1.it)("returns 403 for non-admin users", function () { return __awaiter(void 0, void 0, void 0, function () {
1224
+ var aiApp, aiUser;
1225
+ return __generator(this, function (_a) {
1226
+ switch (_a.label) {
1227
+ case 0:
1228
+ aiApp = buildApp({ aiConfig: aiConfig });
1229
+ return [4 /*yield*/, (0, tests_1.authAsUser)(aiApp, "notAdmin")];
1230
+ case 1:
1231
+ aiUser = _a.sent();
1232
+ return [4 /*yield*/, aiUser
1233
+ .post("/consent-forms/translate")
1234
+ .send({ content: "Hello", fromLocale: "en", toLocale: "es" })
1235
+ .expect(403)];
1236
+ case 2:
1237
+ _a.sent();
1238
+ return [2 /*return*/];
1239
+ }
1240
+ });
1241
+ }); });
1242
+ (0, bun_test_1.it)("returns 400 when content is missing", function () { return __awaiter(void 0, void 0, void 0, function () {
1243
+ var aiApp, aiAdmin;
1244
+ return __generator(this, function (_a) {
1245
+ switch (_a.label) {
1246
+ case 0:
1247
+ aiApp = buildApp({ aiConfig: aiConfig });
1248
+ return [4 /*yield*/, (0, tests_1.authAsUser)(aiApp, "admin")];
1249
+ case 1:
1250
+ aiAdmin = _a.sent();
1251
+ return [4 /*yield*/, aiAdmin
1252
+ .post("/consent-forms/translate")
1253
+ .send({ fromLocale: "en", toLocale: "es" })
1254
+ .expect(400)];
1255
+ case 2:
1256
+ _a.sent();
1257
+ return [2 /*return*/];
1258
+ }
1259
+ });
1260
+ }); });
1261
+ (0, bun_test_1.it)("returns 400 when fromLocale is missing", function () { return __awaiter(void 0, void 0, void 0, function () {
1262
+ var aiApp, aiAdmin;
1263
+ return __generator(this, function (_a) {
1264
+ switch (_a.label) {
1265
+ case 0:
1266
+ aiApp = buildApp({ aiConfig: aiConfig });
1267
+ return [4 /*yield*/, (0, tests_1.authAsUser)(aiApp, "admin")];
1268
+ case 1:
1269
+ aiAdmin = _a.sent();
1270
+ return [4 /*yield*/, aiAdmin
1271
+ .post("/consent-forms/translate")
1272
+ .send({ content: "Hello", toLocale: "es" })
1273
+ .expect(400)];
1274
+ case 2:
1275
+ _a.sent();
1276
+ return [2 /*return*/];
1277
+ }
1278
+ });
1279
+ }); });
1280
+ (0, bun_test_1.it)("returns 400 when toLocale is missing", function () { return __awaiter(void 0, void 0, void 0, function () {
1281
+ var aiApp, aiAdmin;
1282
+ return __generator(this, function (_a) {
1283
+ switch (_a.label) {
1284
+ case 0:
1285
+ aiApp = buildApp({ aiConfig: aiConfig });
1286
+ return [4 /*yield*/, (0, tests_1.authAsUser)(aiApp, "admin")];
1287
+ case 1:
1288
+ aiAdmin = _a.sent();
1289
+ return [4 /*yield*/, aiAdmin
1290
+ .post("/consent-forms/translate")
1291
+ .send({ content: "Hello", fromLocale: "en" })
1292
+ .expect(400)];
1293
+ case 2:
1294
+ _a.sent();
1295
+ return [2 /*return*/];
1296
+ }
1297
+ });
1298
+ }); });
1299
+ (0, bun_test_1.it)("returns 404 when aiConfig is not provided", function () { return __awaiter(void 0, void 0, void 0, function () {
1300
+ return __generator(this, function (_a) {
1301
+ switch (_a.label) {
1302
+ case 0: return [4 /*yield*/, adminAgent
1303
+ .post("/consent-forms/translate")
1304
+ .send({ content: "Hello", fromLocale: "en", toLocale: "es" })
1305
+ .expect(404)];
1306
+ case 1:
1307
+ _a.sent();
1308
+ return [2 /*return*/];
1309
+ }
1310
+ });
1311
+ }); });
1312
+ });
1068
1313
  (0, bun_test_1.describe)("GET /consents/audit/:userId", function () {
1069
1314
  (0, bun_test_1.it)("returns audit history for a user when auditTrail is enabled", function () { return __awaiter(void 0, void 0, void 0, function () {
1070
1315
  var form, res;
@@ -341,16 +341,10 @@ function setupServer(options) {
341
341
  }
342
342
  // Convenience method to execute cronjobs with an always-running server.
343
343
  function cronjob(name, schedule, callback) {
344
- var _cronSchedule = schedule;
345
- if (schedule === "hourly") {
346
- _cronSchedule = "0 * * * *";
347
- }
348
- else if (schedule === "minutely") {
349
- _cronSchedule = "* * * * *";
350
- }
351
- logger_1.logger.info("Adding cronjob ".concat(name, ", running at: ").concat(schedule));
344
+ var cronSchedule = schedule === "hourly" ? "0 * * * *" : schedule === "minutely" ? "* * * * *" : schedule;
345
+ logger_1.logger.info("Adding cronjob ".concat(name, ", running at: ").concat(cronSchedule));
352
346
  try {
353
- new cron_1.default.CronJob(schedule, callback, null, true, "America/Chicago");
347
+ new cron_1.default.CronJob(cronSchedule, callback, null, true, "America/Chicago");
354
348
  }
355
349
  catch (error) {
356
350
  throw new Error("Failed to create cronjob: ".concat(error));
@@ -383,16 +383,13 @@ var tests_1 = require("./tests");
383
383
  var callback = function () { };
384
384
  (0, bun_test_1.expect)(function () { return (0, expressServer_1.cronjob)("test-invalid", "invalid-cron", callback); }).toThrow("Failed to create cronjob");
385
385
  });
386
- // Note: The "hourly" and "minutely" aliases have a bug - they convert the
387
- // schedule to a cron expression but then use the original schedule string.
388
- // This test documents that current (buggy) behavior.
389
- (0, bun_test_1.it)("hourly alias fails due to bug in implementation", function () {
386
+ (0, bun_test_1.it)("accepts hourly alias", function () {
390
387
  var callback = function () { };
391
- (0, bun_test_1.expect)(function () { return (0, expressServer_1.cronjob)("test-hourly-alias", "hourly", callback); }).toThrow("Failed to create cronjob");
388
+ (0, bun_test_1.expect)(function () { return (0, expressServer_1.cronjob)("test-hourly-alias", "hourly", callback); }).not.toThrow();
392
389
  });
393
- (0, bun_test_1.it)("minutely alias fails due to bug in implementation", function () {
390
+ (0, bun_test_1.it)("accepts minutely alias", function () {
394
391
  var callback = function () { };
395
- (0, bun_test_1.expect)(function () { return (0, expressServer_1.cronjob)("test-minutely-alias", "minutely", callback); }).toThrow("Failed to create cronjob");
392
+ (0, bun_test_1.expect)(function () { return (0, expressServer_1.cronjob)("test-minutely-alias", "minutely", callback); }).not.toThrow();
396
393
  });
397
394
  });
398
395
  (0, bun_test_1.describe)("createRouter routePathMiddleware", function () {