@tellescope/sdk 1.256.13 → 1.256.14

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 (106) hide show
  1. package/lib/cjs/tests/api_tests/organization_signup.test.d.ts +20 -0
  2. package/lib/cjs/tests/api_tests/organization_signup.test.d.ts.map +1 -0
  3. package/lib/cjs/tests/api_tests/organization_signup.test.js +272 -0
  4. package/lib/cjs/tests/api_tests/organization_signup.test.js.map +1 -0
  5. package/lib/cjs/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.d.ts +30 -0
  6. package/lib/cjs/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.d.ts.map +1 -0
  7. package/lib/cjs/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.js +336 -0
  8. package/lib/cjs/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.js.map +1 -0
  9. package/lib/cjs/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.d.ts +6 -0
  10. package/lib/cjs/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.d.ts.map +1 -0
  11. package/lib/cjs/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.js +354 -0
  12. package/lib/cjs/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.js.map +1 -0
  13. package/lib/cjs/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.d.ts +46 -0
  14. package/lib/cjs/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.d.ts.map +1 -0
  15. package/lib/cjs/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.js +389 -0
  16. package/lib/cjs/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.js.map +1 -0
  17. package/lib/cjs/tests/api_tests/security/F-0192-automated-actions-process-gate.test.d.ts +43 -0
  18. package/lib/cjs/tests/api_tests/security/F-0192-automated-actions-process-gate.test.d.ts.map +1 -0
  19. package/lib/cjs/tests/api_tests/security/F-0192-automated-actions-process-gate.test.js +324 -0
  20. package/lib/cjs/tests/api_tests/security/F-0192-automated-actions-process-gate.test.js.map +1 -0
  21. package/lib/cjs/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.d.ts +43 -0
  22. package/lib/cjs/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.d.ts.map +1 -0
  23. package/lib/cjs/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.js +282 -0
  24. package/lib/cjs/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.js.map +1 -0
  25. package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts +5 -0
  26. package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts.map +1 -0
  27. package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.js +329 -0
  28. package/lib/cjs/tests/api_tests/tickets_bulk_assign_from_queue.test.js.map +1 -0
  29. package/lib/cjs/tests/setup_isolated.d.ts +68 -0
  30. package/lib/cjs/tests/setup_isolated.d.ts.map +1 -0
  31. package/lib/cjs/tests/setup_isolated.js +232 -0
  32. package/lib/cjs/tests/setup_isolated.js.map +1 -0
  33. package/lib/cjs/tests/tests.d.ts.map +1 -1
  34. package/lib/cjs/tests/tests.js +257 -209
  35. package/lib/cjs/tests/tests.js.map +1 -1
  36. package/lib/cjs/tests/unit_tests/do_not_send_address.test.d.ts +2 -0
  37. package/lib/cjs/tests/unit_tests/do_not_send_address.test.d.ts.map +1 -0
  38. package/lib/cjs/tests/unit_tests/do_not_send_address.test.js +89 -0
  39. package/lib/cjs/tests/unit_tests/do_not_send_address.test.js.map +1 -0
  40. package/lib/cjs/tests/unit_tests/openapi_spec_freshness.test.d.ts +17 -0
  41. package/lib/cjs/tests/unit_tests/openapi_spec_freshness.test.d.ts.map +1 -0
  42. package/lib/cjs/tests/unit_tests/openapi_spec_freshness.test.js +140 -0
  43. package/lib/cjs/tests/unit_tests/openapi_spec_freshness.test.js.map +1 -0
  44. package/lib/esm/enduser.d.ts +1 -0
  45. package/lib/esm/enduser.d.ts.map +1 -1
  46. package/lib/esm/sdk.d.ts +3 -2
  47. package/lib/esm/sdk.d.ts.map +1 -1
  48. package/lib/esm/session.d.ts +1 -0
  49. package/lib/esm/session.d.ts.map +1 -1
  50. package/lib/esm/tests/api_tests/organization_signup.test.d.ts +20 -0
  51. package/lib/esm/tests/api_tests/organization_signup.test.d.ts.map +1 -0
  52. package/lib/esm/tests/api_tests/organization_signup.test.js +264 -0
  53. package/lib/esm/tests/api_tests/organization_signup.test.js.map +1 -0
  54. package/lib/esm/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.d.ts +30 -0
  55. package/lib/esm/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.d.ts.map +1 -0
  56. package/lib/esm/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.js +332 -0
  57. package/lib/esm/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.js.map +1 -0
  58. package/lib/esm/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.d.ts +6 -0
  59. package/lib/esm/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.d.ts.map +1 -0
  60. package/lib/esm/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.js +350 -0
  61. package/lib/esm/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.js.map +1 -0
  62. package/lib/esm/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.d.ts +46 -0
  63. package/lib/esm/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.d.ts.map +1 -0
  64. package/lib/esm/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.js +382 -0
  65. package/lib/esm/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.js.map +1 -0
  66. package/lib/esm/tests/api_tests/security/F-0192-automated-actions-process-gate.test.d.ts +43 -0
  67. package/lib/esm/tests/api_tests/security/F-0192-automated-actions-process-gate.test.d.ts.map +1 -0
  68. package/lib/esm/tests/api_tests/security/F-0192-automated-actions-process-gate.test.js +317 -0
  69. package/lib/esm/tests/api_tests/security/F-0192-automated-actions-process-gate.test.js.map +1 -0
  70. package/lib/esm/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.d.ts +43 -0
  71. package/lib/esm/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.d.ts.map +1 -0
  72. package/lib/esm/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.js +277 -0
  73. package/lib/esm/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.js.map +1 -0
  74. package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts +5 -0
  75. package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.d.ts.map +1 -0
  76. package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.js +324 -0
  77. package/lib/esm/tests/api_tests/tickets_bulk_assign_from_queue.test.js.map +1 -0
  78. package/lib/esm/tests/setup_isolated.d.ts +68 -0
  79. package/lib/esm/tests/setup_isolated.d.ts.map +1 -0
  80. package/lib/esm/tests/setup_isolated.js +221 -0
  81. package/lib/esm/tests/setup_isolated.js.map +1 -0
  82. package/lib/esm/tests/tests.d.ts.map +1 -1
  83. package/lib/esm/tests/tests.js +257 -209
  84. package/lib/esm/tests/tests.js.map +1 -1
  85. package/lib/esm/tests/unit_tests/do_not_send_address.test.d.ts +2 -0
  86. package/lib/esm/tests/unit_tests/do_not_send_address.test.d.ts.map +1 -0
  87. package/lib/esm/tests/unit_tests/do_not_send_address.test.js +85 -0
  88. package/lib/esm/tests/unit_tests/do_not_send_address.test.js.map +1 -0
  89. package/lib/esm/tests/unit_tests/openapi_spec_freshness.test.d.ts +17 -0
  90. package/lib/esm/tests/unit_tests/openapi_spec_freshness.test.d.ts.map +1 -0
  91. package/lib/esm/tests/unit_tests/openapi_spec_freshness.test.js +113 -0
  92. package/lib/esm/tests/unit_tests/openapi_spec_freshness.test.js.map +1 -0
  93. package/lib/tsconfig.tsbuildinfo +1 -1
  94. package/package.json +10 -10
  95. package/src/tests/api_tests/organization_signup.test.ts +240 -0
  96. package/src/tests/api_tests/security/F-0179-booking-info-idor-token-mint.test.ts +225 -0
  97. package/src/tests/api_tests/security/F-0185-startlinktoken-enduser-staff-escalation.test.ts +162 -0
  98. package/src/tests/api_tests/security/F-0191-schema-adminonly-enforcement.test.ts +288 -0
  99. package/src/tests/api_tests/security/F-0192-automated-actions-process-gate.test.ts +238 -0
  100. package/src/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.ts +208 -0
  101. package/src/tests/api_tests/tickets_bulk_assign_from_queue.test.ts +279 -0
  102. package/src/tests/setup_isolated.ts +170 -0
  103. package/src/tests/tests.ts +25 -0
  104. package/src/tests/unit_tests/do_not_send_address.test.ts +60 -0
  105. package/src/tests/unit_tests/openapi_spec_freshness.test.ts +88 -0
  106. package/test_generated.pdf +0 -0
@@ -0,0 +1,277 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var __generator = (this && this.__generator) || function (thisArg, body) {
11
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
12
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
13
+ function verb(n) { return function (v) { return step([n, v]); }; }
14
+ function step(op) {
15
+ if (f) throw new TypeError("Generator is already executing.");
16
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
17
+ 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;
18
+ if (y = 0, t) op = [op[0] & 2, t.value];
19
+ switch (op[0]) {
20
+ case 0: case 1: t = op; break;
21
+ case 4: _.label++; return { value: op[1], done: false };
22
+ case 5: _.label++; y = op[1]; op = [0]; continue;
23
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
24
+ default:
25
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
26
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
27
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
28
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
29
+ if (t[2]) _.ops.pop();
30
+ _.trys.pop(); continue;
31
+ }
32
+ op = body.call(thisArg, _);
33
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
34
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
35
+ }
36
+ };
37
+ require('source-map-support').install();
38
+ import { assert_checks, setup_isolated_tenant, } from "../../setup_isolated";
39
+ var host = process.env.API_URL || 'http://localhost:8080';
40
+ var RAND = function () { return Math.random().toString(36).slice(2, 10); };
41
+ // far enough out that a locally running worker never claims the rows we inspect
42
+ var FUTURE_START = function () { return new Date(Date.now() + 60 * 60 * 1000); };
43
+ /**
44
+ * Regression test for F-0196
45
+ * (security-audit/findings/F-0196-add-to-journey-plants-foreign-enduserid.md).
46
+ *
47
+ * `endusers.add_to_journey` inserted an `automated_actions` row for EACH caller-supplied `enduserId`
48
+ * with only mongoId-shape validation and no tenant-membership check. The preceding
49
+ * `DB.endusers.updateMany` is scoped but its result was discarded, and
50
+ * `DB.automated_actions.insertMany` is a direct write that bypasses the `ensureValidDependentKeys`
51
+ * foreign-key check the generic CRUD route gets. The resulting row is stamped
52
+ * `businessId: <caller's tenant>` while carrying `enduserId: <a victim in another tenant>`.
53
+ *
54
+ * The worker then dereferences that enduser with the raw driver and no `businessId`
55
+ * (worker/modules/automations.ts, the F-0074/F-0111/F-0159 family), so the victim's record loads
56
+ * under the attacker's automation: `pushFormsToPortal` copies the victim's PHI into the attacker's
57
+ * `form_responses` (readable by their staff) and `iterableCustomEvent` writes an attacker-authored
58
+ * note into the victim's chart. F-0196 is the reachability that made those three exploitable.
59
+ *
60
+ * There are TWO independent plant sites and a fix to only one is a half fix:
61
+ * - inline branch, taken when `automationStepId` is supplied (api.ts add_to_journey handler)
62
+ * - module branch, taken when it is not: `handle_automation_event`'s
63
+ * `validEnduserIds = conditionsQuery ? <scoped find> : event.enduserIds` passes caller ids
64
+ * straight through for a step with NO conditions.
65
+ * Both are covered below (A1/A2).
66
+ *
67
+ * Design notes:
68
+ * - Two provisioned tenants, so the victim is a real foreign enduser and we can also assert their
69
+ * record was not mutated. Both must come from provisioning: `add_to_journey` is
70
+ * `requiresSubscription: true`, and a `create_and_join` org is born with an expired subscription.
71
+ * - The steps use `setEnduserStatus` — no vendor, no Bedrock — and `startAt` is an hour out so a
72
+ * running worker cannot process the rows before we read them.
73
+ * - `throttle` is deliberately omitted: it is the only way add_to_journey's per-pair rate limit can
74
+ * throw.
75
+ * - Everything is observable through `automated_actions` reads, so the worker is never needed.
76
+ *
77
+ * RED (pre-fix): A1, A2 and B2 fail — the victim's id appears in the attacker tenant's
78
+ * automated_actions. GREEN (post-fix): the foreign id is silently dropped and the caller's own
79
+ * endusers still enroll normally.
80
+ */
81
+ export var add_to_journey_foreign_enduserid_checks = function () { return __awaiter(void 0, void 0, void 0, function () {
82
+ var checks, record, attacker, victimTenant, victim, own, makeJourney, inline, modulePath, mixed, rowsFor_1, enduserIdsFor, inlineResult, inlineIds, moduleResult, moduleIds, victimAfter, victimJourneys, inlineOwn, moduleOwn, mixedResult, mixedIds, _i, _a, t, e_1;
83
+ var _b;
84
+ var _c;
85
+ return __generator(this, function (_d) {
86
+ switch (_d.label) {
87
+ case 0:
88
+ checks = [];
89
+ record = function (label, pass, detail) { return checks.push({ label: label, pass: pass, detail: detail }); };
90
+ _d.label = 1;
91
+ case 1:
92
+ _d.trys.push([1, , 19, 26]);
93
+ /* ================= fixtures: two isolated tenants ================= */
94
+ ;
95
+ return [4 /*yield*/, Promise.all([
96
+ setup_isolated_tenant({ name: "F0196 attacker ".concat(RAND()), users: [{ isAdmin: true }], apiKeys: 0 }),
97
+ setup_isolated_tenant({ name: "F0196 victim ".concat(RAND()), users: [{ isAdmin: true }], apiKeys: 0 }),
98
+ ])];
99
+ case 2:
100
+ _b = _d.sent(), attacker = _b[0], victimTenant = _b[1];
101
+ return [4 /*yield*/, victimTenant.sdk.api.endusers.createOne({ fname: 'F0196', lname: 'Victim' })];
102
+ case 3:
103
+ victim = _d.sent();
104
+ return [4 /*yield*/, attacker.sdk.api.endusers.createOne({ fname: 'F0196', lname: 'OwnPatient' })];
105
+ case 4:
106
+ own = _d.sent();
107
+ record('setup: two separate tenants, victim belongs to the other one', attacker.businessId !== victimTenant.businessId && !!victim.id && !!own.id, "attacker=".concat(attacker.businessId, " victim tenant=").concat(victimTenant.businessId, " victimEnduser=").concat(victim.id));
108
+ makeJourney = function (title) { return __awaiter(void 0, void 0, void 0, function () {
109
+ var journey, step;
110
+ return __generator(this, function (_a) {
111
+ switch (_a.label) {
112
+ case 0: return [4 /*yield*/, attacker.sdk.api.journeys.createOne({ title: "".concat(title, " ").concat(RAND()) })];
113
+ case 1:
114
+ journey = _a.sent();
115
+ return [4 /*yield*/, attacker.sdk.api.automation_steps.createOne({
116
+ journeyId: journey.id,
117
+ // benign and worker-local: no vendor, no Bedrock. NO `conditions`, which is precisely the
118
+ // vulnerable branch in handle_automation_event.
119
+ action: { type: 'setEnduserStatus', info: { status: 'F0196' } },
120
+ events: [{ type: 'onJourneyStart', info: {} }],
121
+ })];
122
+ case 2:
123
+ step = _a.sent();
124
+ return [2 /*return*/, { journey: journey, step: step }];
125
+ }
126
+ });
127
+ }); };
128
+ return [4 /*yield*/, makeJourney('F0196 inline')];
129
+ case 5:
130
+ inline = _d.sent();
131
+ return [4 /*yield*/, makeJourney('F0196 module')];
132
+ case 6:
133
+ modulePath = _d.sent();
134
+ return [4 /*yield*/, makeJourney('F0196 mixed')
135
+ /** every automated_actions row the attacker tenant can see for a journey */
136
+ ];
137
+ case 7:
138
+ mixed = _d.sent();
139
+ rowsFor_1 = function (journeyId) { return __awaiter(void 0, void 0, void 0, function () {
140
+ return __generator(this, function (_a) {
141
+ switch (_a.label) {
142
+ case 0: return [4 /*yield*/, attacker.sdk.api.automated_actions.getSome({ filter: { journeyId: journeyId } })];
143
+ case 1: return [2 /*return*/, (_a.sent())];
144
+ }
145
+ });
146
+ }); };
147
+ enduserIdsFor = function (journeyId) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
148
+ switch (_a.label) {
149
+ case 0: return [4 /*yield*/, rowsFor_1(journeyId)];
150
+ case 1: return [2 /*return*/, (_a.sent()).map(function (r) { return r.enduserId; })
151
+ /* ===== A: the foreign id must never be planted, on EITHER branch ===== */
152
+ // A1 — inline branch: automationStepId supplied
153
+ ];
154
+ }
155
+ }); }); };
156
+ return [4 /*yield*/, attacker.sdk.api.endusers.add_to_journey({
157
+ enduserIds: [victim.id],
158
+ journeyId: inline.journey.id,
159
+ automationStepId: inline.step.id,
160
+ startAt: FUTURE_START(),
161
+ }).then(function () { return 'resolved'; }).catch(function (e) { return "rejected: ".concat((e === null || e === void 0 ? void 0 : e.message) || e); })];
162
+ case 8:
163
+ inlineResult = _d.sent();
164
+ return [4 /*yield*/, enduserIdsFor(inline.journey.id)];
165
+ case 9:
166
+ inlineIds = _d.sent();
167
+ record('A1: inline branch (automationStepId) does not plant a foreign enduserId', !inlineIds.includes(victim.id), "call=".concat(inlineResult, "; automated_actions enduserIds=").concat(JSON.stringify(inlineIds), " (must not contain ").concat(victim.id, ")"));
168
+ return [4 /*yield*/, attacker.sdk.api.endusers.add_to_journey({
169
+ enduserIds: [victim.id],
170
+ journeyId: modulePath.journey.id,
171
+ startAt: FUTURE_START(),
172
+ }).then(function () { return 'resolved'; }).catch(function (e) { return "rejected: ".concat((e === null || e === void 0 ? void 0 : e.message) || e); })];
173
+ case 10:
174
+ moduleResult = _d.sent();
175
+ return [4 /*yield*/, enduserIdsFor(modulePath.journey.id)];
176
+ case 11:
177
+ moduleIds = _d.sent();
178
+ record('A2: module branch (no automationStepId) does not plant a foreign enduserId', !moduleIds.includes(victim.id), "call=".concat(moduleResult, "; automated_actions enduserIds=").concat(JSON.stringify(moduleIds), " (must not contain ").concat(victim.id, ")"));
179
+ return [4 /*yield*/, victimTenant.sdk.api.endusers.getOne(victim.id)];
180
+ case 12:
181
+ victimAfter = _d.sent();
182
+ victimJourneys = (_c = victimAfter === null || victimAfter === void 0 ? void 0 : victimAfter.journeys) !== null && _c !== void 0 ? _c : {};
183
+ record('A3: the victim enduser was not enrolled in the attacker\'s journey', !victimJourneys[inline.journey.id] && !victimJourneys[modulePath.journey.id], "victim journeys=".concat(JSON.stringify(victimJourneys)));
184
+ /* ===== B: legitimate use must keep working ===== */
185
+ // B1 — positive control on both branches, so a blanket failure can't masquerade as a fix
186
+ return [4 /*yield*/, attacker.sdk.api.endusers.add_to_journey({
187
+ enduserIds: [own.id],
188
+ journeyId: inline.journey.id,
189
+ automationStepId: inline.step.id,
190
+ startAt: FUTURE_START(),
191
+ })];
192
+ case 13:
193
+ /* ===== B: legitimate use must keep working ===== */
194
+ // B1 — positive control on both branches, so a blanket failure can't masquerade as a fix
195
+ _d.sent();
196
+ return [4 /*yield*/, attacker.sdk.api.endusers.add_to_journey({
197
+ enduserIds: [own.id],
198
+ journeyId: modulePath.journey.id,
199
+ startAt: FUTURE_START(),
200
+ })];
201
+ case 14:
202
+ _d.sent();
203
+ return [4 /*yield*/, enduserIdsFor(inline.journey.id)];
204
+ case 15:
205
+ inlineOwn = _d.sent();
206
+ return [4 /*yield*/, enduserIdsFor(modulePath.journey.id)];
207
+ case 16:
208
+ moduleOwn = _d.sent();
209
+ record('B1: the caller\'s OWN enduser still enrolls on both branches', inlineOwn.includes(own.id) && moduleOwn.includes(own.id), "inline=".concat(JSON.stringify(inlineOwn), " module=").concat(JSON.stringify(moduleOwn), " (both must contain ").concat(own.id, ")"));
210
+ return [4 /*yield*/, attacker.sdk.api.endusers.add_to_journey({
211
+ enduserIds: [own.id, victim.id],
212
+ journeyId: mixed.journey.id,
213
+ automationStepId: mixed.step.id,
214
+ startAt: FUTURE_START(),
215
+ }).then(function () { return 'resolved'; }).catch(function (e) { return "rejected: ".concat((e === null || e === void 0 ? void 0 : e.message) || e); })];
216
+ case 17:
217
+ mixedResult = _d.sent();
218
+ return [4 /*yield*/, enduserIdsFor(mixed.journey.id)];
219
+ case 18:
220
+ mixedIds = _d.sent();
221
+ record('B2: a mixed list enrolls the visible enduser and drops only the foreign one', mixedIds.includes(own.id) && !mixedIds.includes(victim.id), "call=".concat(mixedResult, "; enduserIds=").concat(JSON.stringify(mixedIds), " (want ").concat(own.id, ", not ").concat(victim.id, ")"));
222
+ return [3 /*break*/, 26];
223
+ case 19:
224
+ _i = 0, _a = [attacker, victimTenant];
225
+ _d.label = 20;
226
+ case 20:
227
+ if (!(_i < _a.length)) return [3 /*break*/, 25];
228
+ t = _a[_i];
229
+ if (!t) return [3 /*break*/, 24];
230
+ _d.label = 21;
231
+ case 21:
232
+ _d.trys.push([21, 23, , 24]);
233
+ return [4 /*yield*/, t.teardown()];
234
+ case 22:
235
+ _d.sent();
236
+ return [3 /*break*/, 24];
237
+ case 23:
238
+ e_1 = _d.sent();
239
+ console.error('teardown failed', e_1);
240
+ return [3 /*break*/, 24];
241
+ case 24:
242
+ _i++;
243
+ return [3 /*break*/, 20];
244
+ case 25: return [7 /*endfinally*/];
245
+ case 26: return [2 /*return*/, checks];
246
+ }
247
+ });
248
+ }); };
249
+ /** Asserting wrapper, for standalone runs and for sequential use in the suite. */
250
+ export var add_to_journey_foreign_enduserid_tests = function () { return __awaiter(void 0, void 0, void 0, function () {
251
+ var _a, _b;
252
+ return __generator(this, function (_c) {
253
+ switch (_c.label) {
254
+ case 0:
255
+ _a = assert_checks;
256
+ _b = ['F-0196 add_to_journey foreign enduserId'];
257
+ return [4 /*yield*/, add_to_journey_foreign_enduserid_checks()];
258
+ case 1:
259
+ _a.apply(void 0, _b.concat([_c.sent()]));
260
+ return [2 /*return*/];
261
+ }
262
+ });
263
+ }); };
264
+ // Allow running this test file independently
265
+ if (require.main === module) {
266
+ console.log("\uD83C\uDF10 Using API URL: ".concat(host));
267
+ add_to_journey_foreign_enduserid_tests()
268
+ .then(function () {
269
+ console.log("✅ F-0196 add_to_journey foreign enduserId test suite completed successfully");
270
+ process.exit(0);
271
+ })
272
+ .catch(function (error) {
273
+ console.error("❌ F-0196 add_to_journey foreign enduserId test suite failed:", error);
274
+ process.exit(1);
275
+ });
276
+ }
277
+ //# sourceMappingURL=F-0196-add-to-journey-foreign-enduserid.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"F-0196-add-to-journey-foreign-enduserid.test.js","sourceRoot":"","sources":["../../../../../src/tests/api_tests/security/F-0196-add-to-journey-foreign-enduserid.test.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,OAAO,CAAC,oBAAoB,CAAC,CAAC,OAAO,EAAE,CAAC;AAExC,OAAO,EACL,aAAa,EAEb,qBAAqB,GACtB,MAAM,sBAAsB,CAAA;AAE7B,IAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,uBAAgC,CAAA;AAEpE,IAAM,IAAI,GAAG,cAAM,OAAA,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAvC,CAAuC,CAAA;AAE1D,gFAAgF;AAChF,IAAM,YAAY,GAAG,cAAM,OAAA,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,EAArC,CAAqC,CAAA;AAEhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,CAAC,IAAM,uCAAuC,GAAG;;;;;;;gBAC/C,MAAM,GAAY,EAAE,CAAA;gBACpB,MAAM,GAAG,UAAC,KAAa,EAAE,IAAa,EAAE,MAAc,IAAK,OAAA,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,OAAA,EAAE,IAAI,MAAA,EAAE,MAAM,QAAA,EAAE,CAAC,EAApC,CAAoC,CAAA;;;;gBAMnG,wEAAwE;gBAExE,CAAC;gBAA2B,qBAAM,OAAO,CAAC,GAAG,CAAC;wBAC5C,qBAAqB,CAAC,EAAE,IAAI,EAAE,yBAAkB,IAAI,EAAE,CAAE,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;wBACnG,qBAAqB,CAAC,EAAE,IAAI,EAAE,uBAAgB,IAAI,EAAE,CAAE,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;qBAClG,CAAC,EAAA;;gBAHD,KAA2B,SAG1B,EAHA,QAAQ,QAAA,EAAE,YAAY,QAAA,CAGtB;gBAEa,qBAAM,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAA;;gBAA3F,MAAM,GAAG,SAAkF;gBACrF,qBAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,EAAA;;gBAAxF,GAAG,GAAG,SAAkF;gBAE9F,MAAM,CACJ,8DAA8D,EAC9D,QAAQ,CAAC,UAAU,KAAK,YAAY,CAAC,UAAU,IAAI,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,EAC1E,mBAAY,QAAQ,CAAC,UAAU,4BAAkB,YAAY,CAAC,UAAU,4BAAkB,MAAM,CAAC,EAAE,CAAE,CACtG,CAAA;gBAIK,WAAW,GAAG,UAAO,KAAa;;;;oCACtB,qBAAM,QAAS,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,UAAG,KAAK,cAAI,IAAI,EAAE,CAAE,EAAE,CAAC,EAAA;;gCAArF,OAAO,GAAG,SAA2E;gCAC9E,qBAAM,QAAS,CAAC,GAAG,CAAC,GAAG,CAAC,gBAAgB,CAAC,SAAS,CAAC;wCAC9D,SAAS,EAAE,OAAO,CAAC,EAAE;wCACrB,0FAA0F;wCAC1F,gDAAgD;wCAChD,MAAM,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE;wCAC/D,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;qCACxC,CAAC,EAAA;;gCANH,IAAI,GAAG,SAMJ;gCACT,sBAAO,EAAE,OAAO,SAAA,EAAE,IAAI,MAAA,EAAE,EAAA;;;qBACzB,CAAA;gBAEc,qBAAM,WAAW,CAAC,cAAc,CAAC,EAAA;;gBAA1C,MAAM,GAAG,SAAiC;gBAC7B,qBAAM,WAAW,CAAC,cAAc,CAAC,EAAA;;gBAA9C,UAAU,GAAG,SAAiC;gBACtC,qBAAM,WAAW,CAAC,aAAa,CAAC;oBAE9C,4EAA4E;kBAF9B;;gBAAxC,KAAK,GAAG,SAAgC;gBAGxC,YAAU,UAAO,SAAiB;;;oCACtC,qBAAM,QAAS,CAAC,GAAG,CAAC,GAAG,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,EAAE,SAAS,WAAA,EAAE,EAAS,CAAC,EAAA;oCADxC,sBAAA,CAC3C,SAAmF,CACpF,EAAA;;;qBAAA,CAAA;gBACK,aAAa,GAAG,UAAO,SAAiB;;gCAAM,qBAAM,SAAO,CAAC,SAAS,CAAC,EAAA;gCAAzB,sBAAA,CAAC,SAAwB,CAAC,CAAC,GAAG,CAAC,UAAC,CAAM,IAAK,OAAA,CAAC,CAAC,SAAS,EAAX,CAAW,CAAC;4BAE1G,2EAA2E;4BAE3E,gDAAgD;0BAJ0D;;yBAAA,CAAA;gBAKrF,qBAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC;wBAClE,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;wBACvB,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE;wBAC5B,gBAAgB,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE;wBAChC,OAAO,EAAE,YAAY,EAAE;qBACjB,CAAC,CAAC,IAAI,CAAC,cAAM,OAAA,UAAU,EAAV,CAAU,CAAC,CAAC,KAAK,CAAC,UAAC,CAAM,IAAK,OAAA,oBAAa,CAAA,CAAC,aAAD,CAAC,uBAAD,CAAC,CAAE,OAAO,KAAI,CAAC,CAAE,EAA9B,CAA8B,CAAC,EAAA;;gBAL5E,YAAY,GAAG,SAK6D;gBAEhE,qBAAM,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,EAAA;;gBAAlD,SAAS,GAAG,SAAsC;gBACxD,MAAM,CACJ,yEAAyE,EACzE,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,EAC9B,eAAQ,YAAY,4CAAkC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,gCAAsB,MAAM,CAAC,EAAE,MAAG,CAClH,CAAA;gBAGoB,qBAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC;wBAClE,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;wBACvB,SAAS,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE;wBAChC,OAAO,EAAE,YAAY,EAAE;qBACjB,CAAC,CAAC,IAAI,CAAC,cAAM,OAAA,UAAU,EAAV,CAAU,CAAC,CAAC,KAAK,CAAC,UAAC,CAAM,IAAK,OAAA,oBAAa,CAAA,CAAC,aAAD,CAAC,uBAAD,CAAC,CAAE,OAAO,KAAI,CAAC,CAAE,EAA9B,CAA8B,CAAC,EAAA;;gBAJ5E,YAAY,GAAG,SAI6D;gBAEhE,qBAAM,aAAa,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,EAAA;;gBAAtD,SAAS,GAAG,SAA0C;gBAC5D,MAAM,CACJ,4EAA4E,EAC5E,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,EAC9B,eAAQ,YAAY,4CAAkC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,gCAAsB,MAAM,CAAC,EAAE,MAAG,CAClH,CAAA;gBAGmB,qBAAM,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,EAAA;;gBAAnE,WAAW,GAAG,SAAqD;gBACnE,cAAc,GAAG,MAAC,WAAmB,aAAnB,WAAW,uBAAX,WAAW,CAAU,QAAQ,mCAAI,EAAE,CAAA;gBAC3D,MAAM,CACJ,oEAAoE,EACpE,CAAC,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,EAC5E,0BAAmB,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,CAAE,CACpD,CAAA;gBAED,qDAAqD;gBAErD,yFAAyF;gBACzF,qBAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC;wBAC7C,UAAU,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;wBACpB,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,EAAE;wBAC5B,gBAAgB,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE;wBAChC,OAAO,EAAE,YAAY,EAAE;qBACjB,CAAC,EAAA;;gBART,qDAAqD;gBAErD,yFAAyF;gBACzF,SAKS,CAAA;gBACT,qBAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC;wBAC7C,UAAU,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;wBACpB,SAAS,EAAE,UAAU,CAAC,OAAO,CAAC,EAAE;wBAChC,OAAO,EAAE,YAAY,EAAE;qBACjB,CAAC,EAAA;;gBAJT,SAIS,CAAA;gBAES,qBAAM,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,EAAA;;gBAAlD,SAAS,GAAG,SAAsC;gBACtC,qBAAM,aAAa,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC,EAAA;;gBAAtD,SAAS,GAAG,SAA0C;gBAC5D,MAAM,CACJ,8DAA8D,EAC9D,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EACxD,iBAAU,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,qBAAW,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,iCAAuB,GAAG,CAAC,EAAE,MAAG,CACxG,CAAA;gBAImB,qBAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC;wBACjE,UAAU,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC;wBAC/B,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,EAAE;wBAC3B,gBAAgB,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE;wBAC/B,OAAO,EAAE,YAAY,EAAE;qBACjB,CAAC,CAAC,IAAI,CAAC,cAAM,OAAA,UAAU,EAAV,CAAU,CAAC,CAAC,KAAK,CAAC,UAAC,CAAM,IAAK,OAAA,oBAAa,CAAA,CAAC,aAAD,CAAC,uBAAD,CAAC,CAAE,OAAO,KAAI,CAAC,CAAE,EAA9B,CAA8B,CAAC,EAAA;;gBAL5E,WAAW,GAAG,SAK8D;gBAEjE,qBAAM,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,EAAA;;gBAAhD,QAAQ,GAAG,SAAqC;gBACtD,MAAM,CACJ,6EAA6E,EAC7E,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,EAC1D,eAAQ,WAAW,0BAAgB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,oBAAU,GAAG,CAAC,EAAE,mBAAS,MAAM,CAAC,EAAE,MAAG,CACjG,CAAA;;;sBAIuC,EAAxB,MAAC,QAAQ,EAAE,YAAY,CAAC;;;qBAAxB,CAAA,cAAwB,CAAA;gBAA7B,CAAC;qBACN,CAAC,EAAD,yBAAC;;;;gBAAU,qBAAM,CAAC,CAAC,QAAQ,EAAE,EAAA;;gBAAlB,SAAkB,CAAA;;;;gBAAe,OAAO,CAAC,KAAK,CAAC,iBAAiB,EAAE,GAAC,CAAC,CAAA;;;gBADrE,IAAwB,CAAA;;;qBAK1C,sBAAO,MAAM,EAAA;;;KACd,CAAA;AAED,kFAAkF;AAClF,MAAM,CAAC,IAAM,sCAAsC,GAAG;;;;;gBACpD,KAAA,aAAa,CAAA;sBAAC,yCAAyC;gBAAE,qBAAM,uCAAuC,EAAE,EAAA;;gBAAxG,4BAAyD,SAA+C,GAAC,CAAA;;;;KAC1G,CAAA;AAED,6CAA6C;AAC7C,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE;IAC3B,OAAO,CAAC,GAAG,CAAC,sCAAqB,IAAI,CAAE,CAAC,CAAA;IACxC,sCAAsC,EAAE;SACrC,IAAI,CAAC;QACJ,OAAO,CAAC,GAAG,CAAC,6EAA6E,CAAC,CAAA;QAC1F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC,CAAC;SACD,KAAK,CAAC,UAAC,KAAK;QACX,OAAO,CAAC,KAAK,CAAC,8DAA8D,EAAE,KAAK,CAAC,CAAA;QACpF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC,CAAC,CAAA;CACL"}
@@ -0,0 +1,5 @@
1
+ import { Check } from "../setup_isolated";
2
+ export declare const tickets_bulk_assign_from_queue_checks: () => Promise<Check[]>;
3
+ /** Asserting wrapper, for standalone runs and for sequential use in the suite. */
4
+ export declare const tickets_bulk_assign_from_queue_tests: () => Promise<void>;
5
+ //# sourceMappingURL=tickets_bulk_assign_from_queue.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tickets_bulk_assign_from_queue.test.d.ts","sourceRoot":"","sources":["../../../../src/tests/api_tests/tickets_bulk_assign_from_queue.test.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,EAEN,MAAM,mBAAmB,CAAA;AAmD1B,eAAO,MAAM,qCAAqC,QAAa,QAAQ,KAAK,EAAE,CAyM7E,CAAA;AAED,kFAAkF;AAClF,eAAO,MAAM,oCAAoC,qBAEhD,CAAA"}
@@ -0,0 +1,324 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var __generator = (this && this.__generator) || function (thisArg, body) {
11
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
12
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
13
+ function verb(n) { return function (v) { return step([n, v]); }; }
14
+ function step(op) {
15
+ if (f) throw new TypeError("Generator is already executing.");
16
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
17
+ 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;
18
+ if (y = 0, t) op = [op[0] & 2, t.value];
19
+ switch (op[0]) {
20
+ case 0: case 1: t = op; break;
21
+ case 4: _.label++; return { value: op[1], done: false };
22
+ case 5: _.label++; y = op[1]; op = [0]; continue;
23
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
24
+ default:
25
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
26
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
27
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
28
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
29
+ if (t[2]) _.ops.pop();
30
+ _.trys.pop(); continue;
31
+ }
32
+ op = body.call(thisArg, _);
33
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
34
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
35
+ }
36
+ };
37
+ require('source-map-support').install();
38
+ import { Session } from "../../sdk";
39
+ import { assert_checks, setup_isolated_tenant, } from "../setup_isolated";
40
+ var host = process.env.API_URL || 'http://localhost:8080';
41
+ var RAND = function () { return Math.random().toString(36).slice(2, 10); };
42
+ /**
43
+ * Coverage for `tickets.bulk_assign` when a `queueId` is supplied — the bulk equivalent of
44
+ * `tickets.assign_from_queue`, added so a Ticket Queue's tickets can be checkbox-selected and handed
45
+ * to one user in a single call instead of one `assign_from_queue` round trip per ticket.
46
+ *
47
+ * Without `queueId` the endpoint only does `$set: { owner }`. That is insufficient for a QUEUED
48
+ * ticket: it would stay in the queue (still pullable by someone else), carry no dequeue provenance,
49
+ * and leave `ticket_queues.count` drifting upward forever. The queue branch therefore also unsets
50
+ * `queueId`, sets `dequeuedAt`/`dequeuedFrom`, and decrements the queue's count — the same
51
+ * bookkeeping `assign_from_queue` does for a single ticket. A/B/C below pin each of those.
52
+ *
53
+ * D is the check that actually justifies the handler's shape. The tickets model declares
54
+ * `constraints.access: [{ type: 'filter', field: 'owner' }]`, which the DB layer turns into
55
+ * `owner === self OR creator === self` for any non-`All` access. A queued ticket has no owner, and
56
+ * was typically created by someone else, so a non-admin queue manager's `DB.tickets.updateMany`
57
+ * matches NOTHING — the call would return 200 having assigned zero tickets, silently. The handler
58
+ * uses `buildOrganizationWideQueries` to get past that, exactly as `assign_from_queue` does, and
59
+ * re-adds the queue-membership check that those queries bypass. D1 proves the escape hatch works for
60
+ * a legitimate queue member; D2 proves it is still gated, and is the check that fails if someone
61
+ * later drops the membership check while keeping the organization-wide queries.
62
+ *
63
+ * A provisioned non-admin gets `tickets: ASSIGNED_AND_DEFAULT_ACCESS` (read/update = 'Assigned'),
64
+ * which is what makes D meaningful. The D tickets deliberately have NO enduserId, so the 'Assigned'
65
+ * role filter's other branch (enduserId in assignedEnduserIds) cannot match either — the ONLY way
66
+ * they can be assigned is through the organization-wide path.
67
+ *
68
+ * E is the regression guard on the pre-existing no-`queueId` behavior, which must be unchanged.
69
+ *
70
+ * Cross-tenant (F) is covered because `buildOrganizationWideQueries` is bound to the caller's
71
+ * businessId: another tenant's queue is invisible even to an admin, who skips the membership check.
72
+ *
73
+ * `ticket_queues.count` is maintained by asynchronous side effects (increment on ticket create) and
74
+ * the decrement here is deliberately fire-and-forget, so every count read polls.
75
+ */
76
+ // count is maintained by async side effects; give it a moment to settle before reading
77
+ var poll_for = function (get, done, attempts) {
78
+ if (attempts === void 0) { attempts = 20; }
79
+ return __awaiter(void 0, void 0, void 0, function () {
80
+ var value, i;
81
+ return __generator(this, function (_a) {
82
+ switch (_a.label) {
83
+ case 0: return [4 /*yield*/, get()];
84
+ case 1:
85
+ value = _a.sent();
86
+ i = 0;
87
+ _a.label = 2;
88
+ case 2:
89
+ if (!(i < attempts && !done(value))) return [3 /*break*/, 6];
90
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 250); })];
91
+ case 3:
92
+ _a.sent();
93
+ return [4 /*yield*/, get()];
94
+ case 4:
95
+ value = _a.sent();
96
+ _a.label = 5;
97
+ case 5:
98
+ i++;
99
+ return [3 /*break*/, 2];
100
+ case 6: return [2 /*return*/, value];
101
+ }
102
+ });
103
+ });
104
+ };
105
+ export var tickets_bulk_assign_from_queue_checks = function () { return __awaiter(void 0, void 0, void 0, function () {
106
+ var checks, record, tenant, otherTenant, sdk_1, sdkNonAdmin, queueMember, outsider, sdkOutsider, owner, queue_1, enduser, queued1, queued2, countBefore, assignResult, after1, countAfter, _a, _b, replayResult, countAfterReplay, otherQueue, inOtherQueue, wrongQueueResult, stillInOther, forMember1, forMember2, memberResult, memberAfter, forOutsider, outsiderResult, outsiderAfter, stayQueued, legacyResult, stayedAfter, repeatResult, foreignResult, foreignAfter, _i, _c, t, e_1;
107
+ var _d;
108
+ var _e, _f;
109
+ return __generator(this, function (_g) {
110
+ switch (_g.label) {
111
+ case 0:
112
+ checks = [];
113
+ record = function (label, pass, detail) { return checks.push({ label: label, pass: pass, detail: detail }); };
114
+ _g.label = 1;
115
+ case 1:
116
+ _g.trys.push([1, , 33, 40]);
117
+ /* ================= fixtures ================= */
118
+ // users[1] joins the queue, users[2] deliberately does not (D2)
119
+ ;
120
+ return [4 /*yield*/, Promise.all([
121
+ setup_isolated_tenant({
122
+ name: "BulkAssignQueue ".concat(RAND()),
123
+ users: [{ isAdmin: true }, { roles: ['Non-Admin'] }, { roles: ['Non-Admin'] }],
124
+ apiKeys: 0,
125
+ }),
126
+ setup_isolated_tenant({
127
+ name: "BulkAssignQueue other ".concat(RAND()),
128
+ users: [{ isAdmin: true }],
129
+ apiKeys: 0,
130
+ }),
131
+ ])];
132
+ case 2:
133
+ _d = _g.sent(), tenant = _d[0], otherTenant = _d[1];
134
+ sdk_1 = tenant.sdk, sdkNonAdmin = tenant.sdkNonAdmin;
135
+ queueMember = tenant.users[1];
136
+ outsider = tenant.users[2];
137
+ sdkOutsider = new Session({ host: host, authToken: outsider.authToken });
138
+ return [4 /*yield*/, sdkOutsider.refresh_session()];
139
+ case 3:
140
+ _g.sent();
141
+ return [4 /*yield*/, sdk_1.api.users.createOne({
142
+ email: "dontsend-bulk-assign-queue-".concat(RAND(), "@tellescope.com"),
143
+ fname: 'BulkAssign', lname: 'QueueOwner',
144
+ verifiedEmail: true,
145
+ })];
146
+ case 4:
147
+ owner = _g.sent();
148
+ return [4 /*yield*/, sdk_1.api.ticket_queues.createOne({
149
+ title: "Bulk Assign Queue ".concat(RAND()),
150
+ userIds: [owner.id, queueMember.id], // outsider is NOT a member
151
+ })];
152
+ case 5:
153
+ queue_1 = _g.sent();
154
+ return [4 /*yield*/, sdk_1.api.endusers.createOne({ fname: 'BulkAssign', lname: 'QueuePatient' })
155
+ // A/B/C fixtures — one carries an enduser so care team behavior is exercised alongside dequeue
156
+ ];
157
+ case 6:
158
+ enduser = _g.sent();
159
+ return [4 /*yield*/, sdk_1.api.tickets.createOne({ title: 'BAQ 1', enduserId: enduser.id, queueId: queue_1.id })];
160
+ case 7:
161
+ queued1 = _g.sent();
162
+ return [4 /*yield*/, sdk_1.api.tickets.createOne({ title: 'BAQ 2', queueId: queue_1.id })];
163
+ case 8:
164
+ queued2 = _g.sent();
165
+ return [4 /*yield*/, poll_for(function () { return sdk_1.api.ticket_queues.getOne(queue_1.id).then(function (q) { var _a; return (_a = q.count) !== null && _a !== void 0 ? _a : 0; }); }, function (c) { return c === 2; })];
166
+ case 9:
167
+ countBefore = _g.sent();
168
+ record('setup: queue count reflects both queued tickets', countBefore === 2, "count=".concat(countBefore, " (want 2); queue=").concat(queue_1.id));
169
+ return [4 /*yield*/, sdk_1.api.tickets.bulk_assign({
170
+ ids: [queued1.id, queued2.id], userId: owner.id, queueId: queue_1.id, addToCareTeam: true,
171
+ })];
172
+ case 10:
173
+ assignResult = _g.sent();
174
+ record('A1: reports both tickets assigned and none skipped', assignResult.assigned === 2 && assignResult.skipped === 0, "assigned=".concat(assignResult.assigned, " skipped=").concat(assignResult.skipped, " (want 2 / 0)"));
175
+ return [4 /*yield*/, sdk_1.api.tickets.getOne(queued1.id)];
176
+ case 11:
177
+ after1 = _g.sent();
178
+ record('A2: ticket left the queue with owner and dequeue provenance set', !after1.queueId && after1.owner === owner.id && !!after1.dequeuedAt && after1.dequeuedFrom === queue_1.id, "queueId=".concat(JSON.stringify(after1.queueId), " owner=").concat(after1.owner, " dequeuedAt=").concat(JSON.stringify(after1.dequeuedAt), " dequeuedFrom=").concat(after1.dequeuedFrom));
179
+ return [4 /*yield*/, poll_for(function () { return sdk_1.api.ticket_queues.getOne(queue_1.id).then(function (q) { var _a; return (_a = q.count) !== null && _a !== void 0 ? _a : 0; }); }, function (c) { return c === 0; })];
180
+ case 12:
181
+ countAfter = _g.sent();
182
+ record('A3: queue count decremented by the number assigned', countAfter === 0, "count=".concat(countAfter, " (want 0, was ").concat(countBefore, ")"));
183
+ _a = record;
184
+ _b = ['A4: addToCareTeam adds the new owner to the ticket enduser\'s care team'];
185
+ return [4 /*yield*/, sdk_1.api.endusers.getOne(enduser.id)];
186
+ case 13:
187
+ _a.apply(void 0, _b.concat([!!((_e = (_g.sent()).assignedTo) === null || _e === void 0 ? void 0 : _e.includes(owner.id)), "assignedTo must contain ".concat(owner.id)]));
188
+ return [4 /*yield*/, sdk_1.api.tickets.bulk_assign({
189
+ ids: [queued1.id, queued2.id], userId: owner.id, queueId: queue_1.id,
190
+ })];
191
+ case 14:
192
+ replayResult = _g.sent();
193
+ record('B1: already-dequeued ids are reported as skipped', replayResult.assigned === 0 && replayResult.skipped === 2, "assigned=".concat(replayResult.assigned, " skipped=").concat(replayResult.skipped, " (want 0 / 2)"));
194
+ return [4 /*yield*/, sdk_1.api.ticket_queues.getOne(queue_1.id).then(function (q) { var _a; return (_a = q.count) !== null && _a !== void 0 ? _a : 0; })];
195
+ case 15:
196
+ countAfterReplay = _g.sent();
197
+ record('B2: queue count is not decremented again when nothing was assigned', countAfterReplay === 0, "count=".concat(countAfterReplay, " (want 0 \u2014 a decrement here would drive it negative)"));
198
+ return [4 /*yield*/, sdk_1.api.ticket_queues.createOne({
199
+ title: "Bulk Assign Other Queue ".concat(RAND()),
200
+ userIds: [owner.id],
201
+ })];
202
+ case 16:
203
+ otherQueue = _g.sent();
204
+ return [4 /*yield*/, sdk_1.api.tickets.createOne({ title: 'BAQ other queue', queueId: otherQueue.id })];
205
+ case 17:
206
+ inOtherQueue = _g.sent();
207
+ return [4 /*yield*/, sdk_1.api.tickets.bulk_assign({
208
+ ids: [inOtherQueue.id], userId: owner.id, queueId: queue_1.id,
209
+ })];
210
+ case 18:
211
+ wrongQueueResult = _g.sent();
212
+ return [4 /*yield*/, sdk_1.api.tickets.getOne(inOtherQueue.id)];
213
+ case 19:
214
+ stillInOther = _g.sent();
215
+ record('C1: a ticket in a different queue is not assigned when the wrong queueId is passed', wrongQueueResult.assigned === 0 && stillInOther.queueId === otherQueue.id && !stillInOther.owner, "assigned=".concat(wrongQueueResult.assigned, " (want 0); ticket queueId=").concat(stillInOther.queueId, " owner=").concat(JSON.stringify(stillInOther.owner)));
216
+ return [4 /*yield*/, sdk_1.api.tickets.createOne({ title: 'BAQ member 1', queueId: queue_1.id })];
217
+ case 20:
218
+ forMember1 = _g.sent();
219
+ return [4 /*yield*/, sdk_1.api.tickets.createOne({ title: 'BAQ member 2', queueId: queue_1.id })];
220
+ case 21:
221
+ forMember2 = _g.sent();
222
+ return [4 /*yield*/, sdkNonAdmin.api.tickets.bulk_assign({
223
+ ids: [forMember1.id, forMember2.id], userId: owner.id, queueId: queue_1.id,
224
+ }).then(function (r) { return r; }).catch(function (e) { return ({ error: (e === null || e === void 0 ? void 0 : e.message) || String(e) }); })];
225
+ case 22:
226
+ memberResult = _g.sent();
227
+ return [4 /*yield*/, sdk_1.api.tickets.getOne(forMember1.id)];
228
+ case 23:
229
+ memberAfter = _g.sent();
230
+ record('D1: a non-admin queue member can bulk assign queued tickets they do not own', (memberResult === null || memberResult === void 0 ? void 0 : memberResult.assigned) === 2 && !memberAfter.queueId && memberAfter.owner === owner.id, "result=".concat(JSON.stringify(memberResult), "; ticket queueId=").concat(JSON.stringify(memberAfter.queueId), " owner=").concat(memberAfter.owner)
231
+ + " \u2014 0 assigned here means the handler fell back to the owner-filtered DB");
232
+ return [4 /*yield*/, sdk_1.api.tickets.createOne({ title: 'BAQ outsider', queueId: queue_1.id })];
233
+ case 24:
234
+ forOutsider = _g.sent();
235
+ return [4 /*yield*/, sdkOutsider.api.tickets.bulk_assign({
236
+ ids: [forOutsider.id], userId: outsider.id, queueId: queue_1.id,
237
+ }).then(function (r) { return ({ resolved: r }); }).catch(function (e) { return ({ rejected: (e === null || e === void 0 ? void 0 : e.message) || String(e) }); })];
238
+ case 25:
239
+ outsiderResult = _g.sent();
240
+ return [4 /*yield*/, sdk_1.api.tickets.getOne(forOutsider.id)];
241
+ case 26:
242
+ outsiderAfter = _g.sent();
243
+ record('D2: a non-admin who is not a member of the queue is rejected', !!outsiderResult.rejected && outsiderAfter.queueId === queue_1.id && !outsiderAfter.owner, "result=".concat(JSON.stringify(outsiderResult), "; ticket queueId=").concat(outsiderAfter.queueId, " owner=").concat(JSON.stringify(outsiderAfter.owner)));
244
+ return [4 /*yield*/, sdk_1.api.tickets.createOne({ title: 'BAQ not dequeued', queueId: queue_1.id })];
245
+ case 27:
246
+ stayQueued = _g.sent();
247
+ return [4 /*yield*/, sdk_1.api.tickets.bulk_assign({ ids: [stayQueued.id], userId: owner.id })];
248
+ case 28:
249
+ legacyResult = _g.sent();
250
+ return [4 /*yield*/, sdk_1.api.tickets.getOne(stayQueued.id)];
251
+ case 29:
252
+ stayedAfter = _g.sent();
253
+ record('E1: without queueId the ticket is assigned but NOT dequeued', legacyResult.assigned === 1 && stayedAfter.owner === owner.id
254
+ && stayedAfter.queueId === queue_1.id && !stayedAfter.dequeuedAt, "assigned=".concat(legacyResult.assigned, "; owner=").concat(stayedAfter.owner, " queueId=").concat(stayedAfter.queueId, " dequeuedAt=").concat(JSON.stringify(stayedAfter.dequeuedAt)));
255
+ return [4 /*yield*/, sdk_1.api.tickets.bulk_assign({ ids: [stayQueued.id], userId: owner.id })];
256
+ case 30:
257
+ repeatResult = _g.sent();
258
+ record('E2: re-assigning to the same owner is not reported as skipped', repeatResult.assigned === 1 && repeatResult.skipped === 0, "assigned=".concat(repeatResult.assigned, " skipped=").concat(repeatResult.skipped, " (want 1 / 0 \u2014 matchedCount, not modifiedCount)"));
259
+ return [4 /*yield*/, otherTenant.sdk.api.tickets.bulk_assign({
260
+ ids: [forOutsider.id], userId: otherTenant.users[0].id, queueId: queue_1.id,
261
+ }).then(function (r) { return ({ resolved: r }); }).catch(function (e) { return ({ rejected: (e === null || e === void 0 ? void 0 : e.message) || String(e) }); })];
262
+ case 31:
263
+ foreignResult = _g.sent();
264
+ return [4 /*yield*/, sdk_1.api.tickets.getOne(forOutsider.id)];
265
+ case 32:
266
+ foreignAfter = _g.sent();
267
+ record('F1: another tenant cannot assign tickets out of this tenant\'s queue', (foreignAfter.queueId === queue_1.id && !foreignAfter.owner)
268
+ && (foreignResult.rejected || ((_f = foreignResult.resolved) === null || _f === void 0 ? void 0 : _f.assigned) === 0), "result=".concat(JSON.stringify(foreignResult), "; ticket queueId=").concat(foreignAfter.queueId, " owner=").concat(JSON.stringify(foreignAfter.owner)));
269
+ return [3 /*break*/, 40];
270
+ case 33:
271
+ _i = 0, _c = [tenant, otherTenant];
272
+ _g.label = 34;
273
+ case 34:
274
+ if (!(_i < _c.length)) return [3 /*break*/, 39];
275
+ t = _c[_i];
276
+ if (!t) return [3 /*break*/, 38];
277
+ _g.label = 35;
278
+ case 35:
279
+ _g.trys.push([35, 37, , 38]);
280
+ return [4 /*yield*/, t.teardown()];
281
+ case 36:
282
+ _g.sent();
283
+ return [3 /*break*/, 38];
284
+ case 37:
285
+ e_1 = _g.sent();
286
+ console.error('teardown failed', e_1);
287
+ return [3 /*break*/, 38];
288
+ case 38:
289
+ _i++;
290
+ return [3 /*break*/, 34];
291
+ case 39: return [7 /*endfinally*/];
292
+ case 40: return [2 /*return*/, checks];
293
+ }
294
+ });
295
+ }); };
296
+ /** Asserting wrapper, for standalone runs and for sequential use in the suite. */
297
+ export var tickets_bulk_assign_from_queue_tests = function () { return __awaiter(void 0, void 0, void 0, function () {
298
+ var _a, _b;
299
+ return __generator(this, function (_c) {
300
+ switch (_c.label) {
301
+ case 0:
302
+ _a = assert_checks;
303
+ _b = ['Tickets bulk assign from queue'];
304
+ return [4 /*yield*/, tickets_bulk_assign_from_queue_checks()];
305
+ case 1:
306
+ _a.apply(void 0, _b.concat([_c.sent()]));
307
+ return [2 /*return*/];
308
+ }
309
+ });
310
+ }); };
311
+ // Allow running this test file independently
312
+ if (require.main === module) {
313
+ console.log("\uD83C\uDF10 Using API URL: ".concat(host));
314
+ tickets_bulk_assign_from_queue_tests()
315
+ .then(function () {
316
+ console.log("✅ Tickets bulk assign from queue test suite completed successfully");
317
+ process.exit(0);
318
+ })
319
+ .catch(function (error) {
320
+ console.error("❌ Tickets bulk assign from queue test suite failed:", error);
321
+ process.exit(1);
322
+ });
323
+ }
324
+ //# sourceMappingURL=tickets_bulk_assign_from_queue.test.js.map