@tellescope/sdk 1.250.2 → 1.252.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 (136) hide show
  1. package/lib/cjs/sdk.d.ts +9 -0
  2. package/lib/cjs/sdk.d.ts.map +1 -1
  3. package/lib/cjs/sdk.js +3 -0
  4. package/lib/cjs/sdk.js.map +1 -1
  5. package/lib/cjs/tests/api_tests/account_switcher.test.d.ts.map +1 -1
  6. package/lib/cjs/tests/api_tests/account_switcher.test.js +1700 -306
  7. package/lib/cjs/tests/api_tests/account_switcher.test.js.map +1 -1
  8. package/lib/cjs/tests/api_tests/calendar_event_webhook_template.test.d.ts +6 -0
  9. package/lib/cjs/tests/api_tests/calendar_event_webhook_template.test.d.ts.map +1 -0
  10. package/lib/cjs/tests/api_tests/calendar_event_webhook_template.test.js +337 -0
  11. package/lib/cjs/tests/api_tests/calendar_event_webhook_template.test.js.map +1 -0
  12. package/lib/cjs/tests/api_tests/enduser_login.test.d.ts +6 -0
  13. package/lib/cjs/tests/api_tests/enduser_login.test.d.ts.map +1 -0
  14. package/lib/cjs/tests/api_tests/enduser_login.test.js +315 -0
  15. package/lib/cjs/tests/api_tests/enduser_login.test.js.map +1 -0
  16. package/lib/cjs/tests/api_tests/enduser_login_rate_limits.test.d.ts +6 -0
  17. package/lib/cjs/tests/api_tests/enduser_login_rate_limits.test.d.ts.map +1 -0
  18. package/lib/cjs/tests/api_tests/enduser_login_rate_limits.test.js +287 -0
  19. package/lib/cjs/tests/api_tests/enduser_login_rate_limits.test.js.map +1 -0
  20. package/lib/cjs/tests/api_tests/push_forms_to_portal_group_completion.test.d.ts +6 -0
  21. package/lib/cjs/tests/api_tests/push_forms_to_portal_group_completion.test.d.ts.map +1 -0
  22. package/lib/cjs/tests/api_tests/push_forms_to_portal_group_completion.test.js +406 -0
  23. package/lib/cjs/tests/api_tests/push_forms_to_portal_group_completion.test.js.map +1 -0
  24. package/lib/cjs/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.d.ts +28 -0
  25. package/lib/cjs/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.d.ts.map +1 -0
  26. package/lib/cjs/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.js +349 -0
  27. package/lib/cjs/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.js.map +1 -0
  28. package/lib/cjs/tests/api_tests/security/F-0005-ai-conversations-rbac.test.d.ts +28 -0
  29. package/lib/cjs/tests/api_tests/security/F-0005-ai-conversations-rbac.test.d.ts.map +1 -0
  30. package/lib/cjs/tests/api_tests/security/F-0005-ai-conversations-rbac.test.js +247 -0
  31. package/lib/cjs/tests/api_tests/security/F-0005-ai-conversations-rbac.test.js.map +1 -0
  32. package/lib/cjs/tests/api_tests/security/F-0007-invite-user-enumeration.test.d.ts +29 -0
  33. package/lib/cjs/tests/api_tests/security/F-0007-invite-user-enumeration.test.d.ts.map +1 -0
  34. package/lib/cjs/tests/api_tests/security/F-0007-invite-user-enumeration.test.js +278 -0
  35. package/lib/cjs/tests/api_tests/security/F-0007-invite-user-enumeration.test.js.map +1 -0
  36. package/lib/cjs/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.d.ts +24 -0
  37. package/lib/cjs/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.d.ts.map +1 -0
  38. package/lib/cjs/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.js +201 -0
  39. package/lib/cjs/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.js.map +1 -0
  40. package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html.test.d.ts +2 -0
  41. package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html.test.d.ts.map +1 -0
  42. package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html.test.js +148 -0
  43. package/lib/cjs/tests/api_tests/security/F-0013-sanitize-user-html.test.js.map +1 -0
  44. package/lib/cjs/tests/api_tests/security/F-0016-prototype-pollution.test.d.ts +2 -0
  45. package/lib/cjs/tests/api_tests/security/F-0016-prototype-pollution.test.d.ts.map +1 -0
  46. package/lib/cjs/tests/api_tests/security/F-0016-prototype-pollution.test.js +88 -0
  47. package/lib/cjs/tests/api_tests/security/F-0016-prototype-pollution.test.js.map +1 -0
  48. package/lib/cjs/tests/api_tests/set_fields_order_templates.test.d.ts +6 -0
  49. package/lib/cjs/tests/api_tests/set_fields_order_templates.test.d.ts.map +1 -0
  50. package/lib/cjs/tests/api_tests/set_fields_order_templates.test.js +373 -0
  51. package/lib/cjs/tests/api_tests/set_fields_order_templates.test.js.map +1 -0
  52. package/lib/cjs/tests/setup.d.ts.map +1 -1
  53. package/lib/cjs/tests/setup.js +47 -32
  54. package/lib/cjs/tests/setup.js.map +1 -1
  55. package/lib/cjs/tests/tests.d.ts.map +1 -1
  56. package/lib/cjs/tests/tests.js +215 -159
  57. package/lib/cjs/tests/tests.js.map +1 -1
  58. package/lib/esm/sdk.d.ts +9 -0
  59. package/lib/esm/sdk.d.ts.map +1 -1
  60. package/lib/esm/sdk.js +3 -0
  61. package/lib/esm/sdk.js.map +1 -1
  62. package/lib/esm/tests/api_tests/account_switcher.test.d.ts.map +1 -1
  63. package/lib/esm/tests/api_tests/account_switcher.test.js +1702 -305
  64. package/lib/esm/tests/api_tests/account_switcher.test.js.map +1 -1
  65. package/lib/esm/tests/api_tests/calendar_event_webhook_template.test.d.ts +6 -0
  66. package/lib/esm/tests/api_tests/calendar_event_webhook_template.test.d.ts.map +1 -0
  67. package/lib/esm/tests/api_tests/calendar_event_webhook_template.test.js +333 -0
  68. package/lib/esm/tests/api_tests/calendar_event_webhook_template.test.js.map +1 -0
  69. package/lib/esm/tests/api_tests/enduser_login.test.d.ts +6 -0
  70. package/lib/esm/tests/api_tests/enduser_login.test.d.ts.map +1 -0
  71. package/lib/esm/tests/api_tests/enduser_login.test.js +308 -0
  72. package/lib/esm/tests/api_tests/enduser_login.test.js.map +1 -0
  73. package/lib/esm/tests/api_tests/enduser_login_phi_disclosure.test.d.ts +6 -0
  74. package/lib/esm/tests/api_tests/enduser_login_phi_disclosure.test.d.ts.map +1 -0
  75. package/lib/esm/tests/api_tests/enduser_login_phi_disclosure.test.js +268 -0
  76. package/lib/esm/tests/api_tests/enduser_login_phi_disclosure.test.js.map +1 -0
  77. package/lib/esm/tests/api_tests/enduser_login_rate_limits.test.d.ts +6 -0
  78. package/lib/esm/tests/api_tests/enduser_login_rate_limits.test.d.ts.map +1 -0
  79. package/lib/esm/tests/api_tests/enduser_login_rate_limits.test.js +280 -0
  80. package/lib/esm/tests/api_tests/enduser_login_rate_limits.test.js.map +1 -0
  81. package/lib/esm/tests/api_tests/push_forms_to_portal_group_completion.test.d.ts +6 -0
  82. package/lib/esm/tests/api_tests/push_forms_to_portal_group_completion.test.d.ts.map +1 -0
  83. package/lib/esm/tests/api_tests/push_forms_to_portal_group_completion.test.js +402 -0
  84. package/lib/esm/tests/api_tests/push_forms_to_portal_group_completion.test.js.map +1 -0
  85. package/lib/esm/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.d.ts +28 -0
  86. package/lib/esm/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.d.ts.map +1 -0
  87. package/lib/esm/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.js +345 -0
  88. package/lib/esm/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.js.map +1 -0
  89. package/lib/esm/tests/api_tests/security/F-0005-ai-conversations-rbac.test.d.ts +28 -0
  90. package/lib/esm/tests/api_tests/security/F-0005-ai-conversations-rbac.test.d.ts.map +1 -0
  91. package/lib/esm/tests/api_tests/security/F-0005-ai-conversations-rbac.test.js +243 -0
  92. package/lib/esm/tests/api_tests/security/F-0005-ai-conversations-rbac.test.js.map +1 -0
  93. package/lib/esm/tests/api_tests/security/F-0007-invite-user-enumeration.test.d.ts +29 -0
  94. package/lib/esm/tests/api_tests/security/F-0007-invite-user-enumeration.test.d.ts.map +1 -0
  95. package/lib/esm/tests/api_tests/security/F-0007-invite-user-enumeration.test.js +271 -0
  96. package/lib/esm/tests/api_tests/security/F-0007-invite-user-enumeration.test.js.map +1 -0
  97. package/lib/esm/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.d.ts +24 -0
  98. package/lib/esm/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.d.ts.map +1 -0
  99. package/lib/esm/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.js +194 -0
  100. package/lib/esm/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.js.map +1 -0
  101. package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html.test.d.ts +2 -0
  102. package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html.test.d.ts.map +1 -0
  103. package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html.test.js +144 -0
  104. package/lib/esm/tests/api_tests/security/F-0013-sanitize-user-html.test.js.map +1 -0
  105. package/lib/esm/tests/api_tests/security/F-0016-prototype-pollution.test.d.ts +2 -0
  106. package/lib/esm/tests/api_tests/security/F-0016-prototype-pollution.test.d.ts.map +1 -0
  107. package/lib/esm/tests/api_tests/security/F-0016-prototype-pollution.test.js +84 -0
  108. package/lib/esm/tests/api_tests/security/F-0016-prototype-pollution.test.js.map +1 -0
  109. package/lib/esm/tests/api_tests/set_fields_order_templates.test.d.ts +6 -0
  110. package/lib/esm/tests/api_tests/set_fields_order_templates.test.d.ts.map +1 -0
  111. package/lib/esm/tests/api_tests/set_fields_order_templates.test.js +369 -0
  112. package/lib/esm/tests/api_tests/set_fields_order_templates.test.js.map +1 -0
  113. package/lib/esm/tests/setup.d.ts.map +1 -1
  114. package/lib/esm/tests/setup.js +47 -32
  115. package/lib/esm/tests/setup.js.map +1 -1
  116. package/lib/esm/tests/tests.d.ts.map +1 -1
  117. package/lib/esm/tests/tests.js +215 -159
  118. package/lib/esm/tests/tests.js.map +1 -1
  119. package/lib/tsconfig.tsbuildinfo +1 -1
  120. package/package.json +10 -10
  121. package/src/sdk.ts +12 -0
  122. package/src/tests/api_tests/account_switcher.test.ts +1283 -0
  123. package/src/tests/api_tests/calendar_event_webhook_template.test.ts +204 -0
  124. package/src/tests/api_tests/enduser_login.test.ts +215 -0
  125. package/src/tests/api_tests/enduser_login_rate_limits.test.ts +178 -0
  126. package/src/tests/api_tests/push_forms_to_portal_group_completion.test.ts +223 -0
  127. package/src/tests/api_tests/security/F-0001-data-sync-redaction-bypass.test.ts +236 -0
  128. package/src/tests/api_tests/security/F-0005-ai-conversations-rbac.test.ts +154 -0
  129. package/src/tests/api_tests/security/F-0007-invite-user-enumeration.test.ts +198 -0
  130. package/src/tests/api_tests/security/F-0008-handle-incoming-communication-cross-tenant.test.ts +130 -0
  131. package/src/tests/api_tests/security/F-0013-sanitize-user-html.test.ts +109 -0
  132. package/src/tests/api_tests/security/F-0016-prototype-pollution.test.ts +50 -0
  133. package/src/tests/api_tests/set_fields_order_templates.test.ts +258 -0
  134. package/src/tests/setup.ts +8 -1
  135. package/src/tests/tests.ts +35 -5
  136. package/test_generated.pdf +0 -0
@@ -0,0 +1,315 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ var __importDefault = (this && this.__importDefault) || function (mod) {
39
+ return (mod && mod.__esModule) ? mod : { "default": mod };
40
+ };
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.enduser_login_tests = void 0;
43
+ require('source-map-support').install();
44
+ var axios_1 = __importDefault(require("axios"));
45
+ var sdk_1 = require("../../sdk");
46
+ var testing_1 = require("@tellescope/testing");
47
+ var setup_1 = require("../setup");
48
+ var host = process.env.API_URL || 'http://localhost:8080';
49
+ // Coverage for the /v1/login-enduser response surface:
50
+ // - When an enduser has no password set, the error response must not include
51
+ // enduser fields (PHI, hashedPassword, etc.).
52
+ // - The "enduser not found" and "wrong password" cases must be indistinguishable
53
+ // (same status code, same message) to prevent account enumeration.
54
+ // - verify_otp invalid-code error must not include enduser fields.
55
+ var post_login = function (body) { return __awaiter(void 0, void 0, void 0, function () {
56
+ var res, err_1;
57
+ var _a, _b;
58
+ return __generator(this, function (_c) {
59
+ switch (_c.label) {
60
+ case 0:
61
+ _c.trys.push([0, 2, , 3]);
62
+ return [4 /*yield*/, axios_1.default.post("".concat(host, "/v1/login-enduser"), body, { validateStatus: function () { return true; } })];
63
+ case 1:
64
+ res = _c.sent();
65
+ return [2 /*return*/, { status: res.status, data: res.data }];
66
+ case 2:
67
+ err_1 = _c.sent();
68
+ return [2 /*return*/, { status: (_a = err_1 === null || err_1 === void 0 ? void 0 : err_1.response) === null || _a === void 0 ? void 0 : _a.status, data: (_b = err_1 === null || err_1 === void 0 ? void 0 : err_1.response) === null || _b === void 0 ? void 0 : _b.data }];
69
+ case 3: return [2 /*return*/];
70
+ }
71
+ });
72
+ }); };
73
+ var enduser_login_tests = function (_a) {
74
+ var sdk = _a.sdk, sdkNonAdmin = _a.sdkNonAdmin;
75
+ return __awaiter(void 0, void 0, void 0, function () {
76
+ var ts, MARKER_FNAME, MARKER_ADDRESS, MARKER_DOB, noPasswordEnduser, withPasswordEnduser, noPasswordResp_1, noPasswordBody_1, _loop_1, _i, _b, sensitiveKey, wrongPasswordResp_1, unknownEmailResp_1, verifyOtpInvalidResp, verifyOtpBody_1, duplicateError_1, err_2;
77
+ var _c, _d;
78
+ return __generator(this, function (_e) {
79
+ switch (_e.label) {
80
+ case 0:
81
+ (0, testing_1.log_header)("Enduser Login Tests");
82
+ ts = Date.now();
83
+ MARKER_FNAME = "LoginTestFname".concat(ts);
84
+ MARKER_ADDRESS = "".concat(ts, " Test Way");
85
+ MARKER_DOB = '01-01-1990';
86
+ return [4 /*yield*/, sdk.api.endusers.createOne({
87
+ fname: MARKER_FNAME,
88
+ lname: 'LoginTest',
89
+ email: "login-test-no-password-".concat(ts, "@tellescope.com"),
90
+ dateOfBirth: MARKER_DOB,
91
+ addressLineOne: MARKER_ADDRESS,
92
+ addressLineTwo: 'Apt 4B',
93
+ city: 'Springfield',
94
+ state: 'IL',
95
+ zipCode: '62701',
96
+ gender: 'Female',
97
+ assignedTo: [sdk.userInfo.id],
98
+ fields: { secretField: "should-not-leak-".concat(ts) },
99
+ tags: ['vip', 'sensitive'],
100
+ })];
101
+ case 1:
102
+ noPasswordEnduser = _e.sent();
103
+ return [4 /*yield*/, sdk.api.endusers.createOne({
104
+ fname: 'PasswordedEnduser',
105
+ lname: 'LoginTest',
106
+ email: "login-test-with-password-".concat(ts, "@tellescope.com"),
107
+ })];
108
+ case 2:
109
+ withPasswordEnduser = _e.sent();
110
+ return [4 /*yield*/, sdk.api.endusers.set_password({ id: withPasswordEnduser.id, password: 'CorrectPassword123!' })];
111
+ case 3:
112
+ _e.sent();
113
+ _e.label = 4;
114
+ case 4:
115
+ _e.trys.push([4, , 27, 29]);
116
+ return [4 /*yield*/, post_login({
117
+ email: noPasswordEnduser.email,
118
+ password: 'arbitrary-password',
119
+ businessId: sdk.userInfo.businessId,
120
+ })];
121
+ case 5:
122
+ noPasswordResp_1 = _e.sent();
123
+ noPasswordBody_1 = JSON.stringify((_c = noPasswordResp_1.data) !== null && _c !== void 0 ? _c : {});
124
+ return [4 /*yield*/, (0, testing_1.async_test)('No-password login response does not include fname marker', function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
125
+ return [2 /*return*/, noPasswordBody_1.includes(MARKER_FNAME) ? 'leaked' : 'safe'];
126
+ }); }); }, { expectedResult: 'safe' })];
127
+ case 6:
128
+ _e.sent();
129
+ return [4 /*yield*/, (0, testing_1.async_test)('No-password login response does not include dateOfBirth', function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
130
+ return [2 /*return*/, noPasswordBody_1.includes(MARKER_DOB) ? 'leaked' : 'safe'];
131
+ }); }); }, { expectedResult: 'safe' })];
132
+ case 7:
133
+ _e.sent();
134
+ return [4 /*yield*/, (0, testing_1.async_test)('No-password login response does not include addressLineOne marker', function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
135
+ return [2 /*return*/, noPasswordBody_1.includes(MARKER_ADDRESS) ? 'leaked' : 'safe'];
136
+ }); }); }, { expectedResult: 'safe' })];
137
+ case 8:
138
+ _e.sent();
139
+ _loop_1 = function (sensitiveKey) {
140
+ return __generator(this, function (_f) {
141
+ switch (_f.label) {
142
+ case 0: return [4 /*yield*/, (0, testing_1.async_test)("No-password login response does not include \"".concat(sensitiveKey, "\" key"), function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
143
+ return [2 /*return*/, noPasswordBody_1.includes("\"".concat(sensitiveKey, "\"")) ? 'leaked' : 'safe'];
144
+ }); }); }, { expectedResult: 'safe' })];
145
+ case 1:
146
+ _f.sent();
147
+ return [2 /*return*/];
148
+ }
149
+ });
150
+ };
151
+ _i = 0, _b = ['hashedPassword', 'assignedTo', 'fields', 'tags', 'insurance', 'customFields'];
152
+ _e.label = 9;
153
+ case 9:
154
+ if (!(_i < _b.length)) return [3 /*break*/, 12];
155
+ sensitiveKey = _b[_i];
156
+ return [5 /*yield**/, _loop_1(sensitiveKey)];
157
+ case 10:
158
+ _e.sent();
159
+ _e.label = 11;
160
+ case 11:
161
+ _i++;
162
+ return [3 /*break*/, 9];
163
+ case 12: return [4 /*yield*/, (0, testing_1.async_test)('No-password login response info field is absent or empty', function () { return __awaiter(void 0, void 0, void 0, function () {
164
+ var info;
165
+ var _a;
166
+ return __generator(this, function (_b) {
167
+ info = ((_a = noPasswordResp_1.data) !== null && _a !== void 0 ? _a : {}).info;
168
+ if (info === undefined)
169
+ return [2 /*return*/, 'safe'];
170
+ if (typeof info === 'object' && info !== null && Object.keys(info).length === 0)
171
+ return [2 /*return*/, 'safe'];
172
+ return [2 /*return*/, 'leaked'];
173
+ });
174
+ }); }, { expectedResult: 'safe' })
175
+ // All three failure modes (no-password-set, wrong-password, unknown-email)
176
+ // must return an identical 401 + identical message — no enumeration of
177
+ // which case actually applies.
178
+ ];
179
+ case 13:
180
+ _e.sent();
181
+ return [4 /*yield*/, post_login({
182
+ email: withPasswordEnduser.email,
183
+ password: 'WrongPassword!2025',
184
+ businessId: sdk.userInfo.businessId,
185
+ })];
186
+ case 14:
187
+ wrongPasswordResp_1 = _e.sent();
188
+ return [4 /*yield*/, post_login({
189
+ email: "does-not-exist-".concat(ts, "@tellescope.com"),
190
+ password: 'AnyPassword!2025',
191
+ businessId: sdk.userInfo.businessId,
192
+ })];
193
+ case 15:
194
+ unknownEmailResp_1 = _e.sent();
195
+ return [4 /*yield*/, (0, testing_1.async_test)('Login returns 401 for no-password account (indistinguishable from wrong password)', function () { return __awaiter(void 0, void 0, void 0, function () { var _a, _b; return __generator(this, function (_c) {
196
+ return [2 /*return*/, "".concat(noPasswordResp_1.status, ":").concat((_b = (_a = noPasswordResp_1.data) === null || _a === void 0 ? void 0 : _a.message) !== null && _b !== void 0 ? _b : '')];
197
+ }); }); }, { expectedResult: '401:Login details are invalid' })];
198
+ case 16:
199
+ _e.sent();
200
+ return [4 /*yield*/, (0, testing_1.async_test)('Login returns same status for no-password vs wrong-password vs unknown-email', function () { return __awaiter(void 0, void 0, void 0, function () {
201
+ var statuses, allSame;
202
+ return __generator(this, function (_a) {
203
+ statuses = [noPasswordResp_1.status, wrongPasswordResp_1.status, unknownEmailResp_1.status];
204
+ allSame = statuses.every(function (s) { return s === statuses[0]; });
205
+ return [2 /*return*/, allSame ? "same:".concat(statuses[0]) : "diff:".concat(statuses.join(','))];
206
+ });
207
+ }); }, { expectedResult: 'same:401' })];
208
+ case 17:
209
+ _e.sent();
210
+ return [4 /*yield*/, (0, testing_1.async_test)('Login returns same message for no-password vs wrong-password vs unknown-email', function () { return __awaiter(void 0, void 0, void 0, function () {
211
+ var messages, allSame;
212
+ var _a, _b, _c;
213
+ return __generator(this, function (_d) {
214
+ messages = [(_a = noPasswordResp_1.data) === null || _a === void 0 ? void 0 : _a.message, (_b = wrongPasswordResp_1.data) === null || _b === void 0 ? void 0 : _b.message, (_c = unknownEmailResp_1.data) === null || _c === void 0 ? void 0 : _c.message];
215
+ allSame = messages.every(function (m) { return m === messages[0]; });
216
+ return [2 /*return*/, allSame ? 'same' : "diff:".concat(JSON.stringify(messages))];
217
+ });
218
+ }); }, { expectedResult: 'same' })
219
+ // verify_otp invalid-code response must not include enduser fields.
220
+ ];
221
+ case 18:
222
+ _e.sent();
223
+ return [4 /*yield*/, axios_1.default.post("".concat(host, "/v1/verify-otp-code"), { token: 'not-a-real-token', code: '000000', businessId: sdk.userInfo.businessId }, { validateStatus: function () { return true; } })];
224
+ case 19:
225
+ verifyOtpInvalidResp = _e.sent();
226
+ verifyOtpBody_1 = JSON.stringify((_d = verifyOtpInvalidResp.data) !== null && _d !== void 0 ? _d : {});
227
+ return [4 /*yield*/, (0, testing_1.async_test)('verify_otp invalid-code response does not include any enduser fields', function () { return __awaiter(void 0, void 0, void 0, function () {
228
+ return __generator(this, function (_a) {
229
+ return [2 /*return*/, (verifyOtpBody_1.includes('"hashedPassword"')
230
+ || verifyOtpBody_1.includes('"assignedTo"')
231
+ || verifyOtpBody_1.includes(MARKER_FNAME)
232
+ ? 'leaked' : 'safe')];
233
+ });
234
+ }); }, { expectedResult: 'safe' })
235
+ // Regression: admin creating a duplicate enduser (same email) must still
236
+ // receive the existing record's id in the error info — this is an
237
+ // authenticated, intentional API-aid pattern via uniquenessError and must
238
+ // not be regressed by the public-endpoint sanitizer.
239
+ ];
240
+ case 20:
241
+ _e.sent();
242
+ duplicateError_1 = null;
243
+ _e.label = 21;
244
+ case 21:
245
+ _e.trys.push([21, 23, , 24]);
246
+ return [4 /*yield*/, sdk.api.endusers.createOne({ email: noPasswordEnduser.email })];
247
+ case 22:
248
+ _e.sent();
249
+ return [3 /*break*/, 24];
250
+ case 23:
251
+ err_2 = _e.sent();
252
+ duplicateError_1 = err_2;
253
+ return [3 /*break*/, 24];
254
+ case 24: return [4 /*yield*/, (0, testing_1.async_test)('Admin duplicate enduser create rejects with 409 Uniqueness Violation', function () { return __awaiter(void 0, void 0, void 0, function () { var _a; return __generator(this, function (_b) {
255
+ return [2 /*return*/, (_a = duplicateError_1 === null || duplicateError_1 === void 0 ? void 0 : duplicateError_1.message) !== null && _a !== void 0 ? _a : 'no-error'];
256
+ }); }); }, { expectedResult: 'Uniqueness Violation' })];
257
+ case 25:
258
+ _e.sent();
259
+ return [4 /*yield*/, (0, testing_1.async_test)('Admin duplicate enduser create returns the existing record id in info', function () { return __awaiter(void 0, void 0, void 0, function () {
260
+ var info, existing, existingId;
261
+ var _a, _b;
262
+ return __generator(this, function (_c) {
263
+ info = duplicateError_1 === null || duplicateError_1 === void 0 ? void 0 : duplicateError_1.info;
264
+ if (!Array.isArray(info) || info.length === 0)
265
+ return [2 /*return*/, "no-info:".concat(JSON.stringify(info))];
266
+ existing = (_a = info[0]) === null || _a === void 0 ? void 0 : _a.existingRecord;
267
+ existingId = (_b = existing === null || existing === void 0 ? void 0 : existing._id) !== null && _b !== void 0 ? _b : existing === null || existing === void 0 ? void 0 : existing.id;
268
+ return [2 /*return*/, existingId === noPasswordEnduser.id ? 'matched' : "mismatched:".concat(existingId)];
269
+ });
270
+ }); }, { expectedResult: 'matched' })];
271
+ case 26:
272
+ _e.sent();
273
+ return [3 /*break*/, 29];
274
+ case 27: return [4 /*yield*/, Promise.all([
275
+ sdk.api.endusers.deleteOne(noPasswordEnduser.id).catch(function () { return null; }),
276
+ sdk.api.endusers.deleteOne(withPasswordEnduser.id).catch(function () { return null; }),
277
+ ])];
278
+ case 28:
279
+ _e.sent();
280
+ return [7 /*endfinally*/];
281
+ case 29: return [2 /*return*/];
282
+ }
283
+ });
284
+ });
285
+ };
286
+ exports.enduser_login_tests = enduser_login_tests;
287
+ // Allow running this test file independently
288
+ if (require.main === module) {
289
+ console.log("\uD83C\uDF10 Using API URL: ".concat(host));
290
+ var sdk_2 = new sdk_1.Session({ host: host });
291
+ var sdkNonAdmin_1 = new sdk_1.Session({ host: host });
292
+ var runTests = function () { return __awaiter(void 0, void 0, void 0, function () {
293
+ return __generator(this, function (_a) {
294
+ switch (_a.label) {
295
+ case 0: return [4 /*yield*/, (0, setup_1.setup_tests)(sdk_2, sdkNonAdmin_1)];
296
+ case 1:
297
+ _a.sent();
298
+ return [4 /*yield*/, (0, exports.enduser_login_tests)({ sdk: sdk_2, sdkNonAdmin: sdkNonAdmin_1 })];
299
+ case 2:
300
+ _a.sent();
301
+ return [2 /*return*/];
302
+ }
303
+ });
304
+ }); };
305
+ runTests()
306
+ .then(function () {
307
+ console.log("✅ Enduser login test suite completed successfully");
308
+ process.exit(0);
309
+ })
310
+ .catch(function (error) {
311
+ console.error("❌ Enduser login test suite failed:", error);
312
+ process.exit(1);
313
+ });
314
+ }
315
+ //# sourceMappingURL=enduser_login.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enduser_login.test.js","sourceRoot":"","sources":["../../../../src/tests/api_tests/enduser_login.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import { Session } from "../../sdk";
2
+ export declare const enduser_login_rate_limits_tests: ({ sdk, sdkNonAdmin }: {
3
+ sdk: Session;
4
+ sdkNonAdmin: Session;
5
+ }) => Promise<void>;
6
+ //# sourceMappingURL=enduser_login_rate_limits.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enduser_login_rate_limits.test.d.ts","sourceRoot":"","sources":["../../../../src/tests/api_tests/enduser_login_rate_limits.test.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAoCnC,eAAO,MAAM,+BAA+B;SAAwC,OAAO;iBAAe,OAAO;mBAoHhH,CAAA"}
@@ -0,0 +1,287 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ var __importDefault = (this && this.__importDefault) || function (mod) {
39
+ return (mod && mod.__esModule) ? mod : { "default": mod };
40
+ };
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.enduser_login_rate_limits_tests = void 0;
43
+ require('source-map-support').install();
44
+ var axios_1 = __importDefault(require("axios"));
45
+ var sdk_1 = require("../../sdk");
46
+ var testing_1 = require("@tellescope/testing");
47
+ var setup_1 = require("../setup");
48
+ var host = process.env.API_URL || 'http://localhost:8080';
49
+ // Per-IP rate limits applied to enduser public endpoints:
50
+ // POST /v1/login-enduser 20 / min, 100 / hour
51
+ // POST /v1/begin-enduser-login-flow 10 / min, 50 / hour
52
+ // POST /v1/endusers/send-otp-code 5 / min, 30 / hour
53
+ // Plus a per-identifier limit on begin_login_flow: 5 / 10 min per email|phone.
54
+ var post = function (path, body) { return __awaiter(void 0, void 0, void 0, function () {
55
+ var res, err_1;
56
+ var _a, _b;
57
+ return __generator(this, function (_c) {
58
+ switch (_c.label) {
59
+ case 0:
60
+ _c.trys.push([0, 2, , 3]);
61
+ return [4 /*yield*/, axios_1.default.post("".concat(host).concat(path), body, { validateStatus: function () { return true; } })];
62
+ case 1:
63
+ res = _c.sent();
64
+ return [2 /*return*/, { status: res.status, data: res.data }];
65
+ case 2:
66
+ err_1 = _c.sent();
67
+ return [2 /*return*/, { status: (_a = err_1 === null || err_1 === void 0 ? void 0 : err_1.response) === null || _a === void 0 ? void 0 : _a.status, data: (_b = err_1 === null || err_1 === void 0 ? void 0 : err_1.response) === null || _b === void 0 ? void 0 : _b.data }];
68
+ case 3: return [2 /*return*/];
69
+ }
70
+ });
71
+ }); };
72
+ var fire_until_429 = function (cap, send) { return __awaiter(void 0, void 0, void 0, function () {
73
+ var triggeredAt, i, status_1;
74
+ return __generator(this, function (_a) {
75
+ switch (_a.label) {
76
+ case 0:
77
+ triggeredAt = -1;
78
+ i = 0;
79
+ _a.label = 1;
80
+ case 1:
81
+ if (!(i < cap + 5)) return [3 /*break*/, 4];
82
+ return [4 /*yield*/, send(i)];
83
+ case 2:
84
+ status_1 = (_a.sent()).status;
85
+ if (status_1 === 429) {
86
+ triggeredAt = i;
87
+ return [3 /*break*/, 4];
88
+ }
89
+ _a.label = 3;
90
+ case 3:
91
+ i++;
92
+ return [3 /*break*/, 1];
93
+ case 4: return [2 /*return*/, triggeredAt];
94
+ }
95
+ });
96
+ }); };
97
+ var enduser_login_rate_limits_tests = function (_a) {
98
+ var sdk = _a.sdk, sdkNonAdmin = _a.sdkNonAdmin;
99
+ return __awaiter(void 0, void 0, void 0, function () {
100
+ var businessId, loginTrip, beginIpTrip, fixedEmail, beginIdTrip, fakeToken, sendOtpTrip, tripped, ts, enduser, goodLogin_1, goodLoginRetry_1;
101
+ return __generator(this, function (_b) {
102
+ switch (_b.label) {
103
+ case 0:
104
+ (0, testing_1.log_header)("Enduser Login Rate Limit Tests");
105
+ businessId = sdk.userInfo.businessId;
106
+ // Ensure throttled_events from prior tests don't bleed in.
107
+ return [4 /*yield*/, sdk.reset_db()
108
+ // ---- /v1/login-enduser per-IP cap (20/min) ----
109
+ // Bogus emails ensure we never reach the actual DB lookup / login work.
110
+ ];
111
+ case 1:
112
+ // Ensure throttled_events from prior tests don't bleed in.
113
+ _b.sent();
114
+ return [4 /*yield*/, fire_until_429(20, function (i) { return post('/v1/login-enduser', {
115
+ email: "rl-login-".concat(Date.now(), "-").concat(i, "@tellescope.com"),
116
+ password: 'NotARealPassword!2025',
117
+ businessId: businessId,
118
+ }); })];
119
+ case 2:
120
+ loginTrip = _b.sent();
121
+ return [4 /*yield*/, (0, testing_1.async_test)('login per-IP throttle trips within first 21 requests', function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
122
+ return [2 /*return*/, (loginTrip >= 0 && loginTrip <= 20) ? 'tripped' : "not-tripped:".concat(loginTrip)];
123
+ }); }); }, { expectedResult: 'tripped' })];
124
+ case 3:
125
+ _b.sent();
126
+ return [4 /*yield*/, sdk.reset_db()
127
+ // ---- /v1/begin-enduser-login-flow per-IP cap (10/min) ----
128
+ // Use distinct emails so the per-identifier limit (5/10min) does NOT trip first;
129
+ // we want the IP cap to be the first thing to fire.
130
+ ];
131
+ case 4:
132
+ _b.sent();
133
+ return [4 /*yield*/, fire_until_429(10, function (i) { return post('/v1/begin-enduser-login-flow', {
134
+ email: "rl-begin-ip-".concat(Date.now(), "-").concat(i, "@tellescope.com"),
135
+ businessId: businessId,
136
+ }); })];
137
+ case 5:
138
+ beginIpTrip = _b.sent();
139
+ return [4 /*yield*/, (0, testing_1.async_test)('begin_login_flow per-IP throttle trips within first 11 requests', function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
140
+ return [2 /*return*/, (beginIpTrip >= 0 && beginIpTrip <= 10) ? 'tripped' : "not-tripped:".concat(beginIpTrip)];
141
+ }); }); }, { expectedResult: 'tripped' })];
142
+ case 6:
143
+ _b.sent();
144
+ return [4 /*yield*/, sdk.reset_db()
145
+ // ---- /v1/begin-enduser-login-flow per-identifier cap (5 / 10 min per email) ----
146
+ // Hit a single email below the per-IP cap.
147
+ ];
148
+ case 7:
149
+ _b.sent();
150
+ fixedEmail = "rl-begin-id-".concat(Date.now(), "@tellescope.com");
151
+ return [4 /*yield*/, fire_until_429(5, function () { return post('/v1/begin-enduser-login-flow', {
152
+ email: fixedEmail,
153
+ businessId: businessId,
154
+ }); })];
155
+ case 8:
156
+ beginIdTrip = _b.sent();
157
+ return [4 /*yield*/, (0, testing_1.async_test)('begin_login_flow per-identifier throttle trips within first 6 requests', function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
158
+ return [2 /*return*/, (beginIdTrip >= 0 && beginIdTrip <= 5) ? 'tripped' : "not-tripped:".concat(beginIdTrip)];
159
+ }); }); }, { expectedResult: 'tripped' })];
160
+ case 9:
161
+ _b.sent();
162
+ return [4 /*yield*/, sdk.reset_db()
163
+ // ---- /v1/endusers/send-otp-code per-IP cap (5/min) ----
164
+ // Use a bogus JWT-shaped token so we trip the IP guard first (it runs before any DB work).
165
+ ];
166
+ case 10:
167
+ _b.sent();
168
+ fakeToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCJ9.sig';
169
+ return [4 /*yield*/, fire_until_429(5, function () { return post('/v1/endusers/send-otp-code', {
170
+ token: fakeToken,
171
+ method: 'email',
172
+ }); })];
173
+ case 11:
174
+ sendOtpTrip = _b.sent();
175
+ return [4 /*yield*/, (0, testing_1.async_test)('send_otp per-IP throttle trips within first 6 requests', function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
176
+ return [2 /*return*/, (sendOtpTrip >= 0 && sendOtpTrip <= 5) ? 'tripped' : "not-tripped:".concat(sendOtpTrip)];
177
+ }); }); }, { expectedResult: 'tripped' })
178
+ // Confirm 429 response does not leak the keying strategy (no mention of "ip" or "address").
179
+ ];
180
+ case 12:
181
+ _b.sent();
182
+ return [4 /*yield*/, post('/v1/endusers/send-otp-code', { token: fakeToken, method: 'email' })];
183
+ case 13:
184
+ tripped = _b.sent();
185
+ return [4 /*yield*/, (0, testing_1.async_test)('429 response does not mention "ip" or "address"', function () { return __awaiter(void 0, void 0, void 0, function () {
186
+ var msg;
187
+ var _a, _b;
188
+ return __generator(this, function (_c) {
189
+ msg = ((_b = (_a = tripped.data) === null || _a === void 0 ? void 0 : _a.message) !== null && _b !== void 0 ? _b : '').toLowerCase();
190
+ return [2 /*return*/, (msg.includes('ip') || msg.includes('address')) ? 'leaked' : 'safe'];
191
+ });
192
+ }); }, { expectedResult: 'safe' })
193
+ // ---- Legitimate-login regression: a single successful login should still go through ----
194
+ // Reset state, then create a real enduser with a password and confirm one login succeeds.
195
+ ];
196
+ case 14:
197
+ _b.sent();
198
+ // ---- Legitimate-login regression: a single successful login should still go through ----
199
+ // Reset state, then create a real enduser with a password and confirm one login succeeds.
200
+ return [4 /*yield*/, sdk.reset_db()];
201
+ case 15:
202
+ // ---- Legitimate-login regression: a single successful login should still go through ----
203
+ // Reset state, then create a real enduser with a password and confirm one login succeeds.
204
+ _b.sent();
205
+ ts = Date.now();
206
+ return [4 /*yield*/, sdk.api.endusers.createOne({
207
+ fname: 'RateLimitOk', lname: 'Enduser',
208
+ email: "rl-legit-".concat(ts, "@tellescope.com"),
209
+ })];
210
+ case 16:
211
+ enduser = _b.sent();
212
+ _b.label = 17;
213
+ case 17:
214
+ _b.trys.push([17, , 23, 26]);
215
+ return [4 /*yield*/, sdk.api.endusers.set_password({ id: enduser.id, password: 'CorrectPassword123!' })];
216
+ case 18:
217
+ _b.sent();
218
+ return [4 /*yield*/, post('/v1/login-enduser', {
219
+ email: enduser.email,
220
+ password: 'CorrectPassword123!',
221
+ businessId: businessId,
222
+ })];
223
+ case 19:
224
+ goodLogin_1 = _b.sent();
225
+ return [4 /*yield*/, (0, testing_1.async_test)('legitimate login still succeeds (returns authToken, not 429)', function () { return __awaiter(void 0, void 0, void 0, function () { var _a; return __generator(this, function (_b) {
226
+ return [2 /*return*/, goodLogin_1.status === 200 && !!((_a = goodLogin_1.data) === null || _a === void 0 ? void 0 : _a.authToken) ? 'ok' : "failed:".concat(goodLogin_1.status)];
227
+ }); }); }, { expectedResult: 'ok' })
228
+ // A subsequent successful login by the same user/IP should also succeed —
229
+ // a single legitimate user retrying must not trip the per-IP cap.
230
+ ];
231
+ case 20:
232
+ _b.sent();
233
+ return [4 /*yield*/, post('/v1/login-enduser', {
234
+ email: enduser.email,
235
+ password: 'CorrectPassword123!',
236
+ businessId: businessId,
237
+ })];
238
+ case 21:
239
+ goodLoginRetry_1 = _b.sent();
240
+ return [4 /*yield*/, (0, testing_1.async_test)('legitimate login retry still succeeds', function () { return __awaiter(void 0, void 0, void 0, function () { var _a; return __generator(this, function (_b) {
241
+ return [2 /*return*/, goodLoginRetry_1.status === 200 && !!((_a = goodLoginRetry_1.data) === null || _a === void 0 ? void 0 : _a.authToken) ? 'ok' : "failed:".concat(goodLoginRetry_1.status)];
242
+ }); }); }, { expectedResult: 'ok' })];
243
+ case 22:
244
+ _b.sent();
245
+ return [3 /*break*/, 26];
246
+ case 23: return [4 /*yield*/, sdk.api.endusers.deleteOne(enduser.id).catch(function () { return null; })];
247
+ case 24:
248
+ _b.sent();
249
+ return [4 /*yield*/, sdk.reset_db().catch(function () { return null; })];
250
+ case 25:
251
+ _b.sent();
252
+ return [7 /*endfinally*/];
253
+ case 26: return [2 /*return*/];
254
+ }
255
+ });
256
+ });
257
+ };
258
+ exports.enduser_login_rate_limits_tests = enduser_login_rate_limits_tests;
259
+ // Allow running this test file independently
260
+ if (require.main === module) {
261
+ console.log("\uD83C\uDF10 Using API URL: ".concat(host));
262
+ var sdk_2 = new sdk_1.Session({ host: host });
263
+ var sdkNonAdmin_1 = new sdk_1.Session({ host: host });
264
+ var runTests = function () { return __awaiter(void 0, void 0, void 0, function () {
265
+ return __generator(this, function (_a) {
266
+ switch (_a.label) {
267
+ case 0: return [4 /*yield*/, (0, setup_1.setup_tests)(sdk_2, sdkNonAdmin_1)];
268
+ case 1:
269
+ _a.sent();
270
+ return [4 /*yield*/, (0, exports.enduser_login_rate_limits_tests)({ sdk: sdk_2, sdkNonAdmin: sdkNonAdmin_1 })];
271
+ case 2:
272
+ _a.sent();
273
+ return [2 /*return*/];
274
+ }
275
+ });
276
+ }); };
277
+ runTests()
278
+ .then(function () {
279
+ console.log("✅ Enduser login rate limit test suite completed successfully");
280
+ process.exit(0);
281
+ })
282
+ .catch(function (error) {
283
+ console.error("❌ Enduser login rate limit test suite failed:", error);
284
+ process.exit(1);
285
+ });
286
+ }
287
+ //# sourceMappingURL=enduser_login_rate_limits.test.js.map