@oneuptime/common 12.0.34 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/GlobalConfig.ts +12 -12
- package/Models/DatabaseModels/InventoryItem.ts +44 -1
- package/Models/DatabaseModels/NetworkDevice.ts +126 -0
- package/Server/EnvironmentConfig.ts +47 -16
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Infrastructure/Redis.ts +8 -8
- package/Server/Infrastructure/Status.ts +10 -4
- package/Server/Services/DatabaseService.ts +65 -13
- package/Server/Services/InventoryItemService.ts +31 -1
- package/Server/Services/MonitorTemplateService.ts +140 -19
- package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +349 -41
- package/Server/Services/NetworkDeviceDiscoveryScanService.ts +87 -2
- package/Server/Services/NetworkDeviceService.ts +69 -0
- package/Server/Services/UserNotificationRuleService.ts +76 -25
- package/Server/Types/Database/Permissions/ReadPermission.ts +45 -5
- package/Server/Types/Database/QueryUtil.ts +56 -0
- package/Server/Types/Markdown.ts +93 -1
- package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +82 -17
- package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +10 -1
- package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +2 -2
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +82 -35
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +31 -5
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +202 -37
- package/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.ts +82 -9
- package/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.ts +16 -7
- package/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.ts +29 -17
- package/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.ts +107 -48
- package/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.ts +158 -0
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +27 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +11 -1
- package/Server/Utils/Telemetry/TelemetryEntity.ts +83 -9
- package/Tests/App/Dashboard/AddNeighborToMonitoringModal.test.tsx +32 -0
- package/Tests/App/Dashboard/AdoptedDeviceDrawer.test.tsx +374 -0
- package/Tests/App/Dashboard/DashboardLabelVariables.test.tsx +580 -0
- package/Tests/App/Dashboard/DashboardMonitorLabelVariable.test.tsx +438 -0
- package/Tests/App/Dashboard/DashboardVariableAllContract.test.tsx +5 -2
- package/Tests/App/Dashboard/DeviceAttachmentCard.test.tsx +273 -0
- package/Tests/App/Dashboard/DiscoveryReviewInventoryRefresh.test.tsx +505 -0
- package/Tests/App/Dashboard/EntityDetailPanel.test.tsx +163 -0
- package/Tests/App/Dashboard/InfrastructureExplorer.test.tsx +401 -0
- package/Tests/App/Dashboard/InfrastructureGraph.test.tsx +232 -0
- package/Tests/App/Dashboard/InventoryItems.test.tsx +201 -0
- package/Tests/App/Dashboard/InventoryTypeAndStatusFacets.test.tsx +798 -0
- package/Tests/App/Dashboard/NetworkTopologyLiveView.test.tsx +320 -0
- package/Tests/App/Dashboard/NetworkTopologyToolbar.test.tsx +275 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedEmptyState.test.tsx +975 -0
- package/Tests/App/Dashboard/OnCallCalendarFeedPlanGate.test.tsx +63 -0
- package/Tests/App/Dashboard/ServiceMapGraph.test.tsx +626 -0
- package/Tests/App/Dashboard/TopologyDataLoading.test.tsx +348 -0
- package/Tests/App/Dashboard/TopologyPageNavigation.test.tsx +331 -0
- package/Tests/App/Dashboard/UserSettingsEmailPreferences.test.tsx +603 -0
- package/Tests/App/Dashboard/UserSettingsNotificationSettings.test.tsx +324 -0
- package/Tests/App/PublicDashboard/DashboardVariableSelector.test.tsx +13 -4
- package/Tests/App/StatusPage/StatusPageOidcOrigin.test.tsx +223 -0
- package/Tests/Models/InventoryItemStatus.test.ts +147 -0
- package/Tests/Models/NetworkDeviceMacAddressColumn.test.ts +294 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +129 -0
- package/Tests/Server/EnvironmentConfigValkey.test.ts +227 -0
- package/Tests/Server/Infrastructure/Postgres/AddMacAddressToNetworkDeviceMigration.test.ts +365 -0
- package/Tests/Server/Infrastructure/Postgres/CustomFieldValueMappingMigration.test.ts +62 -23
- package/Tests/Server/Infrastructure/ValkeyDeploymentConfig.test.ts +265 -0
- package/Tests/Server/Middleware/HttpMetricsMiddleware.test.ts +382 -0
- package/Tests/Server/Middleware/SCIMAuthorization.test.ts +525 -0
- package/Tests/Server/Services/DatabaseServiceAggregateBy.test.ts +291 -56
- package/Tests/Server/Services/DatabaseServiceSortTiebreaker.test.ts +151 -0
- package/Tests/Server/Services/InventoryItemManualCreate.test.ts +105 -0
- package/Tests/Server/Services/InventoryItemPromotionGate.test.ts +178 -0
- package/Tests/Server/Services/MonitorTemplateServiceCustomFieldSync.test.ts +502 -0
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +1133 -81
- package/Tests/Server/Services/NetworkDeviceDiscoveryScanRegistration.test.ts +520 -0
- package/Tests/Server/Services/NetworkDeviceMacAddressNormalization.test.ts +572 -0
- package/Tests/Server/Types/Database/InventoryStatusPostgres.test.ts +368 -0
- package/Tests/Server/Types/Database/Permissions/ReadBlockPermission.test.ts +261 -14
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroups.test.ts +169 -0
- package/Tests/Server/Types/Database/QueryUtilIncludesAnyOfGroupsPostgres.test.ts +297 -0
- package/Tests/Server/Types/Markdown.test.ts +194 -1
- package/Tests/Server/Types/Workflow/Components/IncomingWebhookUtils.test.ts +279 -0
- package/Tests/Server/Types/Workflow/Components/TextToJson.test.ts +212 -0
- package/Tests/Server/Utils/CustomField/CustomFieldMappingRegistry.test.ts +436 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +214 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupFlushRunnerBehaviour.test.ts +1 -1
- package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +10 -3
- package/Tests/Server/Utils/EventLoop.test.ts +359 -0
- package/Tests/Server/Utils/JsonWebToken.test.ts +30 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +242 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteriaAggregation.test.ts +13 -7
- package/Tests/Server/Utils/Monitor/Criteria/DatabaseMonitorCriteria.test.ts +29 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.test.ts +30 -6
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +246 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEmailEvalLogAgreement.test.ts +280 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +224 -10
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +570 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaExpectationBuilderUnits.test.ts +30 -5
- package/Tests/Server/Utils/Monitor/MonitorCriteriaMessageBuilderUnits.test.ts +68 -21
- package/Tests/Server/Utils/Monitor/MonitorCriteriaObservationBuilderUnits.test.ts +36 -19
- package/Tests/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.test.ts +573 -0
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +317 -0
- package/Tests/Server/Utils/Telemetry/ContextSpanProcessor.test.ts +281 -0
- package/Tests/Server/Utils/Telemetry/InventoryDuplicateIdentity.test.ts +316 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +111 -8
- package/Tests/Types/Database/IncludesAnyOfGroups.test.ts +106 -0
- package/Tests/Types/JSONFunctions.test.ts +40 -0
- package/Tests/Types/NetworkAutomation/AutoImportRunMerge.test.ts +235 -0
- package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +66 -0
- package/Tests/Types/NetworkAutomation/RuleRunResultDescribe.test.ts +309 -1
- package/Tests/Types/Telemetry/InventoryLiveness.test.ts +99 -0
- package/Tests/UI/Components/CustomFields/CustomFieldsDetail.test.tsx +160 -0
- package/Tests/UI/ReactRouterSingletonBuild.test.ts +133 -0
- package/Tests/UI/Rum/ReplayHeader.test.tsx +10 -2
- package/Tests/UI/Rum/ReplayPlayerChrome.test.tsx +514 -0
- package/Tests/UI/Rum/ReplayTimeFormat.test.ts +94 -0
- package/Tests/UI/Rum/ReplayUi.test.tsx +559 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +5 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +1 -0
- package/Tests/Utils/Dashboard/Components/DashboardMonitorListComponent.test.ts +11 -1
- package/Tests/Utils/Dashboard/LabelVariable.test.ts +257 -0
- package/Tests/Utils/Dashboard/VariableUrlState.test.ts +144 -14
- package/Tests/Utils/MetricUnitUtil.test.ts +81 -0
- package/Tests/Utils/Monitor/DeviceMacLearningUtil.test.ts +497 -0
- package/Tests/Utils/Monitor/MetricValueFormatter.test.ts +546 -0
- package/Tests/Utils/Monitor/MonitorTemplateCustomFieldUtil.test.ts +218 -0
- package/Tests/Utils/Monitor/NetworkTopologyEndpointAdoption.test.ts +1755 -0
- package/Tests/Utils/NetworkAutomation/AutoImportRunChain.test.ts +320 -0
- package/Types/BaseDatabase/IncludesAnyOfGroups.ts +77 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardMonitorListComponent.ts +1 -0
- package/Types/Dashboard/DashboardVariable.ts +8 -0
- package/Types/JSON.ts +4 -0
- package/Types/JSONFunctions.ts +16 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
- package/Types/NetworkAutomation/RuleRunResult.ts +402 -13
- package/Types/SerializableObjectDictionary.ts +4 -0
- package/Types/Telemetry/InventoryLiveness.ts +56 -0
- package/UI/Components/CSVFileUpload/CSVFileUpload.tsx +9 -2
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +46 -21
- package/UI/Components/Dashboard/DashboardVariableControl.tsx +285 -0
- package/UI/esbuild-config.js +6 -0
- package/Utils/Dashboard/Components/DashboardMonitorListComponent.ts +12 -1
- package/Utils/Dashboard/LabelVariable.ts +152 -0
- package/Utils/Dashboard/VariableUrlState.ts +27 -5
- package/Utils/MetricUnitUtil.ts +34 -0
- package/Utils/Monitor/DeviceMacLearningUtil.ts +177 -0
- package/Utils/Monitor/MetricValueFormatter.ts +342 -0
- package/Utils/Monitor/MonitorTemplateCustomFieldUtil.ts +124 -0
- package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +7 -2
- package/Utils/Monitor/NetworkTopologyUtil.ts +693 -0
- package/Utils/NetworkAutomation/AutoImportRunChain.ts +138 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +12 -12
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js +34 -1
- package/build/dist/Models/DatabaseModels/InventoryItem.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +128 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +44 -12
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791700000000-AddMacAddressToNetworkDevice.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Redis.js +8 -8
- package/build/dist/Server/Infrastructure/Redis.js.map +1 -1
- package/build/dist/Server/Infrastructure/Status.js +10 -4
- package/build/dist/Server/Infrastructure/Status.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +58 -13
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/InventoryItemService.js +28 -1
- package/build/dist/Server/Services/InventoryItemService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +116 -17
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +270 -37
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +65 -0
- package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +54 -0
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +60 -25
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +29 -4
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryUtil.js +36 -0
- package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +75 -0
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +53 -18
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +10 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +2 -2
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +58 -35
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +29 -5
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +166 -35
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js +58 -9
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaExpectationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js +9 -3
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js +26 -12
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaMessageFormatter.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js +74 -33
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaObservationBuilder.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js +114 -0
- package/build/dist/Server/Utils/Monitor/NetworkDeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +25 -0
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +9 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +70 -13
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js +54 -0
- package/build/dist/Types/BaseDatabase/IncludesAnyOfGroups.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardVariable.js +1 -0
- package/build/dist/Types/Dashboard/DashboardVariable.js.map +1 -1
- package/build/dist/Types/JSON.js +1 -0
- package/build/dist/Types/JSON.js.map +1 -1
- package/build/dist/Types/JSONFunctions.js +13 -0
- package/build/dist/Types/JSONFunctions.js.map +1 -1
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js +291 -6
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -1
- package/build/dist/Types/SerializableObjectDictionary.js +4 -0
- package/build/dist/Types/SerializableObjectDictionary.js.map +1 -1
- package/build/dist/Types/Telemetry/InventoryLiveness.js +37 -0
- package/build/dist/Types/Telemetry/InventoryLiveness.js.map +1 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js +7 -0
- package/build/dist/UI/Components/CSVFileUpload/CSVFileUpload.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +9 -2
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js +137 -0
- package/build/dist/UI/Components/Dashboard/DashboardVariableControl.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js +9 -1
- package/build/dist/Utils/Dashboard/Components/DashboardMonitorListComponent.js.map +1 -1
- package/build/dist/Utils/Dashboard/LabelVariable.js +96 -0
- package/build/dist/Utils/Dashboard/LabelVariable.js.map +1 -0
- package/build/dist/Utils/Dashboard/VariableUrlState.js +19 -4
- package/build/dist/Utils/Dashboard/VariableUrlState.js.map +1 -1
- package/build/dist/Utils/MetricUnitUtil.js +26 -0
- package/build/dist/Utils/MetricUnitUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js +112 -0
- package/build/dist/Utils/Monitor/DeviceMacLearningUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +276 -0
- package/build/dist/Utils/Monitor/MetricValueFormatter.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js +101 -0
- package/build/dist/Utils/Monitor/MonitorTemplateCustomFieldUtil.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +7 -2
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +484 -0
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js +59 -0
- package/build/dist/Utils/NetworkAutomation/AutoImportRunChain.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/test-setup.sh +9 -9
- package/tsconfig.json +51 -11
- package/Tests/App/Dashboard/NotificationEmailPreferences.test.tsx +0 -399
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import URL from "../../../../../Types/API/URL";
|
|
3
|
+
import Exception from "../../../../../Types/Exception/Exception";
|
|
4
|
+
import BadDataException from "../../../../../Types/Exception/BadDataException";
|
|
5
|
+
import { JSONObject } from "../../../../../Types/JSON";
|
|
6
|
+
import ObjectID from "../../../../../Types/ObjectID";
|
|
7
|
+
import { RunOptions } from "../../../../../Server/Types/Workflow/ComponentCode";
|
|
8
|
+
import IncomingWebhookUtils, {
|
|
9
|
+
DISCORD_WEBHOOK_DOMAINS,
|
|
10
|
+
} from "../../../../../Server/Types/Workflow/Components/IncomingWebhookUtils";
|
|
11
|
+
import { MICROSOFT_TEAMS_WEBHOOK_DOMAINS } from "../../../../../Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams";
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* The chat components take a free-text "webhook-url" off the workflow graph -
|
|
15
|
+
* authored by any project member, and templatable from trigger data - and the
|
|
16
|
+
* API server POSTs to it. That is the SSRF primitive GHSA-v5xh-rw9h-77fv
|
|
17
|
+
* reported, so the URL is pinned to the vendor's own hosts.
|
|
18
|
+
*
|
|
19
|
+
* A pin only holds if it is applied to the RAW string. URL.fromString defaults
|
|
20
|
+
* an unrecognised scheme to https, so "gopher://10.0.0.1/" converted first
|
|
21
|
+
* would arrive at the check looking perfectly clean. Every refusal below is
|
|
22
|
+
* therefore about a string that some other parse order would have accepted:
|
|
23
|
+
* a suffix that merely CONTAINS the vendor name, credentials that move the
|
|
24
|
+
* real host past an @, a scheme that is not https, and the usual
|
|
25
|
+
* link-local/metadata targets an attacker actually wants.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
type BuildOptionsFunction = () => RunOptions;
|
|
29
|
+
|
|
30
|
+
const buildOptions: BuildOptionsFunction = (): RunOptions => {
|
|
31
|
+
return {
|
|
32
|
+
log: (): void => {},
|
|
33
|
+
workflowLogId: ObjectID.generate(),
|
|
34
|
+
workflowId: ObjectID.generate(),
|
|
35
|
+
projectId: ObjectID.generate(),
|
|
36
|
+
onError: (exception: Exception): Exception => {
|
|
37
|
+
return exception;
|
|
38
|
+
},
|
|
39
|
+
executeWorkflow: async (): Promise<void> => {},
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type PinFunction = (data: {
|
|
44
|
+
url?: unknown;
|
|
45
|
+
allowedDomains?: Array<string>;
|
|
46
|
+
vendorName?: string;
|
|
47
|
+
}) => URL;
|
|
48
|
+
|
|
49
|
+
const pin: PinFunction = (data: {
|
|
50
|
+
url?: unknown;
|
|
51
|
+
allowedDomains?: Array<string>;
|
|
52
|
+
vendorName?: string;
|
|
53
|
+
}): URL => {
|
|
54
|
+
const args: JSONObject = {} as JSONObject;
|
|
55
|
+
|
|
56
|
+
if (data.url !== undefined) {
|
|
57
|
+
(args as Record<string, unknown>)["webhook-url"] = data.url;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return IncomingWebhookUtils.getPinnedWebhookUrl({
|
|
61
|
+
args: args,
|
|
62
|
+
options: buildOptions(),
|
|
63
|
+
allowedDomains: data.allowedDomains || DISCORD_WEBHOOK_DOMAINS,
|
|
64
|
+
vendorName: data.vendorName || "Discord",
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
describe("a URL on the vendor's own hosts is accepted", () => {
|
|
69
|
+
test("accepts the canonical Discord webhook URL", () => {
|
|
70
|
+
expect(
|
|
71
|
+
pin({
|
|
72
|
+
url: "https://discord.com/api/webhooks/123/abc",
|
|
73
|
+
}).toString(),
|
|
74
|
+
).toBe("https://discord.com/api/webhooks/123/abc");
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("accepts the legacy discordapp.com host", () => {
|
|
78
|
+
expect(
|
|
79
|
+
pin({ url: "https://discordapp.com/api/webhooks/1/x" }).toString(),
|
|
80
|
+
).toContain("discordapp.com");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("accepts a subdomain of an allowed domain", () => {
|
|
84
|
+
expect(
|
|
85
|
+
pin({ url: "https://ptb.discord.com/api/webhooks/1/x" }).toString(),
|
|
86
|
+
).toContain("ptb.discord.com");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("accepts a URL handed in as a URL object rather than a string", () => {
|
|
90
|
+
expect(
|
|
91
|
+
pin({
|
|
92
|
+
url: URL.fromString("https://discord.com/api/webhooks/1/x"),
|
|
93
|
+
}).toString(),
|
|
94
|
+
).toContain("discord.com");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("accepts an allowed host written in mixed case", () => {
|
|
98
|
+
expect(() => {
|
|
99
|
+
return pin({ url: "https://DISCORD.com/api/webhooks/1/x" });
|
|
100
|
+
}).not.toThrow();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("returns a parsed URL, not the raw string", () => {
|
|
104
|
+
expect(pin({ url: "https://discord.com/api/webhooks/1/x" })).toBeInstanceOf(
|
|
105
|
+
URL,
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("accepts every Microsoft Teams host the workspace integration allows", () => {
|
|
110
|
+
for (const domain of MICROSOFT_TEAMS_WEBHOOK_DOMAINS) {
|
|
111
|
+
expect(
|
|
112
|
+
pin({
|
|
113
|
+
url: `https://${domain}/webhookb2/abc`,
|
|
114
|
+
allowedDomains: MICROSOFT_TEAMS_WEBHOOK_DOMAINS,
|
|
115
|
+
vendorName: "Microsoft Teams",
|
|
116
|
+
}).toString(),
|
|
117
|
+
).toContain(domain);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe("a missing webhook URL is refused before anything is parsed", () => {
|
|
123
|
+
test("refuses an args bag with no webhook-url at all", () => {
|
|
124
|
+
expect(() => {
|
|
125
|
+
return pin({});
|
|
126
|
+
}).toThrow("Discord Webhook URL not found");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("refuses an empty webhook-url", () => {
|
|
130
|
+
expect(() => {
|
|
131
|
+
return pin({ url: "" });
|
|
132
|
+
}).toThrow("Discord Webhook URL not found");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("refuses a null webhook-url", () => {
|
|
136
|
+
expect(() => {
|
|
137
|
+
return pin({ url: null });
|
|
138
|
+
}).toThrow("Discord Webhook URL not found");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("names the vendor it was asked about in the message", () => {
|
|
142
|
+
expect(() => {
|
|
143
|
+
return pin({
|
|
144
|
+
vendorName: "Microsoft Teams",
|
|
145
|
+
allowedDomains: MICROSOFT_TEAMS_WEBHOOK_DOMAINS,
|
|
146
|
+
});
|
|
147
|
+
}).toThrow("Microsoft Teams Webhook URL not found");
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe("a host that is not the vendor's is refused", () => {
|
|
152
|
+
test("refuses an unrelated public host", () => {
|
|
153
|
+
expect(() => {
|
|
154
|
+
return pin({ url: "https://evil.example.com/api/webhooks/1/x" });
|
|
155
|
+
}).toThrow(BadDataException);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("refuses a suffix that merely CONTAINS the vendor name", () => {
|
|
159
|
+
expect(() => {
|
|
160
|
+
return pin({ url: "https://notdiscord.com/api/webhooks/1/x" });
|
|
161
|
+
}).toThrow(BadDataException);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("refuses the vendor name used as a prefix of an attacker's domain", () => {
|
|
165
|
+
expect(() => {
|
|
166
|
+
return pin({ url: "https://discord.com.evil.example/x" });
|
|
167
|
+
}).toThrow(BadDataException);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("refuses the vendor host hidden in the userinfo of another host", () => {
|
|
171
|
+
expect(() => {
|
|
172
|
+
return pin({ url: "https://discord.com@evil.example.com/x" });
|
|
173
|
+
}).toThrow(BadDataException);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("refuses a Teams host when the component asked about Discord", () => {
|
|
177
|
+
expect(() => {
|
|
178
|
+
return pin({ url: "https://office.com/webhookb2/abc" });
|
|
179
|
+
}).toThrow(BadDataException);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("says which domains would have been acceptable", () => {
|
|
183
|
+
expect(() => {
|
|
184
|
+
return pin({ url: "https://evil.example.com/x" });
|
|
185
|
+
}).toThrow("discord.com or discordapp.com");
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe("a scheme that is not https is refused, whatever it would parse to", () => {
|
|
190
|
+
test("refuses plain http even on an allowed host", () => {
|
|
191
|
+
expect(() => {
|
|
192
|
+
return pin({ url: "http://discord.com/api/webhooks/1/x" });
|
|
193
|
+
}).toThrow(BadDataException);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("refuses a scheme URL.fromString would have quietly rewritten to https", () => {
|
|
197
|
+
expect(() => {
|
|
198
|
+
return pin({ url: "gopher://discord.com/api/webhooks/1/x" });
|
|
199
|
+
}).toThrow(BadDataException);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("refuses file:", () => {
|
|
203
|
+
expect(() => {
|
|
204
|
+
return pin({ url: "file:///etc/passwd" });
|
|
205
|
+
}).toThrow(BadDataException);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
test("refuses a data: URL", () => {
|
|
209
|
+
expect(() => {
|
|
210
|
+
return pin({ url: "data:text/plain,discord.com" });
|
|
211
|
+
}).toThrow(BadDataException);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("refuses a string that is not a URL at all", () => {
|
|
215
|
+
expect(() => {
|
|
216
|
+
return pin({ url: "discord.com/api/webhooks/1/x" });
|
|
217
|
+
}).toThrow(BadDataException);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
describe("the internal targets an SSRF is actually aimed at", () => {
|
|
222
|
+
const internalTargets: Array<string> = [
|
|
223
|
+
"https://169.254.169.254/latest/meta-data/",
|
|
224
|
+
"https://metadata.google.internal/computeMetadata/v1/",
|
|
225
|
+
"https://127.0.0.1/api/webhooks/1/x",
|
|
226
|
+
"https://localhost/api/webhooks/1/x",
|
|
227
|
+
"https://10.0.0.1/api/webhooks/1/x",
|
|
228
|
+
"https://192.168.1.1/api/webhooks/1/x",
|
|
229
|
+
"https://[::1]/api/webhooks/1/x",
|
|
230
|
+
"https://0x7f000001/api/webhooks/1/x",
|
|
231
|
+
"https://2130706433/api/webhooks/1/x",
|
|
232
|
+
];
|
|
233
|
+
|
|
234
|
+
test.each(internalTargets)("refuses %s", (target: string) => {
|
|
235
|
+
expect(() => {
|
|
236
|
+
return pin({ url: target });
|
|
237
|
+
}).toThrow(BadDataException);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("refuses them for Microsoft Teams too", () => {
|
|
241
|
+
for (const target of internalTargets) {
|
|
242
|
+
expect(() => {
|
|
243
|
+
return pin({
|
|
244
|
+
url: target,
|
|
245
|
+
allowedDomains: MICROSOFT_TEAMS_WEBHOOK_DOMAINS,
|
|
246
|
+
vendorName: "Microsoft Teams",
|
|
247
|
+
});
|
|
248
|
+
}).toThrow(BadDataException);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
describe("an empty allowlist pins to nothing at all", () => {
|
|
254
|
+
test("refuses even the vendor's own host", () => {
|
|
255
|
+
expect(() => {
|
|
256
|
+
return pin({
|
|
257
|
+
url: "https://discord.com/api/webhooks/1/x",
|
|
258
|
+
allowedDomains: [],
|
|
259
|
+
});
|
|
260
|
+
}).toThrow(BadDataException);
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe("the exported allowlists are the ones the components use", () => {
|
|
265
|
+
test("Discord pins to discord.com and discordapp.com only", () => {
|
|
266
|
+
expect(DISCORD_WEBHOOK_DOMAINS).toEqual(["discord.com", "discordapp.com"]);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("no allowlist entry is a bare public suffix", () => {
|
|
270
|
+
for (const domain of [
|
|
271
|
+
...DISCORD_WEBHOOK_DOMAINS,
|
|
272
|
+
...MICROSOFT_TEAMS_WEBHOOK_DOMAINS,
|
|
273
|
+
]) {
|
|
274
|
+
expect(domain.split(".").length).toBeGreaterThanOrEqual(2);
|
|
275
|
+
expect(domain).not.toContain("/");
|
|
276
|
+
expect(domain).toBe(domain.toLowerCase());
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
});
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import Exception from "../../../../../Types/Exception/Exception";
|
|
3
|
+
import BadDataException from "../../../../../Types/Exception/BadDataException";
|
|
4
|
+
import { JSONObject } from "../../../../../Types/JSON";
|
|
5
|
+
import ObjectID from "../../../../../Types/ObjectID";
|
|
6
|
+
import ComponentID from "../../../../../Types/Workflow/ComponentID";
|
|
7
|
+
import {
|
|
8
|
+
RunOptions,
|
|
9
|
+
RunReturnType,
|
|
10
|
+
} from "../../../../../Server/Types/Workflow/ComponentCode";
|
|
11
|
+
import TextToJSON from "../../../../../Server/Types/Workflow/Components/JSON/TextToJson";
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* A workflow's Text to JSON step sits between two things the author does not
|
|
15
|
+
* control: whatever a webhook or an API step returned, and whatever the next
|
|
16
|
+
* step reads off `json`. So the two branches have to stay honest.
|
|
17
|
+
*
|
|
18
|
+
* A misuse of the STEP - no text, or text that is not a string - is an author
|
|
19
|
+
* mistake and stops the workflow through onError, because carrying on would
|
|
20
|
+
* feed the rest of the graph a value nobody chose. Text that is simply not
|
|
21
|
+
* JSON is a DATA condition, not an author mistake: it leaves through the error
|
|
22
|
+
* port with a log line, so the author can branch on it and the workflow keeps
|
|
23
|
+
* running.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
type BuildOptionsFunction = () => {
|
|
27
|
+
options: RunOptions;
|
|
28
|
+
logs: Array<unknown>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const buildOptions: BuildOptionsFunction = (): {
|
|
32
|
+
options: RunOptions;
|
|
33
|
+
logs: Array<unknown>;
|
|
34
|
+
} => {
|
|
35
|
+
const logs: Array<unknown> = [];
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
logs: logs,
|
|
39
|
+
options: {
|
|
40
|
+
log: (item: unknown): void => {
|
|
41
|
+
logs.push(item);
|
|
42
|
+
},
|
|
43
|
+
workflowLogId: ObjectID.generate(),
|
|
44
|
+
workflowId: ObjectID.generate(),
|
|
45
|
+
projectId: ObjectID.generate(),
|
|
46
|
+
onError: (exception: Exception): Exception => {
|
|
47
|
+
return exception;
|
|
48
|
+
},
|
|
49
|
+
executeWorkflow: async (): Promise<void> => {},
|
|
50
|
+
} as RunOptions,
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
describe("the component is wired to the catalogue entry the graph names", () => {
|
|
55
|
+
test("adopts the TextToJson metadata", () => {
|
|
56
|
+
expect(new TextToJSON().getMetadata().id).toBe(ComponentID.TextToJson);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("declares both a success and an error port", () => {
|
|
60
|
+
const portIds: Array<string> = new TextToJSON()
|
|
61
|
+
.getMetadata()
|
|
62
|
+
.outPorts.map((port: { id: string }) => {
|
|
63
|
+
return port.id;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
expect(portIds).toContain("success");
|
|
67
|
+
expect(portIds).toContain("error");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe("text that parses leaves through the success port", () => {
|
|
72
|
+
test("returns the parsed object", async () => {
|
|
73
|
+
const result: RunReturnType = await new TextToJSON().run(
|
|
74
|
+
{ text: '{"status":"ok","count":2}' },
|
|
75
|
+
buildOptions().options,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
expect(result.executePort?.id).toBe("success");
|
|
79
|
+
expect(result.returnValues["json"]).toEqual({ status: "ok", count: 2 });
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("keeps nested structure intact", async () => {
|
|
83
|
+
const result: RunReturnType = await new TextToJSON().run(
|
|
84
|
+
{ text: '{"a":{"b":[1,2,{"c":true}]}}' },
|
|
85
|
+
buildOptions().options,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
expect(result.returnValues["json"]).toEqual({
|
|
89
|
+
a: { b: [1, 2, { c: true }] },
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("accepts an empty object", async () => {
|
|
94
|
+
const result: RunReturnType = await new TextToJSON().run(
|
|
95
|
+
{ text: "{}" },
|
|
96
|
+
buildOptions().options,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
expect(result.executePort?.id).toBe("success");
|
|
100
|
+
expect(result.returnValues["json"]).toEqual({});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("accepts surrounding whitespace", async () => {
|
|
104
|
+
const result: RunReturnType = await new TextToJSON().run(
|
|
105
|
+
{ text: ' {"a":1}\n' },
|
|
106
|
+
buildOptions().options,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
expect(result.executePort?.id).toBe("success");
|
|
110
|
+
expect(result.returnValues["json"]).toEqual({ a: 1 });
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("logs nothing on the happy path", async () => {
|
|
114
|
+
const built: { options: RunOptions; logs: Array<unknown> } = buildOptions();
|
|
115
|
+
|
|
116
|
+
await new TextToJSON().run({ text: '{"a":1}' }, built.options);
|
|
117
|
+
|
|
118
|
+
expect(built.logs).toEqual([]);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe("text that does not parse leaves through the error port", () => {
|
|
123
|
+
const unparseable: Array<[string, string]> = [
|
|
124
|
+
["plain prose", "not json at all"],
|
|
125
|
+
["a truncated object", '{"a":'],
|
|
126
|
+
["an HTML error page", "<html><body>502</body></html>"],
|
|
127
|
+
["a bare string literal", '"just a string"'],
|
|
128
|
+
["a bare number", "42"],
|
|
129
|
+
["an empty-ish blob of whitespace", " \n "],
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
test.each(unparseable)(
|
|
133
|
+
"routes %s to the error port",
|
|
134
|
+
async (_name: string, text: string) => {
|
|
135
|
+
const result: RunReturnType = await new TextToJSON().run(
|
|
136
|
+
{ text: text },
|
|
137
|
+
buildOptions().options,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
expect(result.executePort?.id).toBe("error");
|
|
141
|
+
expect(result.returnValues).toEqual({});
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
test("a JSON ARRAY is not a JSON object, and takes the error port", async () => {
|
|
146
|
+
const result: RunReturnType = await new TextToJSON().run(
|
|
147
|
+
{ text: '[{"a":1}]' },
|
|
148
|
+
buildOptions().options,
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
expect(result.executePort?.id).toBe("error");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("tells the author, in the workflow log, that the text was malformed", async () => {
|
|
155
|
+
const built: { options: RunOptions; logs: Array<unknown> } = buildOptions();
|
|
156
|
+
|
|
157
|
+
await new TextToJSON().run({ text: "not json at all" }, built.options);
|
|
158
|
+
|
|
159
|
+
expect(built.logs).toContain("text is not in the correct format.");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("does not throw - the workflow is expected to carry on", async () => {
|
|
163
|
+
await expect(
|
|
164
|
+
new TextToJSON().run({ text: "not json" }, buildOptions().options),
|
|
165
|
+
).resolves.toBeDefined();
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe("a misconfigured step stops the workflow instead of guessing", () => {
|
|
170
|
+
test("refuses a missing text argument", async () => {
|
|
171
|
+
await expect(
|
|
172
|
+
new TextToJSON().run({} as JSONObject, buildOptions().options),
|
|
173
|
+
).rejects.toThrow("text is undefined.");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("refuses an empty text argument", async () => {
|
|
177
|
+
await expect(
|
|
178
|
+
new TextToJSON().run({ text: "" }, buildOptions().options),
|
|
179
|
+
).rejects.toThrow("text is undefined.");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("refuses a text argument that is already an object", async () => {
|
|
183
|
+
await expect(
|
|
184
|
+
new TextToJSON().run({ text: { a: 1 } }, buildOptions().options),
|
|
185
|
+
).rejects.toThrow("text is should be of type string.");
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("refuses a numeric text argument", async () => {
|
|
189
|
+
await expect(
|
|
190
|
+
new TextToJSON().run({ text: 42 }, buildOptions().options),
|
|
191
|
+
).rejects.toThrow("text is should be of type string.");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("raises the refusal as a BadDataException through onError", async () => {
|
|
195
|
+
await expect(
|
|
196
|
+
new TextToJSON().run({}, buildOptions().options),
|
|
197
|
+
).rejects.toBeInstanceOf(BadDataException);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("routes the refusal through the caller's onError hook", async () => {
|
|
201
|
+
const seen: Array<Exception> = [];
|
|
202
|
+
const built: { options: RunOptions; logs: Array<unknown> } = buildOptions();
|
|
203
|
+
|
|
204
|
+
built.options.onError = (exception: Exception): Exception => {
|
|
205
|
+
seen.push(exception);
|
|
206
|
+
return exception;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
await expect(new TextToJSON().run({}, built.options)).rejects.toThrow();
|
|
210
|
+
expect(seen.length).toBe(1);
|
|
211
|
+
});
|
|
212
|
+
});
|