@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,282 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.add_to_journey_foreign_enduserid_tests = exports.add_to_journey_foreign_enduserid_checks = void 0;
40
+ require('source-map-support').install();
41
+ var setup_isolated_1 = require("../../setup_isolated");
42
+ var host = process.env.API_URL || 'http://localhost:8080';
43
+ var RAND = function () { return Math.random().toString(36).slice(2, 10); };
44
+ // far enough out that a locally running worker never claims the rows we inspect
45
+ var FUTURE_START = function () { return new Date(Date.now() + 60 * 60 * 1000); };
46
+ /**
47
+ * Regression test for F-0196
48
+ * (security-audit/findings/F-0196-add-to-journey-plants-foreign-enduserid.md).
49
+ *
50
+ * `endusers.add_to_journey` inserted an `automated_actions` row for EACH caller-supplied `enduserId`
51
+ * with only mongoId-shape validation and no tenant-membership check. The preceding
52
+ * `DB.endusers.updateMany` is scoped but its result was discarded, and
53
+ * `DB.automated_actions.insertMany` is a direct write that bypasses the `ensureValidDependentKeys`
54
+ * foreign-key check the generic CRUD route gets. The resulting row is stamped
55
+ * `businessId: <caller's tenant>` while carrying `enduserId: <a victim in another tenant>`.
56
+ *
57
+ * The worker then dereferences that enduser with the raw driver and no `businessId`
58
+ * (worker/modules/automations.ts, the F-0074/F-0111/F-0159 family), so the victim's record loads
59
+ * under the attacker's automation: `pushFormsToPortal` copies the victim's PHI into the attacker's
60
+ * `form_responses` (readable by their staff) and `iterableCustomEvent` writes an attacker-authored
61
+ * note into the victim's chart. F-0196 is the reachability that made those three exploitable.
62
+ *
63
+ * There are TWO independent plant sites and a fix to only one is a half fix:
64
+ * - inline branch, taken when `automationStepId` is supplied (api.ts add_to_journey handler)
65
+ * - module branch, taken when it is not: `handle_automation_event`'s
66
+ * `validEnduserIds = conditionsQuery ? <scoped find> : event.enduserIds` passes caller ids
67
+ * straight through for a step with NO conditions.
68
+ * Both are covered below (A1/A2).
69
+ *
70
+ * Design notes:
71
+ * - Two provisioned tenants, so the victim is a real foreign enduser and we can also assert their
72
+ * record was not mutated. Both must come from provisioning: `add_to_journey` is
73
+ * `requiresSubscription: true`, and a `create_and_join` org is born with an expired subscription.
74
+ * - The steps use `setEnduserStatus` — no vendor, no Bedrock — and `startAt` is an hour out so a
75
+ * running worker cannot process the rows before we read them.
76
+ * - `throttle` is deliberately omitted: it is the only way add_to_journey's per-pair rate limit can
77
+ * throw.
78
+ * - Everything is observable through `automated_actions` reads, so the worker is never needed.
79
+ *
80
+ * RED (pre-fix): A1, A2 and B2 fail — the victim's id appears in the attacker tenant's
81
+ * automated_actions. GREEN (post-fix): the foreign id is silently dropped and the caller's own
82
+ * endusers still enroll normally.
83
+ */
84
+ var add_to_journey_foreign_enduserid_checks = function () { return __awaiter(void 0, void 0, void 0, function () {
85
+ 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;
86
+ var _b;
87
+ var _c;
88
+ return __generator(this, function (_d) {
89
+ switch (_d.label) {
90
+ case 0:
91
+ checks = [];
92
+ record = function (label, pass, detail) { return checks.push({ label: label, pass: pass, detail: detail }); };
93
+ _d.label = 1;
94
+ case 1:
95
+ _d.trys.push([1, , 19, 26]);
96
+ /* ================= fixtures: two isolated tenants ================= */
97
+ ;
98
+ return [4 /*yield*/, Promise.all([
99
+ (0, setup_isolated_1.setup_isolated_tenant)({ name: "F0196 attacker ".concat(RAND()), users: [{ isAdmin: true }], apiKeys: 0 }),
100
+ (0, setup_isolated_1.setup_isolated_tenant)({ name: "F0196 victim ".concat(RAND()), users: [{ isAdmin: true }], apiKeys: 0 }),
101
+ ])];
102
+ case 2:
103
+ _b = _d.sent(), attacker = _b[0], victimTenant = _b[1];
104
+ return [4 /*yield*/, victimTenant.sdk.api.endusers.createOne({ fname: 'F0196', lname: 'Victim' })];
105
+ case 3:
106
+ victim = _d.sent();
107
+ return [4 /*yield*/, attacker.sdk.api.endusers.createOne({ fname: 'F0196', lname: 'OwnPatient' })];
108
+ case 4:
109
+ own = _d.sent();
110
+ 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));
111
+ makeJourney = function (title) { return __awaiter(void 0, void 0, void 0, function () {
112
+ var journey, step;
113
+ return __generator(this, function (_a) {
114
+ switch (_a.label) {
115
+ case 0: return [4 /*yield*/, attacker.sdk.api.journeys.createOne({ title: "".concat(title, " ").concat(RAND()) })];
116
+ case 1:
117
+ journey = _a.sent();
118
+ return [4 /*yield*/, attacker.sdk.api.automation_steps.createOne({
119
+ journeyId: journey.id,
120
+ // benign and worker-local: no vendor, no Bedrock. NO `conditions`, which is precisely the
121
+ // vulnerable branch in handle_automation_event.
122
+ action: { type: 'setEnduserStatus', info: { status: 'F0196' } },
123
+ events: [{ type: 'onJourneyStart', info: {} }],
124
+ })];
125
+ case 2:
126
+ step = _a.sent();
127
+ return [2 /*return*/, { journey: journey, step: step }];
128
+ }
129
+ });
130
+ }); };
131
+ return [4 /*yield*/, makeJourney('F0196 inline')];
132
+ case 5:
133
+ inline = _d.sent();
134
+ return [4 /*yield*/, makeJourney('F0196 module')];
135
+ case 6:
136
+ modulePath = _d.sent();
137
+ return [4 /*yield*/, makeJourney('F0196 mixed')
138
+ /** every automated_actions row the attacker tenant can see for a journey */
139
+ ];
140
+ case 7:
141
+ mixed = _d.sent();
142
+ rowsFor_1 = function (journeyId) { return __awaiter(void 0, void 0, void 0, function () {
143
+ return __generator(this, function (_a) {
144
+ switch (_a.label) {
145
+ case 0: return [4 /*yield*/, attacker.sdk.api.automated_actions.getSome({ filter: { journeyId: journeyId } })];
146
+ case 1: return [2 /*return*/, (_a.sent())];
147
+ }
148
+ });
149
+ }); };
150
+ enduserIdsFor = function (journeyId) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
151
+ switch (_a.label) {
152
+ case 0: return [4 /*yield*/, rowsFor_1(journeyId)];
153
+ case 1: return [2 /*return*/, (_a.sent()).map(function (r) { return r.enduserId; })
154
+ /* ===== A: the foreign id must never be planted, on EITHER branch ===== */
155
+ // A1 — inline branch: automationStepId supplied
156
+ ];
157
+ }
158
+ }); }); };
159
+ return [4 /*yield*/, attacker.sdk.api.endusers.add_to_journey({
160
+ enduserIds: [victim.id],
161
+ journeyId: inline.journey.id,
162
+ automationStepId: inline.step.id,
163
+ startAt: FUTURE_START(),
164
+ }).then(function () { return 'resolved'; }).catch(function (e) { return "rejected: ".concat((e === null || e === void 0 ? void 0 : e.message) || e); })];
165
+ case 8:
166
+ inlineResult = _d.sent();
167
+ return [4 /*yield*/, enduserIdsFor(inline.journey.id)];
168
+ case 9:
169
+ inlineIds = _d.sent();
170
+ 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, ")"));
171
+ return [4 /*yield*/, attacker.sdk.api.endusers.add_to_journey({
172
+ enduserIds: [victim.id],
173
+ journeyId: modulePath.journey.id,
174
+ startAt: FUTURE_START(),
175
+ }).then(function () { return 'resolved'; }).catch(function (e) { return "rejected: ".concat((e === null || e === void 0 ? void 0 : e.message) || e); })];
176
+ case 10:
177
+ moduleResult = _d.sent();
178
+ return [4 /*yield*/, enduserIdsFor(modulePath.journey.id)];
179
+ case 11:
180
+ moduleIds = _d.sent();
181
+ 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, ")"));
182
+ return [4 /*yield*/, victimTenant.sdk.api.endusers.getOne(victim.id)];
183
+ case 12:
184
+ victimAfter = _d.sent();
185
+ victimJourneys = (_c = victimAfter === null || victimAfter === void 0 ? void 0 : victimAfter.journeys) !== null && _c !== void 0 ? _c : {};
186
+ 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)));
187
+ /* ===== B: legitimate use must keep working ===== */
188
+ // B1 — positive control on both branches, so a blanket failure can't masquerade as a fix
189
+ return [4 /*yield*/, attacker.sdk.api.endusers.add_to_journey({
190
+ enduserIds: [own.id],
191
+ journeyId: inline.journey.id,
192
+ automationStepId: inline.step.id,
193
+ startAt: FUTURE_START(),
194
+ })];
195
+ case 13:
196
+ /* ===== B: legitimate use must keep working ===== */
197
+ // B1 — positive control on both branches, so a blanket failure can't masquerade as a fix
198
+ _d.sent();
199
+ return [4 /*yield*/, attacker.sdk.api.endusers.add_to_journey({
200
+ enduserIds: [own.id],
201
+ journeyId: modulePath.journey.id,
202
+ startAt: FUTURE_START(),
203
+ })];
204
+ case 14:
205
+ _d.sent();
206
+ return [4 /*yield*/, enduserIdsFor(inline.journey.id)];
207
+ case 15:
208
+ inlineOwn = _d.sent();
209
+ return [4 /*yield*/, enduserIdsFor(modulePath.journey.id)];
210
+ case 16:
211
+ moduleOwn = _d.sent();
212
+ 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, ")"));
213
+ return [4 /*yield*/, attacker.sdk.api.endusers.add_to_journey({
214
+ enduserIds: [own.id, victim.id],
215
+ journeyId: mixed.journey.id,
216
+ automationStepId: mixed.step.id,
217
+ startAt: FUTURE_START(),
218
+ }).then(function () { return 'resolved'; }).catch(function (e) { return "rejected: ".concat((e === null || e === void 0 ? void 0 : e.message) || e); })];
219
+ case 17:
220
+ mixedResult = _d.sent();
221
+ return [4 /*yield*/, enduserIdsFor(mixed.journey.id)];
222
+ case 18:
223
+ mixedIds = _d.sent();
224
+ 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, ")"));
225
+ return [3 /*break*/, 26];
226
+ case 19:
227
+ _i = 0, _a = [attacker, victimTenant];
228
+ _d.label = 20;
229
+ case 20:
230
+ if (!(_i < _a.length)) return [3 /*break*/, 25];
231
+ t = _a[_i];
232
+ if (!t) return [3 /*break*/, 24];
233
+ _d.label = 21;
234
+ case 21:
235
+ _d.trys.push([21, 23, , 24]);
236
+ return [4 /*yield*/, t.teardown()];
237
+ case 22:
238
+ _d.sent();
239
+ return [3 /*break*/, 24];
240
+ case 23:
241
+ e_1 = _d.sent();
242
+ console.error('teardown failed', e_1);
243
+ return [3 /*break*/, 24];
244
+ case 24:
245
+ _i++;
246
+ return [3 /*break*/, 20];
247
+ case 25: return [7 /*endfinally*/];
248
+ case 26: return [2 /*return*/, checks];
249
+ }
250
+ });
251
+ }); };
252
+ exports.add_to_journey_foreign_enduserid_checks = add_to_journey_foreign_enduserid_checks;
253
+ /** Asserting wrapper, for standalone runs and for sequential use in the suite. */
254
+ var add_to_journey_foreign_enduserid_tests = function () { return __awaiter(void 0, void 0, void 0, function () {
255
+ var _a, _b;
256
+ return __generator(this, function (_c) {
257
+ switch (_c.label) {
258
+ case 0:
259
+ _a = setup_isolated_1.assert_checks;
260
+ _b = ['F-0196 add_to_journey foreign enduserId'];
261
+ return [4 /*yield*/, (0, exports.add_to_journey_foreign_enduserid_checks)()];
262
+ case 1:
263
+ _a.apply(void 0, _b.concat([_c.sent()]));
264
+ return [2 /*return*/];
265
+ }
266
+ });
267
+ }); };
268
+ exports.add_to_journey_foreign_enduserid_tests = add_to_journey_foreign_enduserid_tests;
269
+ // Allow running this test file independently
270
+ if (require.main === module) {
271
+ console.log("\uD83C\uDF10 Using API URL: ".concat(host));
272
+ (0, exports.add_to_journey_foreign_enduserid_tests)()
273
+ .then(function () {
274
+ console.log("✅ F-0196 add_to_journey foreign enduserId test suite completed successfully");
275
+ process.exit(0);
276
+ })
277
+ .catch(function (error) {
278
+ console.error("❌ F-0196 add_to_journey foreign enduserId test suite failed:", error);
279
+ process.exit(1);
280
+ });
281
+ }
282
+ //# 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":"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"}
@@ -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,329 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.tickets_bulk_assign_from_queue_tests = exports.tickets_bulk_assign_from_queue_checks = void 0;
40
+ require('source-map-support').install();
41
+ var sdk_1 = require("../../sdk");
42
+ var setup_isolated_1 = require("../setup_isolated");
43
+ var host = process.env.API_URL || 'http://localhost:8080';
44
+ var RAND = function () { return Math.random().toString(36).slice(2, 10); };
45
+ /**
46
+ * Coverage for `tickets.bulk_assign` when a `queueId` is supplied — the bulk equivalent of
47
+ * `tickets.assign_from_queue`, added so a Ticket Queue's tickets can be checkbox-selected and handed
48
+ * to one user in a single call instead of one `assign_from_queue` round trip per ticket.
49
+ *
50
+ * Without `queueId` the endpoint only does `$set: { owner }`. That is insufficient for a QUEUED
51
+ * ticket: it would stay in the queue (still pullable by someone else), carry no dequeue provenance,
52
+ * and leave `ticket_queues.count` drifting upward forever. The queue branch therefore also unsets
53
+ * `queueId`, sets `dequeuedAt`/`dequeuedFrom`, and decrements the queue's count — the same
54
+ * bookkeeping `assign_from_queue` does for a single ticket. A/B/C below pin each of those.
55
+ *
56
+ * D is the check that actually justifies the handler's shape. The tickets model declares
57
+ * `constraints.access: [{ type: 'filter', field: 'owner' }]`, which the DB layer turns into
58
+ * `owner === self OR creator === self` for any non-`All` access. A queued ticket has no owner, and
59
+ * was typically created by someone else, so a non-admin queue manager's `DB.tickets.updateMany`
60
+ * matches NOTHING — the call would return 200 having assigned zero tickets, silently. The handler
61
+ * uses `buildOrganizationWideQueries` to get past that, exactly as `assign_from_queue` does, and
62
+ * re-adds the queue-membership check that those queries bypass. D1 proves the escape hatch works for
63
+ * a legitimate queue member; D2 proves it is still gated, and is the check that fails if someone
64
+ * later drops the membership check while keeping the organization-wide queries.
65
+ *
66
+ * A provisioned non-admin gets `tickets: ASSIGNED_AND_DEFAULT_ACCESS` (read/update = 'Assigned'),
67
+ * which is what makes D meaningful. The D tickets deliberately have NO enduserId, so the 'Assigned'
68
+ * role filter's other branch (enduserId in assignedEnduserIds) cannot match either — the ONLY way
69
+ * they can be assigned is through the organization-wide path.
70
+ *
71
+ * E is the regression guard on the pre-existing no-`queueId` behavior, which must be unchanged.
72
+ *
73
+ * Cross-tenant (F) is covered because `buildOrganizationWideQueries` is bound to the caller's
74
+ * businessId: another tenant's queue is invisible even to an admin, who skips the membership check.
75
+ *
76
+ * `ticket_queues.count` is maintained by asynchronous side effects (increment on ticket create) and
77
+ * the decrement here is deliberately fire-and-forget, so every count read polls.
78
+ */
79
+ // count is maintained by async side effects; give it a moment to settle before reading
80
+ var poll_for = function (get, done, attempts) {
81
+ if (attempts === void 0) { attempts = 20; }
82
+ return __awaiter(void 0, void 0, void 0, function () {
83
+ var value, i;
84
+ return __generator(this, function (_a) {
85
+ switch (_a.label) {
86
+ case 0: return [4 /*yield*/, get()];
87
+ case 1:
88
+ value = _a.sent();
89
+ i = 0;
90
+ _a.label = 2;
91
+ case 2:
92
+ if (!(i < attempts && !done(value))) return [3 /*break*/, 6];
93
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 250); })];
94
+ case 3:
95
+ _a.sent();
96
+ return [4 /*yield*/, get()];
97
+ case 4:
98
+ value = _a.sent();
99
+ _a.label = 5;
100
+ case 5:
101
+ i++;
102
+ return [3 /*break*/, 2];
103
+ case 6: return [2 /*return*/, value];
104
+ }
105
+ });
106
+ });
107
+ };
108
+ var tickets_bulk_assign_from_queue_checks = function () { return __awaiter(void 0, void 0, void 0, function () {
109
+ var checks, record, tenant, otherTenant, sdk_2, 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;
110
+ var _d;
111
+ var _e, _f;
112
+ return __generator(this, function (_g) {
113
+ switch (_g.label) {
114
+ case 0:
115
+ checks = [];
116
+ record = function (label, pass, detail) { return checks.push({ label: label, pass: pass, detail: detail }); };
117
+ _g.label = 1;
118
+ case 1:
119
+ _g.trys.push([1, , 33, 40]);
120
+ /* ================= fixtures ================= */
121
+ // users[1] joins the queue, users[2] deliberately does not (D2)
122
+ ;
123
+ return [4 /*yield*/, Promise.all([
124
+ (0, setup_isolated_1.setup_isolated_tenant)({
125
+ name: "BulkAssignQueue ".concat(RAND()),
126
+ users: [{ isAdmin: true }, { roles: ['Non-Admin'] }, { roles: ['Non-Admin'] }],
127
+ apiKeys: 0,
128
+ }),
129
+ (0, setup_isolated_1.setup_isolated_tenant)({
130
+ name: "BulkAssignQueue other ".concat(RAND()),
131
+ users: [{ isAdmin: true }],
132
+ apiKeys: 0,
133
+ }),
134
+ ])];
135
+ case 2:
136
+ _d = _g.sent(), tenant = _d[0], otherTenant = _d[1];
137
+ sdk_2 = tenant.sdk, sdkNonAdmin = tenant.sdkNonAdmin;
138
+ queueMember = tenant.users[1];
139
+ outsider = tenant.users[2];
140
+ sdkOutsider = new sdk_1.Session({ host: host, authToken: outsider.authToken });
141
+ return [4 /*yield*/, sdkOutsider.refresh_session()];
142
+ case 3:
143
+ _g.sent();
144
+ return [4 /*yield*/, sdk_2.api.users.createOne({
145
+ email: "dontsend-bulk-assign-queue-".concat(RAND(), "@tellescope.com"),
146
+ fname: 'BulkAssign', lname: 'QueueOwner',
147
+ verifiedEmail: true,
148
+ })];
149
+ case 4:
150
+ owner = _g.sent();
151
+ return [4 /*yield*/, sdk_2.api.ticket_queues.createOne({
152
+ title: "Bulk Assign Queue ".concat(RAND()),
153
+ userIds: [owner.id, queueMember.id], // outsider is NOT a member
154
+ })];
155
+ case 5:
156
+ queue_1 = _g.sent();
157
+ return [4 /*yield*/, sdk_2.api.endusers.createOne({ fname: 'BulkAssign', lname: 'QueuePatient' })
158
+ // A/B/C fixtures — one carries an enduser so care team behavior is exercised alongside dequeue
159
+ ];
160
+ case 6:
161
+ enduser = _g.sent();
162
+ return [4 /*yield*/, sdk_2.api.tickets.createOne({ title: 'BAQ 1', enduserId: enduser.id, queueId: queue_1.id })];
163
+ case 7:
164
+ queued1 = _g.sent();
165
+ return [4 /*yield*/, sdk_2.api.tickets.createOne({ title: 'BAQ 2', queueId: queue_1.id })];
166
+ case 8:
167
+ queued2 = _g.sent();
168
+ return [4 /*yield*/, poll_for(function () { return sdk_2.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; })];
169
+ case 9:
170
+ countBefore = _g.sent();
171
+ record('setup: queue count reflects both queued tickets', countBefore === 2, "count=".concat(countBefore, " (want 2); queue=").concat(queue_1.id));
172
+ return [4 /*yield*/, sdk_2.api.tickets.bulk_assign({
173
+ ids: [queued1.id, queued2.id], userId: owner.id, queueId: queue_1.id, addToCareTeam: true,
174
+ })];
175
+ case 10:
176
+ assignResult = _g.sent();
177
+ 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)"));
178
+ return [4 /*yield*/, sdk_2.api.tickets.getOne(queued1.id)];
179
+ case 11:
180
+ after1 = _g.sent();
181
+ 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));
182
+ return [4 /*yield*/, poll_for(function () { return sdk_2.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; })];
183
+ case 12:
184
+ countAfter = _g.sent();
185
+ record('A3: queue count decremented by the number assigned', countAfter === 0, "count=".concat(countAfter, " (want 0, was ").concat(countBefore, ")"));
186
+ _a = record;
187
+ _b = ['A4: addToCareTeam adds the new owner to the ticket enduser\'s care team'];
188
+ return [4 /*yield*/, sdk_2.api.endusers.getOne(enduser.id)];
189
+ case 13:
190
+ _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)]));
191
+ return [4 /*yield*/, sdk_2.api.tickets.bulk_assign({
192
+ ids: [queued1.id, queued2.id], userId: owner.id, queueId: queue_1.id,
193
+ })];
194
+ case 14:
195
+ replayResult = _g.sent();
196
+ 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)"));
197
+ return [4 /*yield*/, sdk_2.api.ticket_queues.getOne(queue_1.id).then(function (q) { var _a; return (_a = q.count) !== null && _a !== void 0 ? _a : 0; })];
198
+ case 15:
199
+ countAfterReplay = _g.sent();
200
+ 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)"));
201
+ return [4 /*yield*/, sdk_2.api.ticket_queues.createOne({
202
+ title: "Bulk Assign Other Queue ".concat(RAND()),
203
+ userIds: [owner.id],
204
+ })];
205
+ case 16:
206
+ otherQueue = _g.sent();
207
+ return [4 /*yield*/, sdk_2.api.tickets.createOne({ title: 'BAQ other queue', queueId: otherQueue.id })];
208
+ case 17:
209
+ inOtherQueue = _g.sent();
210
+ return [4 /*yield*/, sdk_2.api.tickets.bulk_assign({
211
+ ids: [inOtherQueue.id], userId: owner.id, queueId: queue_1.id,
212
+ })];
213
+ case 18:
214
+ wrongQueueResult = _g.sent();
215
+ return [4 /*yield*/, sdk_2.api.tickets.getOne(inOtherQueue.id)];
216
+ case 19:
217
+ stillInOther = _g.sent();
218
+ 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)));
219
+ return [4 /*yield*/, sdk_2.api.tickets.createOne({ title: 'BAQ member 1', queueId: queue_1.id })];
220
+ case 20:
221
+ forMember1 = _g.sent();
222
+ return [4 /*yield*/, sdk_2.api.tickets.createOne({ title: 'BAQ member 2', queueId: queue_1.id })];
223
+ case 21:
224
+ forMember2 = _g.sent();
225
+ return [4 /*yield*/, sdkNonAdmin.api.tickets.bulk_assign({
226
+ ids: [forMember1.id, forMember2.id], userId: owner.id, queueId: queue_1.id,
227
+ }).then(function (r) { return r; }).catch(function (e) { return ({ error: (e === null || e === void 0 ? void 0 : e.message) || String(e) }); })];
228
+ case 22:
229
+ memberResult = _g.sent();
230
+ return [4 /*yield*/, sdk_2.api.tickets.getOne(forMember1.id)];
231
+ case 23:
232
+ memberAfter = _g.sent();
233
+ 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)
234
+ + " \u2014 0 assigned here means the handler fell back to the owner-filtered DB");
235
+ return [4 /*yield*/, sdk_2.api.tickets.createOne({ title: 'BAQ outsider', queueId: queue_1.id })];
236
+ case 24:
237
+ forOutsider = _g.sent();
238
+ return [4 /*yield*/, sdkOutsider.api.tickets.bulk_assign({
239
+ ids: [forOutsider.id], userId: outsider.id, queueId: queue_1.id,
240
+ }).then(function (r) { return ({ resolved: r }); }).catch(function (e) { return ({ rejected: (e === null || e === void 0 ? void 0 : e.message) || String(e) }); })];
241
+ case 25:
242
+ outsiderResult = _g.sent();
243
+ return [4 /*yield*/, sdk_2.api.tickets.getOne(forOutsider.id)];
244
+ case 26:
245
+ outsiderAfter = _g.sent();
246
+ 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)));
247
+ return [4 /*yield*/, sdk_2.api.tickets.createOne({ title: 'BAQ not dequeued', queueId: queue_1.id })];
248
+ case 27:
249
+ stayQueued = _g.sent();
250
+ return [4 /*yield*/, sdk_2.api.tickets.bulk_assign({ ids: [stayQueued.id], userId: owner.id })];
251
+ case 28:
252
+ legacyResult = _g.sent();
253
+ return [4 /*yield*/, sdk_2.api.tickets.getOne(stayQueued.id)];
254
+ case 29:
255
+ stayedAfter = _g.sent();
256
+ record('E1: without queueId the ticket is assigned but NOT dequeued', legacyResult.assigned === 1 && stayedAfter.owner === owner.id
257
+ && 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)));
258
+ return [4 /*yield*/, sdk_2.api.tickets.bulk_assign({ ids: [stayQueued.id], userId: owner.id })];
259
+ case 30:
260
+ repeatResult = _g.sent();
261
+ 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)"));
262
+ return [4 /*yield*/, otherTenant.sdk.api.tickets.bulk_assign({
263
+ ids: [forOutsider.id], userId: otherTenant.users[0].id, queueId: queue_1.id,
264
+ }).then(function (r) { return ({ resolved: r }); }).catch(function (e) { return ({ rejected: (e === null || e === void 0 ? void 0 : e.message) || String(e) }); })];
265
+ case 31:
266
+ foreignResult = _g.sent();
267
+ return [4 /*yield*/, sdk_2.api.tickets.getOne(forOutsider.id)];
268
+ case 32:
269
+ foreignAfter = _g.sent();
270
+ record('F1: another tenant cannot assign tickets out of this tenant\'s queue', (foreignAfter.queueId === queue_1.id && !foreignAfter.owner)
271
+ && (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)));
272
+ return [3 /*break*/, 40];
273
+ case 33:
274
+ _i = 0, _c = [tenant, otherTenant];
275
+ _g.label = 34;
276
+ case 34:
277
+ if (!(_i < _c.length)) return [3 /*break*/, 39];
278
+ t = _c[_i];
279
+ if (!t) return [3 /*break*/, 38];
280
+ _g.label = 35;
281
+ case 35:
282
+ _g.trys.push([35, 37, , 38]);
283
+ return [4 /*yield*/, t.teardown()];
284
+ case 36:
285
+ _g.sent();
286
+ return [3 /*break*/, 38];
287
+ case 37:
288
+ e_1 = _g.sent();
289
+ console.error('teardown failed', e_1);
290
+ return [3 /*break*/, 38];
291
+ case 38:
292
+ _i++;
293
+ return [3 /*break*/, 34];
294
+ case 39: return [7 /*endfinally*/];
295
+ case 40: return [2 /*return*/, checks];
296
+ }
297
+ });
298
+ }); };
299
+ exports.tickets_bulk_assign_from_queue_checks = tickets_bulk_assign_from_queue_checks;
300
+ /** Asserting wrapper, for standalone runs and for sequential use in the suite. */
301
+ var tickets_bulk_assign_from_queue_tests = function () { return __awaiter(void 0, void 0, void 0, function () {
302
+ var _a, _b;
303
+ return __generator(this, function (_c) {
304
+ switch (_c.label) {
305
+ case 0:
306
+ _a = setup_isolated_1.assert_checks;
307
+ _b = ['Tickets bulk assign from queue'];
308
+ return [4 /*yield*/, (0, exports.tickets_bulk_assign_from_queue_checks)()];
309
+ case 1:
310
+ _a.apply(void 0, _b.concat([_c.sent()]));
311
+ return [2 /*return*/];
312
+ }
313
+ });
314
+ }); };
315
+ exports.tickets_bulk_assign_from_queue_tests = tickets_bulk_assign_from_queue_tests;
316
+ // Allow running this test file independently
317
+ if (require.main === module) {
318
+ console.log("\uD83C\uDF10 Using API URL: ".concat(host));
319
+ (0, exports.tickets_bulk_assign_from_queue_tests)()
320
+ .then(function () {
321
+ console.log("✅ Tickets bulk assign from queue test suite completed successfully");
322
+ process.exit(0);
323
+ })
324
+ .catch(function (error) {
325
+ console.error("❌ Tickets bulk assign from queue test suite failed:", error);
326
+ process.exit(1);
327
+ });
328
+ }
329
+ //# sourceMappingURL=tickets_bulk_assign_from_queue.test.js.map