@tellescope/sdk 1.256.10 → 1.256.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/sdk.d.ts +2 -2
- package/lib/cjs/tests/api_tests/generate_suggested_reply.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/suggested-reply-redaction.test.d.ts +26 -0
- package/lib/cjs/tests/api_tests/security/suggested-reply-redaction.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/security/suggested-reply-redaction.test.js +336 -0
- package/lib/cjs/tests/api_tests/security/suggested-reply-redaction.test.js.map +1 -0
- package/lib/cjs/tests/api_tests/stedi_approval.test.d.ts +6 -0
- package/lib/cjs/tests/api_tests/stedi_approval.test.d.ts.map +1 -0
- package/lib/cjs/tests/api_tests/stedi_approval.test.js +205 -0
- package/lib/cjs/tests/api_tests/stedi_approval.test.js.map +1 -0
- package/lib/cjs/tests/tests.d.ts.map +1 -1
- package/lib/cjs/tests/tests.js +236 -203
- package/lib/cjs/tests/tests.js.map +1 -1
- package/lib/esm/enduser.d.ts +0 -1
- package/lib/esm/enduser.d.ts.map +1 -1
- package/lib/esm/sdk.d.ts +4 -5
- package/lib/esm/sdk.d.ts.map +1 -1
- package/lib/esm/session.d.ts +0 -1
- package/lib/esm/session.d.ts.map +1 -1
- package/lib/esm/tests/api_tests/generate_suggested_reply.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/suggested-reply-redaction.test.d.ts +26 -0
- package/lib/esm/tests/api_tests/security/suggested-reply-redaction.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/security/suggested-reply-redaction.test.js +332 -0
- package/lib/esm/tests/api_tests/security/suggested-reply-redaction.test.js.map +1 -0
- package/lib/esm/tests/api_tests/stedi_approval.test.d.ts +6 -0
- package/lib/esm/tests/api_tests/stedi_approval.test.d.ts.map +1 -0
- package/lib/esm/tests/api_tests/stedi_approval.test.js +201 -0
- package/lib/esm/tests/api_tests/stedi_approval.test.js.map +1 -0
- package/lib/esm/tests/tests.d.ts.map +1 -1
- package/lib/esm/tests/tests.js +236 -203
- package/lib/esm/tests/tests.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +10 -10
- package/src/tests/api_tests/security/readonly-enduser-preview.test.ts +301 -0
- package/src/tests/api_tests/security/suggested-reply-redaction.test.ts +232 -0
- package/src/tests/api_tests/stedi_approval.test.ts +167 -0
- package/src/tests/tests.ts +34 -13
- package/test_generated.pdf +0 -0
package/lib/cjs/sdk.d.ts
CHANGED
|
@@ -4193,7 +4193,7 @@ export declare class Session extends SessionManager {
|
|
|
4193
4193
|
iat: number;
|
|
4194
4194
|
exp: number;
|
|
4195
4195
|
requiresMFA?: boolean | undefined;
|
|
4196
|
-
sessionScopes?: ("video-join-link" | "video-start-link" | "public-form" | "appointment-booking" | "ics-download" | "embeddables-token")[] | undefined;
|
|
4196
|
+
sessionScopes?: ("video-join-link" | "video-start-link" | "public-form" | "appointment-booking" | "ics-download" | "embeddables-token" | "portal-preview")[] | undefined;
|
|
4197
4197
|
scopeContext?: import("@tellescope/types-models").SessionScopeContext | undefined;
|
|
4198
4198
|
allowedPaths?: string[] | undefined;
|
|
4199
4199
|
users?: number | undefined;
|
|
@@ -4358,7 +4358,7 @@ export declare class Session extends SessionManager {
|
|
|
4358
4358
|
iat: number;
|
|
4359
4359
|
exp: number;
|
|
4360
4360
|
requiresMFA?: boolean | undefined;
|
|
4361
|
-
sessionScopes?: ("video-join-link" | "video-start-link" | "public-form" | "appointment-booking" | "ics-download" | "embeddables-token")[] | undefined;
|
|
4361
|
+
sessionScopes?: ("video-join-link" | "video-start-link" | "public-form" | "appointment-booking" | "ics-download" | "embeddables-token" | "portal-preview")[] | undefined;
|
|
4362
4362
|
scopeContext?: import("@tellescope/types-models").SessionScopeContext | undefined;
|
|
4363
4363
|
allowedPaths?: string[] | undefined;
|
|
4364
4364
|
users?: number | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate_suggested_reply.test.d.ts","sourceRoot":"","sources":["../../../../src/tests/api_tests/generate_suggested_reply.test.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAiBnC,eAAO,MAAM,8BAA8B;SAAwC,OAAO;iBAAe,OAAO;mBAgR/G,CAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Session } from "../../../sdk";
|
|
2
|
+
/**
|
|
3
|
+
* Security regressions for the generateSuggestedReply journey step.
|
|
4
|
+
*
|
|
5
|
+
* `suggestedReply` holds a staff-facing draft that may be AI-generated and unreviewed, built from the
|
|
6
|
+
* patient's own chart context. Four defects were fixed:
|
|
7
|
+
*
|
|
8
|
+
* 1. emails / sms_messages / group_mms_conversations `suggestedReply` had no `redactions: ['enduser']`
|
|
9
|
+
* (only the sibling `suggestedReplyIsAIGenerated` flag did, so content leaked but the "machine
|
|
10
|
+
* generated" marker was stripped).
|
|
11
|
+
* 2. POST /v1/bulk-actions/read admitted enduser sessions via schema.users.enduserActions.readMany but
|
|
12
|
+
* never checked the REQUESTED model's enduserActions — so any model in the enduser query map was
|
|
13
|
+
* readable, regardless of whether it grants endusers read.
|
|
14
|
+
* 3. chat_rooms.mark_read spread the whole room with no applyRedactions, bypassing the redaction that
|
|
15
|
+
* *was* declared. The patient portal calls mark_read on every chat view.
|
|
16
|
+
* 4. generateSuggestedReply accepted an OUTBOUND message as its target (journeyContext carries outbound
|
|
17
|
+
* ids for the Message Delivery Failure and Message Link Clicked triggers).
|
|
18
|
+
*
|
|
19
|
+
* NO BEDROCK: drafts are written directly with a staff updateOne, and the fix-4 case returns from the
|
|
20
|
+
* inbound guard before the context load / Bedrock call are reached. Deterministic, consumes no AI credits.
|
|
21
|
+
*/
|
|
22
|
+
export declare const suggested_reply_redaction_tests: ({ sdk }: {
|
|
23
|
+
sdk: Session;
|
|
24
|
+
sdkNonAdmin: Session;
|
|
25
|
+
}) => Promise<void>;
|
|
26
|
+
//# sourceMappingURL=suggested-reply-redaction.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suggested-reply-redaction.test.d.ts","sourceRoot":"","sources":["../../../../../src/tests/api_tests/security/suggested-reply-redaction.test.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAStC;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,+BAA+B;SAA2B,OAAO;iBAAe,OAAO;mBAmLnG,CAAA"}
|
|
@@ -0,0 +1,336 @@
|
|
|
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.suggested_reply_redaction_tests = void 0;
|
|
40
|
+
require('source-map-support').install();
|
|
41
|
+
var sdk_1 = require("../../../sdk");
|
|
42
|
+
var schema_1 = require("@tellescope/schema");
|
|
43
|
+
var testing_1 = require("@tellescope/testing");
|
|
44
|
+
var setup_1 = require("../../setup");
|
|
45
|
+
var host = process.env.API_URL || 'http://localhost:8080';
|
|
46
|
+
var DRAFT = 'STAFF-ONLY-DRAFT-do-not-show-the-patient';
|
|
47
|
+
/**
|
|
48
|
+
* Security regressions for the generateSuggestedReply journey step.
|
|
49
|
+
*
|
|
50
|
+
* `suggestedReply` holds a staff-facing draft that may be AI-generated and unreviewed, built from the
|
|
51
|
+
* patient's own chart context. Four defects were fixed:
|
|
52
|
+
*
|
|
53
|
+
* 1. emails / sms_messages / group_mms_conversations `suggestedReply` had no `redactions: ['enduser']`
|
|
54
|
+
* (only the sibling `suggestedReplyIsAIGenerated` flag did, so content leaked but the "machine
|
|
55
|
+
* generated" marker was stripped).
|
|
56
|
+
* 2. POST /v1/bulk-actions/read admitted enduser sessions via schema.users.enduserActions.readMany but
|
|
57
|
+
* never checked the REQUESTED model's enduserActions — so any model in the enduser query map was
|
|
58
|
+
* readable, regardless of whether it grants endusers read.
|
|
59
|
+
* 3. chat_rooms.mark_read spread the whole room with no applyRedactions, bypassing the redaction that
|
|
60
|
+
* *was* declared. The patient portal calls mark_read on every chat view.
|
|
61
|
+
* 4. generateSuggestedReply accepted an OUTBOUND message as its target (journeyContext carries outbound
|
|
62
|
+
* ids for the Message Delivery Failure and Message Link Clicked triggers).
|
|
63
|
+
*
|
|
64
|
+
* NO BEDROCK: drafts are written directly with a staff updateOne, and the fix-4 case returns from the
|
|
65
|
+
* inbound guard before the context load / Bedrock call are reached. Deterministic, consumes no AI credits.
|
|
66
|
+
*/
|
|
67
|
+
var suggested_reply_redaction_tests = function (_a) {
|
|
68
|
+
var sdk = _a.sdk;
|
|
69
|
+
return __awaiter(void 0, void 0, void 0, function () {
|
|
70
|
+
var enduserId, emailId, inboundSMSId, outboundSMSId, roomId, journeyId, enduser, email, inboundSMS, outboundSMS, room, _i, _b, _c, model, id, roomAsAdmin, emailAsAdmin, smsAsAdmin, authToken, asEnduser_1, roomAsEnduser, roomsAsEnduser, foundRoom, bulk, bulkAsStaff, _d, _e, model, field, journey, step, automatedAction, processResult, outboundAfter, _f, _g, _h, _j, _k, _l;
|
|
71
|
+
var _m, _o, _p, _q;
|
|
72
|
+
return __generator(this, function (_r) {
|
|
73
|
+
switch (_r.label) {
|
|
74
|
+
case 0:
|
|
75
|
+
(0, testing_1.log_header)("Suggested Reply redaction + inbound guard");
|
|
76
|
+
_r.label = 1;
|
|
77
|
+
case 1:
|
|
78
|
+
_r.trys.push([1, , 25, 50]);
|
|
79
|
+
return [4 /*yield*/, sdk.api.endusers.createOne({
|
|
80
|
+
fname: 'SRRedaction', lname: 'Test', phone: '+15555550188',
|
|
81
|
+
email: "sr-redaction-".concat(Date.now(), "@example.com"),
|
|
82
|
+
})];
|
|
83
|
+
case 2:
|
|
84
|
+
enduser = _r.sent();
|
|
85
|
+
enduserId = enduser.id;
|
|
86
|
+
return [4 /*yield*/, sdk.api.emails.createOne({
|
|
87
|
+
enduserId: enduserId,
|
|
88
|
+
subject: 'SR seed', textContent: 'seed', logOnly: true, inbound: true,
|
|
89
|
+
})];
|
|
90
|
+
case 3:
|
|
91
|
+
email = _r.sent();
|
|
92
|
+
emailId = email.id;
|
|
93
|
+
return [4 /*yield*/, sdk.api.sms_messages.createOne({
|
|
94
|
+
enduserId: enduserId,
|
|
95
|
+
message: 'inbound seed', logOnly: true, inbound: true,
|
|
96
|
+
})];
|
|
97
|
+
case 4:
|
|
98
|
+
inboundSMS = _r.sent();
|
|
99
|
+
inboundSMSId = inboundSMS.id;
|
|
100
|
+
return [4 /*yield*/, sdk.api.sms_messages.createOne({
|
|
101
|
+
enduserId: enduserId,
|
|
102
|
+
message: 'outbound seed', logOnly: true, inbound: false,
|
|
103
|
+
})];
|
|
104
|
+
case 5:
|
|
105
|
+
outboundSMS = _r.sent();
|
|
106
|
+
outboundSMSId = outboundSMS.id;
|
|
107
|
+
return [4 /*yield*/, sdk.api.chat_rooms.createOne({
|
|
108
|
+
type: 'internal', userIds: [], enduserIds: [enduserId], title: 'SR redaction room',
|
|
109
|
+
})];
|
|
110
|
+
case 6:
|
|
111
|
+
room = _r.sent();
|
|
112
|
+
roomId = room.id;
|
|
113
|
+
_i = 0, _b = [['emails', emailId], ['sms_messages', inboundSMSId], ['chat_rooms', roomId]];
|
|
114
|
+
_r.label = 7;
|
|
115
|
+
case 7:
|
|
116
|
+
if (!(_i < _b.length)) return [3 /*break*/, 10];
|
|
117
|
+
_c = _b[_i], model = _c[0], id = _c[1];
|
|
118
|
+
return [4 /*yield*/, sdk.api[model].updateOne(id, { suggestedReply: DRAFT, suggestedReplyIsAIGenerated: true })];
|
|
119
|
+
case 8:
|
|
120
|
+
_r.sent();
|
|
121
|
+
_r.label = 9;
|
|
122
|
+
case 9:
|
|
123
|
+
_i++;
|
|
124
|
+
return [3 /*break*/, 7];
|
|
125
|
+
case 10: return [4 /*yield*/, sdk.api.chat_rooms.getOne(roomId)];
|
|
126
|
+
case 11:
|
|
127
|
+
roomAsAdmin = _r.sent();
|
|
128
|
+
return [4 /*yield*/, sdk.api.emails.getOne(emailId)];
|
|
129
|
+
case 12:
|
|
130
|
+
emailAsAdmin = _r.sent();
|
|
131
|
+
return [4 /*yield*/, sdk.api.sms_messages.getOne(inboundSMSId)];
|
|
132
|
+
case 13:
|
|
133
|
+
smsAsAdmin = _r.sent();
|
|
134
|
+
(0, testing_1.assert)(roomAsAdmin.suggestedReply === DRAFT && roomAsAdmin.suggestedReplyIsAIGenerated === true
|
|
135
|
+
&& emailAsAdmin.suggestedReply === DRAFT && smsAsAdmin.suggestedReply === DRAFT, 'drafts did not persist / are not visible to staff — the API server is likely running a stale '
|
|
136
|
+
+ '@tellescope/schema, so the redaction assertions below would pass spuriously. Restart it.', 'CONTROL: drafts persist and staff can read them on all three models');
|
|
137
|
+
return [4 /*yield*/, sdk.api.endusers.generate_auth_token({ id: enduserId })];
|
|
138
|
+
case 14:
|
|
139
|
+
authToken = (_r.sent()).authToken;
|
|
140
|
+
asEnduser_1 = new sdk_1.Session({ host: host });
|
|
141
|
+
asEnduser_1.setAuthToken(authToken);
|
|
142
|
+
/*************** Fix 3: mark_read must redact ***************/
|
|
143
|
+
return [4 /*yield*/, (0, testing_1.async_test)('chat_rooms.mark_read does not leak suggestedReply to the patient', function () { return asEnduser_1.api.chat_rooms.mark_read({ id: roomId }); }, {
|
|
144
|
+
onResult: function (r) {
|
|
145
|
+
var _a, _b;
|
|
146
|
+
var updated = r === null || r === void 0 ? void 0 : r.updated;
|
|
147
|
+
if (!updated)
|
|
148
|
+
return false;
|
|
149
|
+
// unreadCount proves the handler still did its job rather than erroring out
|
|
150
|
+
var stillWorks = ((_b = (_a = updated.infoForUser) === null || _a === void 0 ? void 0 : _a[enduserId]) === null || _b === void 0 ? void 0 : _b.unreadCount) === 0;
|
|
151
|
+
return stillWorks
|
|
152
|
+
&& updated.suggestedReply === undefined
|
|
153
|
+
&& updated.suggestedReplyIsAIGenerated === undefined;
|
|
154
|
+
},
|
|
155
|
+
})
|
|
156
|
+
/*************** Fix 1: plain reads on chat_rooms must redact ***************/
|
|
157
|
+
];
|
|
158
|
+
case 15:
|
|
159
|
+
/*************** Fix 3: mark_read must redact ***************/
|
|
160
|
+
_r.sent();
|
|
161
|
+
return [4 /*yield*/, asEnduser_1.api.chat_rooms.getOne(roomId)];
|
|
162
|
+
case 16:
|
|
163
|
+
roomAsEnduser = _r.sent();
|
|
164
|
+
(0, testing_1.assert)(roomAsEnduser.suggestedReply === undefined && roomAsEnduser.suggestedReplyIsAIGenerated === undefined, "enduser read chat_rooms.suggestedReply: ".concat(roomAsEnduser.suggestedReply), 'chat_rooms.getOne redacts suggestedReply for endusers');
|
|
165
|
+
return [4 /*yield*/, asEnduser_1.api.chat_rooms.getSome({ limit: 50 })];
|
|
166
|
+
case 17:
|
|
167
|
+
roomsAsEnduser = _r.sent();
|
|
168
|
+
foundRoom = roomsAsEnduser.find(function (r) { return r.id === roomId; });
|
|
169
|
+
(0, testing_1.assert)(!!foundRoom && foundRoom.suggestedReply === undefined, 'enduser readMany leaked chat_rooms.suggestedReply', 'chat_rooms.getSome redacts suggestedReply for endusers');
|
|
170
|
+
return [4 /*yield*/, asEnduser_1.bulk_load({
|
|
171
|
+
load: [{ model: 'emails' }, { model: 'sms_messages' }, { model: 'chat_rooms' }],
|
|
172
|
+
})];
|
|
173
|
+
case 18:
|
|
174
|
+
bulk = _r.sent();
|
|
175
|
+
(0, testing_1.assert)(bulk.results[0] === null && bulk.results[1] === null, "bulk_load exposed models without enduser readMany: emails=".concat(JSON.stringify(bulk.results[0]), " sms=").concat(JSON.stringify(bulk.results[1])), 'bulk_load returns null for models with no enduser readMany (emails, sms_messages)');
|
|
176
|
+
(0, testing_1.assert)(!!((_m = bulk.results[2]) === null || _m === void 0 ? void 0 : _m.records) && !bulk.results[2].records.some(function (r) { return r.suggestedReply !== undefined; }), 'bulk_load leaked chat_rooms.suggestedReply', 'bulk_load still returns chat_rooms for endusers, with suggestedReply redacted');
|
|
177
|
+
return [4 /*yield*/, sdk.bulk_load({ load: [{ model: 'emails' }] })];
|
|
178
|
+
case 19:
|
|
179
|
+
bulkAsStaff = _r.sent();
|
|
180
|
+
(0, testing_1.assert)(!!((_o = bulkAsStaff.results[0]) === null || _o === void 0 ? void 0 : _o.records) && bulkAsStaff.results[0].records.length > 0, 'bulk_load stopped returning emails for staff — the enduser gate leaked into staff sessions', 'bulk_load unchanged for staff sessions');
|
|
181
|
+
/*************** Fix 1 (defense-in-depth): redaction is declared in schema ***************/
|
|
182
|
+
// Once the bulk-read gate is in place, emails/sms_messages are unreachable by enduser sessions, so
|
|
183
|
+
// their redaction is no longer observable end-to-end. Assert it structurally so it survives the
|
|
184
|
+
// gate ever being relaxed.
|
|
185
|
+
for (_d = 0, _e = ['emails', 'sms_messages', 'group_mms_conversations']; _d < _e.length; _d++) {
|
|
186
|
+
model = _e[_d];
|
|
187
|
+
field = schema_1.schema[model].fields.suggestedReply;
|
|
188
|
+
(0, testing_1.assert)(!!((_p = field === null || field === void 0 ? void 0 : field.redactions) === null || _p === void 0 ? void 0 : _p.includes('enduser')), "".concat(model, ".suggestedReply is missing redactions: ['enduser']"), "".concat(model, ".suggestedReply declares an enduser redaction"));
|
|
189
|
+
}
|
|
190
|
+
return [4 /*yield*/, sdk.api.journeys.createOne({ title: "SR Inbound Guard ".concat(Date.now()) })];
|
|
191
|
+
case 20:
|
|
192
|
+
journey = _r.sent();
|
|
193
|
+
journeyId = journey.id;
|
|
194
|
+
return [4 /*yield*/, sdk.api.automation_steps.createOne({
|
|
195
|
+
journeyId: journeyId,
|
|
196
|
+
events: [{ type: 'onJourneyStart', info: {} }],
|
|
197
|
+
action: { type: 'generateSuggestedReply', info: {} },
|
|
198
|
+
})];
|
|
199
|
+
case 21:
|
|
200
|
+
step = _r.sent();
|
|
201
|
+
return [4 /*yield*/, sdk.api.automated_actions.createOne({
|
|
202
|
+
enduserId: enduserId,
|
|
203
|
+
journeyId: journeyId,
|
|
204
|
+
automationStepId: step.id,
|
|
205
|
+
event: { type: 'onJourneyStart', info: {} },
|
|
206
|
+
action: { type: 'generateSuggestedReply', info: {} },
|
|
207
|
+
status: 'active',
|
|
208
|
+
processAfter: Date.now(),
|
|
209
|
+
journeyContext: { smsId: outboundSMSId },
|
|
210
|
+
})
|
|
211
|
+
// returns before loadAISummaryContextFromDB / Bedrock.sendMessage — no AI credits consumed
|
|
212
|
+
];
|
|
213
|
+
case 22:
|
|
214
|
+
automatedAction = _r.sent();
|
|
215
|
+
return [4 /*yield*/, sdk.api.automated_actions.process({ automatedActionId: automatedAction.id })];
|
|
216
|
+
case 23:
|
|
217
|
+
processResult = _r.sent();
|
|
218
|
+
return [4 /*yield*/, sdk.api.sms_messages.getOne(outboundSMSId)];
|
|
219
|
+
case 24:
|
|
220
|
+
outboundAfter = _r.sent();
|
|
221
|
+
(0, testing_1.assert)(!outboundAfter.suggestedReply, "a draft was written onto an OUTBOUND message: ".concat(outboundAfter.suggestedReply), 'outbound target is rejected without writing suggestedReply');
|
|
222
|
+
// success:false is what makes the worker throw — which marks the automated_action 'error', fires
|
|
223
|
+
// any onError branch, honors continueOnError, and logs the background_error. Returning
|
|
224
|
+
// success:true here would report a clean run and silently skip all of that. Asserted on the
|
|
225
|
+
// response rather than on background_errors because this calls the API directly, so the worker
|
|
226
|
+
// (which writes that row) is not in the loop.
|
|
227
|
+
(0, testing_1.assert)(processResult.success === false
|
|
228
|
+
&& /outbound message, not an incoming one/i.test((_q = processResult.error) !== null && _q !== void 0 ? _q : ''), "expected success:false with an outbound-specific error, got: ".concat(JSON.stringify(processResult)), 'outbound rejection returns success:false so journey error handling engages');
|
|
229
|
+
return [3 /*break*/, 50];
|
|
230
|
+
case 25:
|
|
231
|
+
if (!journeyId) return [3 /*break*/, 29];
|
|
232
|
+
_r.label = 26;
|
|
233
|
+
case 26:
|
|
234
|
+
_r.trys.push([26, 28, , 29]);
|
|
235
|
+
return [4 /*yield*/, sdk.api.journeys.deleteOne(journeyId)];
|
|
236
|
+
case 27:
|
|
237
|
+
_r.sent();
|
|
238
|
+
return [3 /*break*/, 29];
|
|
239
|
+
case 28:
|
|
240
|
+
_f = _r.sent();
|
|
241
|
+
return [3 /*break*/, 29];
|
|
242
|
+
case 29:
|
|
243
|
+
if (!roomId) return [3 /*break*/, 33];
|
|
244
|
+
_r.label = 30;
|
|
245
|
+
case 30:
|
|
246
|
+
_r.trys.push([30, 32, , 33]);
|
|
247
|
+
return [4 /*yield*/, sdk.api.chat_rooms.deleteOne(roomId)];
|
|
248
|
+
case 31:
|
|
249
|
+
_r.sent();
|
|
250
|
+
return [3 /*break*/, 33];
|
|
251
|
+
case 32:
|
|
252
|
+
_g = _r.sent();
|
|
253
|
+
return [3 /*break*/, 33];
|
|
254
|
+
case 33:
|
|
255
|
+
if (!emailId) return [3 /*break*/, 37];
|
|
256
|
+
_r.label = 34;
|
|
257
|
+
case 34:
|
|
258
|
+
_r.trys.push([34, 36, , 37]);
|
|
259
|
+
return [4 /*yield*/, sdk.api.emails.deleteOne(emailId)];
|
|
260
|
+
case 35:
|
|
261
|
+
_r.sent();
|
|
262
|
+
return [3 /*break*/, 37];
|
|
263
|
+
case 36:
|
|
264
|
+
_h = _r.sent();
|
|
265
|
+
return [3 /*break*/, 37];
|
|
266
|
+
case 37:
|
|
267
|
+
if (!inboundSMSId) return [3 /*break*/, 41];
|
|
268
|
+
_r.label = 38;
|
|
269
|
+
case 38:
|
|
270
|
+
_r.trys.push([38, 40, , 41]);
|
|
271
|
+
return [4 /*yield*/, sdk.api.sms_messages.deleteOne(inboundSMSId)];
|
|
272
|
+
case 39:
|
|
273
|
+
_r.sent();
|
|
274
|
+
return [3 /*break*/, 41];
|
|
275
|
+
case 40:
|
|
276
|
+
_j = _r.sent();
|
|
277
|
+
return [3 /*break*/, 41];
|
|
278
|
+
case 41:
|
|
279
|
+
if (!outboundSMSId) return [3 /*break*/, 45];
|
|
280
|
+
_r.label = 42;
|
|
281
|
+
case 42:
|
|
282
|
+
_r.trys.push([42, 44, , 45]);
|
|
283
|
+
return [4 /*yield*/, sdk.api.sms_messages.deleteOne(outboundSMSId)];
|
|
284
|
+
case 43:
|
|
285
|
+
_r.sent();
|
|
286
|
+
return [3 /*break*/, 45];
|
|
287
|
+
case 44:
|
|
288
|
+
_k = _r.sent();
|
|
289
|
+
return [3 /*break*/, 45];
|
|
290
|
+
case 45:
|
|
291
|
+
if (!enduserId) return [3 /*break*/, 49];
|
|
292
|
+
_r.label = 46;
|
|
293
|
+
case 46:
|
|
294
|
+
_r.trys.push([46, 48, , 49]);
|
|
295
|
+
return [4 /*yield*/, sdk.api.endusers.deleteOne(enduserId)];
|
|
296
|
+
case 47:
|
|
297
|
+
_r.sent();
|
|
298
|
+
return [3 /*break*/, 49];
|
|
299
|
+
case 48:
|
|
300
|
+
_l = _r.sent();
|
|
301
|
+
return [3 /*break*/, 49];
|
|
302
|
+
case 49: return [7 /*endfinally*/];
|
|
303
|
+
case 50: return [2 /*return*/];
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
};
|
|
308
|
+
exports.suggested_reply_redaction_tests = suggested_reply_redaction_tests;
|
|
309
|
+
if (require.main === module) {
|
|
310
|
+
console.log("\uD83C\uDF10 Using API URL: ".concat(host));
|
|
311
|
+
var sdk_2 = new sdk_1.Session({ host: host });
|
|
312
|
+
var sdkNonAdmin_1 = new sdk_1.Session({ host: host });
|
|
313
|
+
var runTests = function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
314
|
+
return __generator(this, function (_a) {
|
|
315
|
+
switch (_a.label) {
|
|
316
|
+
case 0: return [4 /*yield*/, (0, setup_1.setup_tests)(sdk_2, sdkNonAdmin_1)];
|
|
317
|
+
case 1:
|
|
318
|
+
_a.sent();
|
|
319
|
+
return [4 /*yield*/, (0, exports.suggested_reply_redaction_tests)({ sdk: sdk_2, sdkNonAdmin: sdkNonAdmin_1 })];
|
|
320
|
+
case 2:
|
|
321
|
+
_a.sent();
|
|
322
|
+
return [2 /*return*/];
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
}); };
|
|
326
|
+
runTests()
|
|
327
|
+
.then(function () {
|
|
328
|
+
console.log("✅ Suggested Reply redaction test suite completed successfully");
|
|
329
|
+
process.exit(0);
|
|
330
|
+
})
|
|
331
|
+
.catch(function (error) {
|
|
332
|
+
console.error("❌ Suggested Reply redaction test suite failed:", error);
|
|
333
|
+
process.exit(1);
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
//# sourceMappingURL=suggested-reply-redaction.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"suggested-reply-redaction.test.js","sourceRoot":"","sources":["../../../../../src/tests/api_tests/security/suggested-reply-redaction.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stedi_approval.test.d.ts","sourceRoot":"","sources":["../../../../src/tests/api_tests/stedi_approval.test.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAkB,MAAM,WAAW,CAAA;AAenD,eAAO,MAAM,oBAAoB;SAAwC,OAAO;iBAAe,OAAO;mBA+HrG,CAAA"}
|
|
@@ -0,0 +1,205 @@
|
|
|
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.stedi_approval_tests = void 0;
|
|
40
|
+
require('source-map-support').install();
|
|
41
|
+
var sdk_1 = require("../../sdk");
|
|
42
|
+
var testing_1 = require("@tellescope/testing");
|
|
43
|
+
var constants_1 = require("@tellescope/constants");
|
|
44
|
+
var setup_1 = require("../setup");
|
|
45
|
+
var host = process.env.API_URL || 'http://localhost:8080';
|
|
46
|
+
// Stedi runs on Tellescope's own platform credentials (zz_secrets) and each eligibility check costs
|
|
47
|
+
// Tellescope $0.10, so an organization must be approved by a Tellescope super admin
|
|
48
|
+
// (Organization.stediAllowed) before any Stedi call is made on its behalf.
|
|
49
|
+
//
|
|
50
|
+
// Every assertion here is a REJECTION assertion, so nothing in this file ever reaches Stedi.
|
|
51
|
+
var stedi_approval_tests = function (_a) {
|
|
52
|
+
var sdk = _a.sdk, sdkNonAdmin = _a.sdkNonAdmin;
|
|
53
|
+
return __awaiter(void 0, void 0, void 0, function () {
|
|
54
|
+
var orgId, businessId, organization, enduser, enduserSdk_1, messageIsGenericForEnduser;
|
|
55
|
+
return __generator(this, function (_b) {
|
|
56
|
+
switch (_b.label) {
|
|
57
|
+
case 0:
|
|
58
|
+
(0, testing_1.log_header)("Stedi Pre-Approval (stediAllowed) Tests");
|
|
59
|
+
orgId = sdk.userInfo.businessId;
|
|
60
|
+
businessId = orgId;
|
|
61
|
+
// A. Super-admin write gate.
|
|
62
|
+
//
|
|
63
|
+
// `isa` is only minted by GET /switch-tenant, so this plainly-authenticated admin session never
|
|
64
|
+
// has it — even when TEST_EMAIL happens to be in the API's SUPER_ADMINS allowlist.
|
|
65
|
+
(0, testing_1.log_header)("A. stediAllowed is writable by super admins only");
|
|
66
|
+
return [4 /*yield*/, (0, testing_1.async_test)("org admin cannot enable stediAllowed for their own organization", function () { return sdk.api.organizations.updateOne(orgId, { stediAllowed: true }); }, { shouldError: true, onError: function (e) { return /not allowed/i.test(e.message); } })
|
|
67
|
+
// Regression test for the truthiness hole in the shared super-admin relationship constraint:
|
|
68
|
+
// it used to test `options.updates?.stediAllowed` (falsy for `false`), so an org admin could
|
|
69
|
+
// clear super-admin-owned flags even though they could never set them.
|
|
70
|
+
];
|
|
71
|
+
case 1:
|
|
72
|
+
_b.sent();
|
|
73
|
+
// Regression test for the truthiness hole in the shared super-admin relationship constraint:
|
|
74
|
+
// it used to test `options.updates?.stediAllowed` (falsy for `false`), so an org admin could
|
|
75
|
+
// clear super-admin-owned flags even though they could never set them.
|
|
76
|
+
return [4 /*yield*/, (0, testing_1.async_test)("org admin cannot disable stediAllowed either (guard is key-presence, not truthiness)", function () { return sdk.api.organizations.updateOne(orgId, { stediAllowed: false }); }, { shouldError: true, onError: function (e) { return /not allowed/i.test(e.message); } })
|
|
77
|
+
// Same bug, pre-existing field — proves the fix wasn't scoped to the new flag only.
|
|
78
|
+
];
|
|
79
|
+
case 2:
|
|
80
|
+
// Regression test for the truthiness hole in the shared super-admin relationship constraint:
|
|
81
|
+
// it used to test `options.updates?.stediAllowed` (falsy for `false`), so an org admin could
|
|
82
|
+
// clear super-admin-owned flags even though they could never set them.
|
|
83
|
+
_b.sent();
|
|
84
|
+
// Same bug, pre-existing field — proves the fix wasn't scoped to the new flag only.
|
|
85
|
+
return [4 /*yield*/, (0, testing_1.async_test)("org admin cannot disable bedrockAIAllowed", function () { return sdk.api.organizations.updateOne(orgId, { bedrockAIAllowed: false }); }, { shouldError: true, onError: function (e) { return /not allowed/i.test(e.message); } })
|
|
86
|
+
// ...and that it covers the whole gated list, not just the two booleans.
|
|
87
|
+
];
|
|
88
|
+
case 3:
|
|
89
|
+
// Same bug, pre-existing field — proves the fix wasn't scoped to the new flag only.
|
|
90
|
+
_b.sent();
|
|
91
|
+
// ...and that it covers the whole gated list, not just the two booleans.
|
|
92
|
+
return [4 /*yield*/, (0, testing_1.async_test)("org admin cannot disable allowCreateSuborganizations", function () { return sdk.api.organizations.updateOne(orgId, { allowCreateSuborganizations: false }); }, { shouldError: true, onError: function (e) { return /not allowed/i.test(e.message); } })
|
|
93
|
+
// B. An unapproved organization is rejected at every Stedi entry point.
|
|
94
|
+
];
|
|
95
|
+
case 4:
|
|
96
|
+
// ...and that it covers the whole gated list, not just the two booleans.
|
|
97
|
+
_b.sent();
|
|
98
|
+
// B. An unapproved organization is rejected at every Stedi entry point.
|
|
99
|
+
(0, testing_1.log_header)("B. Unapproved organizations cannot reach Stedi");
|
|
100
|
+
return [4 /*yield*/, sdk.api.organizations.getOne(orgId)];
|
|
101
|
+
case 5:
|
|
102
|
+
organization = _b.sent();
|
|
103
|
+
if (organization.stediAllowed) {
|
|
104
|
+
console.log("⚠️ SKIPPING section B: the test organization already has stediAllowed=true "
|
|
105
|
+
+ "(hand-toggled in this dev database). Turn it off in the super admin console and re-run — "
|
|
106
|
+
+ "these assertions would otherwise report a false pass.");
|
|
107
|
+
return [2 /*return*/];
|
|
108
|
+
}
|
|
109
|
+
return [4 /*yield*/, sdk.api.endusers.createOne({
|
|
110
|
+
email: "stedi-approval-".concat(Date.now(), "@test.tellescope.com"),
|
|
111
|
+
fname: 'Stedi',
|
|
112
|
+
lname: 'Approval',
|
|
113
|
+
})];
|
|
114
|
+
case 6:
|
|
115
|
+
enduser = _b.sent();
|
|
116
|
+
_b.label = 7;
|
|
117
|
+
case 7:
|
|
118
|
+
_b.trys.push([7, , 14, 16]);
|
|
119
|
+
return [4 /*yield*/, (0, testing_1.async_test)("staff: Stedi payer search (proxy_read) is rejected", function () { return sdk.api.integrations.proxy_read({ integration: constants_1.STEDI_TITLE, type: 'payers', query: 'aetna' }); }, { shouldError: true, onError: function (e) { return /not enabled for this organization/i.test(e.message); } })];
|
|
120
|
+
case 8:
|
|
121
|
+
_b.sent();
|
|
122
|
+
return [4 /*yield*/, (0, testing_1.async_test)("staff: Stedi payer importer (load_payers) is rejected", function () { return sdk.api.integrations.load_payers({ integration: constants_1.STEDI_TITLE, query: 'aetna' }); }, { shouldError: true, onError: function (e) { return /not enabled for this organization/i.test(e.message); } })];
|
|
123
|
+
case 9:
|
|
124
|
+
_b.sent();
|
|
125
|
+
return [4 /*yield*/, (0, testing_1.async_test)("staff: Stedi eligibility check is rejected on approval, before the credit checks", function () { return sdk.api.integrations.proxy_read({
|
|
126
|
+
integration: constants_1.STEDI_TITLE,
|
|
127
|
+
type: 'stedi-eligibility',
|
|
128
|
+
id: enduser.id,
|
|
129
|
+
query: JSON.stringify({ serviceCode: '30', npi: '1234567890' }),
|
|
130
|
+
}); }, {
|
|
131
|
+
shouldError: true,
|
|
132
|
+
// The absence of /credit/ is the real assertion: it proves the approval gate fires ahead of
|
|
133
|
+
// the credit gate, so an unapproved org is never told to go buy credits.
|
|
134
|
+
onError: function (e) { return (/not enabled for this organization/i.test(e.message) && !/credit/i.test(e.message)); },
|
|
135
|
+
})
|
|
136
|
+
// Enduser sessions reach both branches for real: StediEligibilityInput auto-runs the check, and
|
|
137
|
+
// InsuranceInput search-on-types against the payer branch. Approval/billing state must not leak
|
|
138
|
+
// to patients.
|
|
139
|
+
];
|
|
140
|
+
case 10:
|
|
141
|
+
_b.sent();
|
|
142
|
+
enduserSdk_1 = new sdk_1.EnduserSession({ host: host, businessId: businessId });
|
|
143
|
+
return [4 /*yield*/, (0, setup_1.authenticate_enduser_via_token)(sdk, enduserSdk_1, { id: enduser.id })
|
|
144
|
+
// Exact-match on the generic copy: no "Stedi", no "credits", no "contact sales", no mention that
|
|
145
|
+
// approval is the reason. Note this is deliberately the SAME string the enduser-facing credit
|
|
146
|
+
// rejection uses, so it cannot prove which gate fired — the staff assertion above is what pins
|
|
147
|
+
// down the approval-before-credits ordering.
|
|
148
|
+
];
|
|
149
|
+
case 11:
|
|
150
|
+
_b.sent();
|
|
151
|
+
messageIsGenericForEnduser = function (e) { return (e.message === 'Eligibility check is currently unavailable'); };
|
|
152
|
+
return [4 /*yield*/, (0, testing_1.async_test)("enduser: eligibility check rejected with a generic message (no approval/billing leak)", function () { return enduserSdk_1.api.integrations.proxy_read({
|
|
153
|
+
integration: constants_1.STEDI_TITLE,
|
|
154
|
+
type: 'stedi-eligibility',
|
|
155
|
+
query: JSON.stringify({ serviceCode: '30', npi: '1234567890' }),
|
|
156
|
+
}); }, { shouldError: true, onError: messageIsGenericForEnduser })];
|
|
157
|
+
case 12:
|
|
158
|
+
_b.sent();
|
|
159
|
+
return [4 /*yield*/, (0, testing_1.async_test)("enduser: payer search rejected with a generic message (no approval/billing leak)", function () { return enduserSdk_1.api.integrations.proxy_read({
|
|
160
|
+
integration: constants_1.STEDI_TITLE,
|
|
161
|
+
type: 'payers',
|
|
162
|
+
query: 'aetna',
|
|
163
|
+
}); }, { shouldError: true, onError: messageIsGenericForEnduser })];
|
|
164
|
+
case 13:
|
|
165
|
+
_b.sent();
|
|
166
|
+
return [3 /*break*/, 16];
|
|
167
|
+
case 14: return [4 /*yield*/, sdk.api.endusers.deleteOne(enduser.id).catch(console.error)];
|
|
168
|
+
case 15:
|
|
169
|
+
_b.sent();
|
|
170
|
+
return [7 /*endfinally*/];
|
|
171
|
+
case 16: return [2 /*return*/];
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
};
|
|
176
|
+
exports.stedi_approval_tests = stedi_approval_tests;
|
|
177
|
+
// Allow running this test file independently
|
|
178
|
+
if (require.main === module) {
|
|
179
|
+
console.log("Using API URL: ".concat(host));
|
|
180
|
+
var sdk_2 = new sdk_1.Session({ host: host });
|
|
181
|
+
var sdkNonAdmin_1 = new sdk_1.Session({ host: host });
|
|
182
|
+
var runTests = function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
183
|
+
return __generator(this, function (_a) {
|
|
184
|
+
switch (_a.label) {
|
|
185
|
+
case 0: return [4 /*yield*/, (0, setup_1.setup_tests)(sdk_2, sdkNonAdmin_1)];
|
|
186
|
+
case 1:
|
|
187
|
+
_a.sent();
|
|
188
|
+
return [4 /*yield*/, (0, exports.stedi_approval_tests)({ sdk: sdk_2, sdkNonAdmin: sdkNonAdmin_1 })];
|
|
189
|
+
case 2:
|
|
190
|
+
_a.sent();
|
|
191
|
+
return [2 /*return*/];
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}); };
|
|
195
|
+
runTests()
|
|
196
|
+
.then(function () {
|
|
197
|
+
console.log("✅ Stedi pre-approval tests completed successfully");
|
|
198
|
+
process.exit(0);
|
|
199
|
+
})
|
|
200
|
+
.catch(function (error) {
|
|
201
|
+
console.error("❌ Stedi pre-approval tests failed:", error);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=stedi_approval.test.js.map
|