@oneuptime/common 12.0.9 → 12.0.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/Models/DatabaseModels/NetworkDevice.ts +44 -0
- package/Models/DatabaseModels/NetworkDeviceLink.ts +116 -0
- package/Models/DatabaseModels/RunnerJob.ts +17 -2
- package/Server/API/LlmProviderAPI.ts +8 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.ts +52 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/NetworkDeviceLabelRuleEngineService.ts +240 -0
- package/Server/Services/NetworkDeviceLinkService.ts +338 -2
- package/Server/Services/NetworkDeviceService.ts +265 -12
- package/Server/Services/RunnerJobService.ts +24 -1
- package/Server/Types/Billing/MeteredPlan/AllMeteredPlans.ts +19 -5
- package/Server/Types/Database/JSONColumnQuery.ts +42 -4
- package/Server/Utils/LLM/LLMService.ts +108 -19
- package/Server/Utils/Monitor/MonitorAlert.ts +22 -115
- package/Server/Utils/Monitor/MonitorIncident.ts +7 -337
- package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +27 -2
- package/Server/Utils/Monitor/SeriesResourceLabels.ts +32 -0
- package/Server/Utils/Monitor/SeriesResourceLinker.ts +469 -0
- package/Server/Utils/SSRFProtection.ts +5 -4
- package/Server/Utils/Telemetry/InventoryEntityRegistry.ts +186 -38
- package/Tests/App/AdminDashboard/AdminUsersBulkAddToProject.test.tsx +955 -0
- package/Tests/App/AdminDashboard/BulkAddUsersToProject.test.ts +778 -0
- package/Tests/App/AdminDashboard/BulkAddUsersToProjectModal.test.tsx +1051 -0
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +420 -112
- package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +81 -6
- package/Tests/App/Dashboard/PayAsYouGoConsentGate.test.tsx +543 -0
- package/Tests/App/Dashboard/PayAsYouGoFreePlan.test.ts +125 -0
- package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +612 -0
- package/Tests/App/Dashboard/SloWidgetChartRendering.test.tsx +536 -0
- package/Tests/App/Dashboard/UserElementEmail.test.tsx +447 -0
- package/Tests/App/Dashboard/UserEmailCallSites.test.tsx +284 -0
- package/Tests/Models/AnalyticsModels/CommonModel.test.ts +312 -0
- package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +250 -0
- package/Tests/Server/Infrastructure/Postgres/EpisodeMemberNotifyIndexesMigration.test.ts +46 -18
- package/Tests/Server/Services/NetworkDeviceLabelRuleRun.test.ts +618 -0
- package/Tests/Server/Services/NetworkDeviceLinkParentValidation.test.ts +1043 -0
- package/Tests/Server/Services/NetworkSiteAssignmentRuleRun.test.ts +706 -0
- package/Tests/Server/Services/RunnerJobEnqueue.test.ts +412 -0
- package/Tests/Server/Types/Billing/PayAsYouGoMeteredPricing.test.ts +125 -0
- package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +508 -0
- package/Tests/Server/Utils/AI/LLMServiceRequestPolicy.test.ts +2 -2
- package/Tests/Server/Utils/AI/LLMServiceRetryPolicy.test.ts +450 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +556 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +547 -0
- package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +30 -0
- package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +699 -0
- package/Tests/Server/Utils/SeriesResourceLabels.test.ts +55 -0
- package/Tests/Server/Utils/Telemetry/InventoryEntityRegistry.test.ts +668 -0
- package/Tests/Types/API/URLMalformedTolerance.test.ts +301 -0
- package/Tests/Types/API/URLTelScheme.test.ts +261 -0
- package/Tests/Types/API/URLUnsupportedScheme.test.ts +148 -0
- package/Tests/Types/Billing/PayAsYouGoPricing.test.ts +64 -0
- package/Tests/Types/CustomField/CustomFieldTypeCorrespondence.test.ts +107 -0
- package/Tests/Types/Monitor/MonitorType.test.ts +62 -0
- package/Tests/Types/NetworkAutomation/RuleRunResult.test.ts +184 -0
- package/Tests/Types/Runbook/RunbookStepType.test.ts +124 -0
- package/Tests/Types/Workflow/BaseModelComponents.test.ts +17 -0
- package/Tests/UI/Components/Button.test.tsx +279 -0
- package/Tests/UI/Components/Charts/ChartXAxisDataKey.test.tsx +256 -0
- package/Tests/UI/Components/Charts/DataPointUtil.test.ts +323 -0
- package/Tests/UI/Components/DictionaryValuePlaceholderAndSuggestions.test.tsx +595 -0
- package/Tests/UI/Components/Icon.test.tsx +57 -0
- package/Tests/UI/Components/List.test.tsx +20 -3
- package/Tests/UI/Components/LogsSavedViewsDropdown.test.tsx +304 -0
- package/Tests/UI/Components/LogsViewerClearSavedView.test.tsx +179 -0
- package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +349 -1
- package/Tests/UI/Components/Pagination.test.tsx +721 -146
- package/Tests/UI/Components/PaginationIntegration.test.tsx +386 -0
- package/Tests/UI/Components/PaginationUtil.test.ts +417 -0
- package/Tests/UI/Components/TelemetrySavedViewsDropdown.test.tsx +343 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnControl.test.ts +289 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnOperators.test.ts +271 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRow.test.ts +119 -0
- package/Tests/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.test.ts +968 -0
- package/Tests/UI/Components/Workflow/ModelColumnEditor.render.test.tsx +766 -0
- package/Tests/UI/Utils/RunNetworkRule.test.ts +320 -0
- package/Tests/Utils/APIRetryPolicy.test.ts +823 -0
- package/Tests/Utils/Monitor/NetworkDeviceRoleUtil.test.ts +502 -0
- package/Tests/Utils/Monitor/NetworkTopologyDeclaredHierarchy.test.ts +759 -0
- package/Tests/Utils/NetworkAutomation/RuleRunSummary.test.ts +286 -0
- package/Tests/Utils/Schema/BaseSchema.test.ts +651 -0
- package/Types/API/Protocol.ts +9 -0
- package/Types/API/URL.ts +286 -20
- package/Types/Billing/PayAsYouGoPricing.ts +47 -0
- package/Types/CustomField/CustomFieldType.ts +28 -0
- package/Types/Monitor/MonitorType.ts +11 -0
- package/Types/Monitor/SnmpMonitor/NetworkTopology.ts +13 -0
- package/Types/NetworkAutomation/RuleRunResult.ts +121 -0
- package/Types/Runbook/RunbookStepType.ts +23 -0
- package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +7 -0
- package/UI/Components/Button/Button.tsx +14 -22
- package/UI/Components/Charts/Area/AreaChart.tsx +4 -2
- package/UI/Components/Charts/Bar/BarChart.tsx +4 -2
- package/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.ts +13 -0
- package/UI/Components/Charts/Line/LineChart.tsx +4 -2
- package/UI/Components/Charts/Types/XAxis/XAxis.ts +6 -0
- package/UI/Components/Charts/Utils/DataPoint.ts +15 -12
- package/UI/Components/Dictionary/Dictionary.tsx +4 -39
- package/UI/Components/Icon/Icon.tsx +1 -1
- package/UI/Components/LogsViewer/LogsViewer.tsx +5 -1
- package/UI/Components/LogsViewer/components/LogsFacetSidebar.tsx +8 -0
- package/UI/Components/LogsViewer/components/LogsPagination.tsx +29 -91
- package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +2 -0
- package/UI/Components/LogsViewer/components/SavedViewsDropdown.tsx +66 -1
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +55 -0
- package/UI/Components/Pagination/Pagination.tsx +373 -328
- package/UI/Components/Pagination/PaginationUtil.ts +263 -0
- package/UI/Components/Pagination/Screenshots/README.md +15 -0
- package/UI/Components/Pagination/Screenshots/pagination-before-after.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-go-to-page-modal.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-long-list.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-mobile.png +0 -0
- package/UI/Components/Pagination/Screenshots/pagination-telemetry.png +0 -0
- package/UI/Components/TelemetryViewer/components/SavedViewsDropdown.tsx +66 -1
- package/UI/Components/TelemetryViewer/components/TelemetryPagination.tsx +38 -92
- package/UI/Components/TextArea/TextArea.tsx +3 -0
- package/UI/Components/Workflow/ArgumentsForm.tsx +18 -1
- package/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.tsx +177 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.tsx +243 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnControl.ts +255 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.tsx +178 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnOperators.ts +206 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnRow.ts +157 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.ts +517 -0
- package/UI/Components/Workflow/ColumnEditor/ColumnValueInput.tsx +482 -0
- package/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.tsx +131 -0
- package/UI/Components/Workflow/ColumnEditor/ModelRecordForm.tsx +163 -0
- package/UI/Components/Workflow/ModelColumnEditor.tsx +284 -100
- package/UI/Utils/NetworkAutomation/RunNetworkRule.ts +122 -0
- package/Utils/API.ts +243 -36
- package/Utils/Monitor/NetworkDeviceRoleUtil.ts +45 -0
- package/Utils/Monitor/NetworkTopologyUtil.ts +42 -2
- package/Utils/NetworkAutomation/RuleRunSummary.ts +147 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +45 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js +115 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLink.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RunnerJob.js +17 -2
- package/build/dist/Models/DatabaseModels/RunnerJob.js.map +1 -1
- package/build/dist/Server/API/LlmProviderAPI.js +9 -1
- package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.js +33 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787400000000-AddDeviceRoleAndDeclaredLinkParent.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/Services/NetworkDeviceLabelRuleEngineService.js +202 -0
- package/build/dist/Server/Services/NetworkDeviceLabelRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceLinkService.js +253 -1
- package/build/dist/Server/Services/NetworkDeviceLinkService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceService.js +223 -12
- package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
- package/build/dist/Server/Services/RunnerJobService.js +20 -2
- package/build/dist/Server/Services/RunnerJobService.js.map +1 -1
- package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js +12 -5
- package/build/dist/Server/Types/Billing/MeteredPlan/AllMeteredPlans.js.map +1 -1
- package/build/dist/Server/Types/Database/JSONColumnQuery.js +40 -4
- package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -1
- package/build/dist/Server/Utils/LLM/LLMService.js +78 -22
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +22 -98
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js +7 -254
- package/build/dist/Server/Utils/Monitor/MonitorIncident.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +14 -2
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +27 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +285 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -0
- package/build/dist/Server/Utils/SSRFProtection.js +5 -4
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js +128 -35
- package/build/dist/Server/Utils/Telemetry/InventoryEntityRegistry.js.map +1 -1
- package/build/dist/Types/API/Protocol.js +9 -0
- package/build/dist/Types/API/Protocol.js.map +1 -1
- package/build/dist/Types/API/URL.js +241 -18
- package/build/dist/Types/API/URL.js.map +1 -1
- package/build/dist/Types/Billing/PayAsYouGoPricing.js +41 -0
- package/build/dist/Types/Billing/PayAsYouGoPricing.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldType.js +26 -0
- package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorType.js +10 -0
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js +39 -0
- package/build/dist/Types/NetworkAutomation/RuleRunResult.js.map +1 -0
- package/build/dist/Types/Runbook/RunbookStepType.js +21 -0
- package/build/dist/Types/Runbook/RunbookStepType.js.map +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
- package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
- package/build/dist/UI/Components/Button/Button.js +10 -16
- package/build/dist/UI/Components/Button/Button.js.map +1 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +2 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +2 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js +12 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Types/ChartDataPoint.js.map +1 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js +2 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js +17 -10
- package/build/dist/UI/Components/Charts/Utils/DataPoint.js.map +1 -1
- package/build/dist/UI/Components/Dictionary/Dictionary.js +4 -18
- package/build/dist/UI/Components/Dictionary/Dictionary.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +1 -1
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +3 -3
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js +5 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsFacetSidebar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js +14 -44
- package/build/dist/UI/Components/LogsViewer/components/LogsPagination.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js +20 -2
- package/build/dist/UI/Components/LogsViewer/components/SavedViewsDropdown.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +42 -0
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/Pagination/Pagination.js +174 -199
- package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
- package/build/dist/UI/Components/Pagination/PaginationUtil.js +160 -0
- package/build/dist/UI/Components/Pagination/PaginationUtil.js.map +1 -0
- package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js +20 -2
- package/build/dist/UI/Components/TelemetryViewer/components/SavedViewsDropdown.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js +23 -47
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryPagination.js.map +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js +1 -1
- package/build/dist/UI/Components/TextArea/TextArea.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js +14 -2
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js +86 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/AddColumnDropdown.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js +119 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnConditionRow.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js +206 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnControl.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js +57 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnFieldRow.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js +150 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnOperators.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js +67 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRow.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js +309 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnRowSerialization.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js +204 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ColumnValueInput.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js +66 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelQueryBuilder.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js +83 -0
- package/build/dist/UI/Components/Workflow/ColumnEditor/ModelRecordForm.js.map +1 -0
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js +187 -68
- package/build/dist/UI/Components/Workflow/ModelColumnEditor.js.map +1 -1
- package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js +75 -0
- package/build/dist/UI/Utils/NetworkAutomation/RunNetworkRule.js.map +1 -0
- package/build/dist/Utils/API.js +175 -34
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js +37 -0
- package/build/dist/Utils/Monitor/NetworkDeviceRoleUtil.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +25 -3
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js +78 -0
- package/build/dist/Utils/NetworkAutomation/RuleRunSummary.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
import RunnerJobService from "../../../Server/Services/RunnerJobService";
|
|
2
|
+
import RunnerJob from "../../../Models/DatabaseModels/RunnerJob";
|
|
3
|
+
import RunbookStepType, {
|
|
4
|
+
RUNNER_EXECUTED_STEP_TYPES,
|
|
5
|
+
isPayloadCarryingStepType,
|
|
6
|
+
} from "../../../Types/Runbook/RunbookStepType";
|
|
7
|
+
import RunnerJobOrigin from "../../../Types/Runbook/RunnerJobOrigin";
|
|
8
|
+
import RunnerJobStatus from "../../../Types/Runbook/RunnerJobStatus";
|
|
9
|
+
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
10
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
11
|
+
import logger from "../../../Server/Utils/Logger";
|
|
12
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
13
|
+
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* ---------------------------------------------------------------------------
|
|
17
|
+
* Contract under test — RunnerJobService.enqueue, and the RunnerJob column
|
|
18
|
+
* declarations it has to satisfy.
|
|
19
|
+
*
|
|
20
|
+
* Background (issue #3209): RunnerJob.script was declared `required: true`.
|
|
21
|
+
* DatabaseService.checkRequiredFields rejects any falsy value on a required
|
|
22
|
+
* column, and an empty string is falsy — so every SSH and Kubernetes job,
|
|
23
|
+
* which carries its instruction in `payload` and an empty script BY DESIGN,
|
|
24
|
+
* died at create() with "script is required". Two whole step types were
|
|
25
|
+
* unusable, and nothing caught it because every test stubbed enqueue.
|
|
26
|
+
*
|
|
27
|
+
* Two things are pinned here, and they are deliberately different in kind:
|
|
28
|
+
*
|
|
29
|
+
* 1. the column declaration itself — `script` must not be a required
|
|
30
|
+
* column. This is the direct tripwire on the change that caused the bug.
|
|
31
|
+
*
|
|
32
|
+
* 2. the rule that `required: true` was standing in for, now enforced
|
|
33
|
+
* per-type where it can actually be expressed: a script-carrying type
|
|
34
|
+
* must bring a script, a payload-carrying type must bring a payload, and
|
|
35
|
+
* the row that comes out passes the real validator either way.
|
|
36
|
+
*
|
|
37
|
+
* No database: create is stubbed at the boundary, and everything above it is
|
|
38
|
+
* the real implementation.
|
|
39
|
+
* ---------------------------------------------------------------------------
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
const PROJECT_ID: ObjectID = new ObjectID(
|
|
43
|
+
"22222222-2222-4222-8222-222222222222",
|
|
44
|
+
);
|
|
45
|
+
const EXECUTION_ID: ObjectID = new ObjectID(
|
|
46
|
+
"33333333-3333-4333-8333-333333333333",
|
|
47
|
+
);
|
|
48
|
+
const AGENT_ID: ObjectID = new ObjectID("44444444-4444-4444-8444-444444444444");
|
|
49
|
+
|
|
50
|
+
const SSH_PAYLOAD: JSONObject = {
|
|
51
|
+
credentialId: "55555555-5555-4555-8555-555555555555",
|
|
52
|
+
command: "systemctl restart nginx",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/*
|
|
56
|
+
* The production validator, bound off the live service rather than
|
|
57
|
+
* reimplemented — the point is to fail if the model's declarations change,
|
|
58
|
+
* not if a copy of the rule drifts.
|
|
59
|
+
*/
|
|
60
|
+
type CreatePathInternals = {
|
|
61
|
+
generateDefaultValues: (model: RunnerJob) => RunnerJob;
|
|
62
|
+
checkRequiredFields: (model: RunnerJob) => RunnerJob;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function runRealCreateValidation(row: RunnerJob): RunnerJob {
|
|
66
|
+
const internals: CreatePathInternals =
|
|
67
|
+
RunnerJobService as unknown as CreatePathInternals;
|
|
68
|
+
|
|
69
|
+
return internals.checkRequiredFields.call(
|
|
70
|
+
RunnerJobService,
|
|
71
|
+
internals.generateDefaultValues.call(RunnerJobService, row),
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
type EnqueueArgs = Parameters<typeof RunnerJobService.enqueue>[0];
|
|
76
|
+
|
|
77
|
+
function enqueueArgs(overrides: Partial<EnqueueArgs> = {}): EnqueueArgs {
|
|
78
|
+
return {
|
|
79
|
+
projectId: PROJECT_ID,
|
|
80
|
+
runbookExecutionId: EXECUTION_ID,
|
|
81
|
+
stepId: "step-1",
|
|
82
|
+
stepType: RunbookStepType.Bash,
|
|
83
|
+
targetAgentId: AGENT_ID,
|
|
84
|
+
script: "uptime",
|
|
85
|
+
timeoutInMs: 30_000,
|
|
86
|
+
...overrides,
|
|
87
|
+
} as EnqueueArgs;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/*
|
|
91
|
+
* Arguments that are valid for the given step type — a script for the script
|
|
92
|
+
* types, a payload and an empty script for the payload types. Used by the
|
|
93
|
+
* table-driven tests so every runner-executed type is exercised through the
|
|
94
|
+
* same assertions.
|
|
95
|
+
*/
|
|
96
|
+
function validArgsFor(stepType: RunbookStepType): EnqueueArgs {
|
|
97
|
+
if (isPayloadCarryingStepType(stepType)) {
|
|
98
|
+
return enqueueArgs({ stepType, script: "", payload: SSH_PAYLOAD });
|
|
99
|
+
}
|
|
100
|
+
return enqueueArgs({ stepType, script: "echo hello" });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let createSpy: jest.SpyInstance;
|
|
104
|
+
|
|
105
|
+
function createdRow(): RunnerJob {
|
|
106
|
+
expect(createSpy).toHaveBeenCalledTimes(1);
|
|
107
|
+
return (createSpy.mock.calls[0]![0] as { data: RunnerJob }).data;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
beforeEach(() => {
|
|
111
|
+
/*
|
|
112
|
+
* enqueue is wrapped in @CaptureSpan, which records a thrown exception at
|
|
113
|
+
* error level. The rejection tests expect those throws, so keep the output
|
|
114
|
+
* readable.
|
|
115
|
+
*/
|
|
116
|
+
jest.spyOn(logger, "error").mockImplementation((): void => {
|
|
117
|
+
return undefined;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
createSpy = jest
|
|
121
|
+
.spyOn(RunnerJobService, "create")
|
|
122
|
+
.mockImplementation((args: { data: RunnerJob }): Promise<RunnerJob> => {
|
|
123
|
+
const row: RunnerJob = args.data;
|
|
124
|
+
row._id = "66666666-6666-4666-8666-666666666666";
|
|
125
|
+
return Promise.resolve(row);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
afterEach(() => {
|
|
130
|
+
jest.restoreAllMocks();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("RunnerJob column declarations", () => {
|
|
134
|
+
test("script is NOT a required column — the declaration that caused #3209", () => {
|
|
135
|
+
/*
|
|
136
|
+
* The single assertion that would have caught the bug. Flipping this back
|
|
137
|
+
* to `required: true` makes every SSH and Kubernetes job unpersistable,
|
|
138
|
+
* because their script is legitimately an empty string.
|
|
139
|
+
*/
|
|
140
|
+
const requiredColumns: Array<string> = new RunnerJob().getRequiredColumns()
|
|
141
|
+
.columns;
|
|
142
|
+
|
|
143
|
+
expect(requiredColumns).not.toContain("script");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("the columns that genuinely cannot be empty are still required", () => {
|
|
147
|
+
/*
|
|
148
|
+
* The fix must not have relaxed anything else. Each of these is written by
|
|
149
|
+
* enqueue on every job and has no meaningful empty value, so a missing one
|
|
150
|
+
* should still be refused before it reaches Postgres.
|
|
151
|
+
*/
|
|
152
|
+
const requiredColumns: Array<string> = new RunnerJob().getRequiredColumns()
|
|
153
|
+
.columns;
|
|
154
|
+
|
|
155
|
+
for (const column of [
|
|
156
|
+
"projectId",
|
|
157
|
+
"origin",
|
|
158
|
+
"stepId",
|
|
159
|
+
"stepType",
|
|
160
|
+
"status",
|
|
161
|
+
"timeoutInMs",
|
|
162
|
+
"claimDeadlineAt",
|
|
163
|
+
]) {
|
|
164
|
+
expect(requiredColumns).toContain(column);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("payload is optional, since script-carrying jobs have none", () => {
|
|
169
|
+
expect(new RunnerJob().getRequiredColumns().columns).not.toContain(
|
|
170
|
+
"payload",
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("an empty script is rejected by the required-column check, which is why script is not required", () => {
|
|
175
|
+
/*
|
|
176
|
+
* Documents the mechanism rather than assuming it: checkRequiredFields
|
|
177
|
+
* tests truthiness, so "" fails exactly like undefined. Any future column
|
|
178
|
+
* that can legitimately hold an empty string has the same constraint.
|
|
179
|
+
*/
|
|
180
|
+
const row: RunnerJob = new RunnerJob();
|
|
181
|
+
row.projectId = PROJECT_ID;
|
|
182
|
+
row.origin = RunnerJobOrigin.Runbook;
|
|
183
|
+
row.stepId = "step-1";
|
|
184
|
+
row.stepType = RunbookStepType.SSH;
|
|
185
|
+
row.status = RunnerJobStatus.Pending;
|
|
186
|
+
row.timeoutInMs = 30_000;
|
|
187
|
+
row.claimDeadlineAt = new Date();
|
|
188
|
+
row.script = "";
|
|
189
|
+
row.payload = SSH_PAYLOAD;
|
|
190
|
+
|
|
191
|
+
// Passes only because `script` is not in the required set.
|
|
192
|
+
expect(() => {
|
|
193
|
+
return runRealCreateValidation(row);
|
|
194
|
+
}).not.toThrow();
|
|
195
|
+
|
|
196
|
+
const stillRequired: RunnerJob = Object.assign(new RunnerJob(), row);
|
|
197
|
+
stillRequired.stepId = "";
|
|
198
|
+
|
|
199
|
+
// Same falsy value on a column that IS required — still refused.
|
|
200
|
+
expect(() => {
|
|
201
|
+
return runRealCreateValidation(stillRequired);
|
|
202
|
+
}).toThrow(/stepId is required/);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
describe("RunnerJobService.enqueue — what may be dispatched", () => {
|
|
207
|
+
test.each([
|
|
208
|
+
RunbookStepType.Manual,
|
|
209
|
+
RunbookStepType.HttpRequest,
|
|
210
|
+
RunbookStepType.AI,
|
|
211
|
+
])(
|
|
212
|
+
"refuses %s, which no Runner can execute",
|
|
213
|
+
async (type: RunbookStepType) => {
|
|
214
|
+
await expect(
|
|
215
|
+
RunnerJobService.enqueue(enqueueArgs({ stepType: type })),
|
|
216
|
+
).rejects.toThrow(BadDataException);
|
|
217
|
+
|
|
218
|
+
expect(createSpy).not.toHaveBeenCalled();
|
|
219
|
+
},
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
test("refuses a job with no target Runner", async () => {
|
|
223
|
+
await expect(
|
|
224
|
+
RunnerJobService.enqueue(
|
|
225
|
+
enqueueArgs({ targetAgentId: undefined as unknown as ObjectID }),
|
|
226
|
+
),
|
|
227
|
+
).rejects.toThrow(/targetAgentId is required/);
|
|
228
|
+
|
|
229
|
+
expect(createSpy).not.toHaveBeenCalled();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
test.each([RunbookStepType.SSH, RunbookStepType.Kubernetes])(
|
|
233
|
+
"refuses a %s job that arrives without structured instructions",
|
|
234
|
+
async (type: RunbookStepType) => {
|
|
235
|
+
/*
|
|
236
|
+
* The invariant that replaced the required column. A payload-carrying
|
|
237
|
+
* job with neither script nor payload would reach a Runner with nothing
|
|
238
|
+
* to do; the Runner's own guard would fail it, but only after a claim,
|
|
239
|
+
* a lease and a round trip. Refuse it at the source, with a message that
|
|
240
|
+
* names what is missing.
|
|
241
|
+
*/
|
|
242
|
+
await expect(
|
|
243
|
+
RunnerJobService.enqueue(enqueueArgs({ stepType: type, script: "" })),
|
|
244
|
+
).rejects.toThrow(/structured instructions/);
|
|
245
|
+
|
|
246
|
+
expect(createSpy).not.toHaveBeenCalled();
|
|
247
|
+
},
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
test.each([RunbookStepType.Bash, RunbookStepType.JavaScript])(
|
|
251
|
+
"refuses a %s job with an empty script",
|
|
252
|
+
async (type: RunbookStepType) => {
|
|
253
|
+
/*
|
|
254
|
+
* The mirror image. The step executors already short-circuit an empty
|
|
255
|
+
* script as a no-op before dispatching, so this is a backstop for any
|
|
256
|
+
* other caller — and it is the check that used to be (accidentally)
|
|
257
|
+
* provided by the required column.
|
|
258
|
+
*/
|
|
259
|
+
await expect(
|
|
260
|
+
RunnerJobService.enqueue(enqueueArgs({ stepType: type, script: "" })),
|
|
261
|
+
).rejects.toThrow(/needs a script/);
|
|
262
|
+
|
|
263
|
+
expect(createSpy).not.toHaveBeenCalled();
|
|
264
|
+
},
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
test("a payload alone does not satisfy a script-carrying step type", async () => {
|
|
268
|
+
await expect(
|
|
269
|
+
RunnerJobService.enqueue(
|
|
270
|
+
enqueueArgs({
|
|
271
|
+
stepType: RunbookStepType.Bash,
|
|
272
|
+
script: "",
|
|
273
|
+
payload: SSH_PAYLOAD,
|
|
274
|
+
}),
|
|
275
|
+
),
|
|
276
|
+
).rejects.toThrow(/needs a script/);
|
|
277
|
+
|
|
278
|
+
expect(createSpy).not.toHaveBeenCalled();
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
describe("RunnerJobService.enqueue — the row that is written", () => {
|
|
283
|
+
test.each(RUNNER_EXECUTED_STEP_TYPES)(
|
|
284
|
+
"a %s job passes the model's real required-field validation",
|
|
285
|
+
async (type: RunbookStepType) => {
|
|
286
|
+
/*
|
|
287
|
+
* The regression test for #3209, run across every type a Runner can be
|
|
288
|
+
* handed. With `script` required again, the two payload-carrying types
|
|
289
|
+
* fail here exactly as they failed in production.
|
|
290
|
+
*/
|
|
291
|
+
await RunnerJobService.enqueue(validArgsFor(type));
|
|
292
|
+
|
|
293
|
+
const row: RunnerJob = createdRow();
|
|
294
|
+
|
|
295
|
+
expect(() => {
|
|
296
|
+
return runRealCreateValidation(row);
|
|
297
|
+
}).not.toThrow();
|
|
298
|
+
},
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
test.each([RunbookStepType.SSH, RunbookStepType.Kubernetes])(
|
|
302
|
+
"a %s job is written with an empty script and its payload",
|
|
303
|
+
async (type: RunbookStepType) => {
|
|
304
|
+
await RunnerJobService.enqueue(validArgsFor(type));
|
|
305
|
+
|
|
306
|
+
const row: RunnerJob = createdRow();
|
|
307
|
+
|
|
308
|
+
expect(row.script).toBe("");
|
|
309
|
+
expect(row.payload).toEqual(SSH_PAYLOAD);
|
|
310
|
+
expect(row.stepType).toBe(type);
|
|
311
|
+
},
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
test("an undefined script becomes an empty string, never undefined", async () => {
|
|
315
|
+
/*
|
|
316
|
+
* The column is NOT NULL in Postgres. Relaxing the application-level check
|
|
317
|
+
* must not let an undefined value through to the database, where it would
|
|
318
|
+
* surface as a driver error instead of a readable one.
|
|
319
|
+
*/
|
|
320
|
+
await RunnerJobService.enqueue(
|
|
321
|
+
enqueueArgs({
|
|
322
|
+
stepType: RunbookStepType.SSH,
|
|
323
|
+
script: undefined as unknown as string,
|
|
324
|
+
payload: SSH_PAYLOAD,
|
|
325
|
+
}),
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
const row: RunnerJob = createdRow();
|
|
329
|
+
|
|
330
|
+
expect(row.script).toBe("");
|
|
331
|
+
expect(typeof row.script).toBe("string");
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test("a script-carrying job keeps its script verbatim and gets no payload key", async () => {
|
|
335
|
+
const script: string = "pg_dump -Fc app | gzip > /backup/$(date +%F).gz";
|
|
336
|
+
|
|
337
|
+
await RunnerJobService.enqueue(
|
|
338
|
+
enqueueArgs({ stepType: RunbookStepType.Bash, script }),
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
const row: RunnerJob = createdRow();
|
|
342
|
+
|
|
343
|
+
expect(row.script).toBe(script);
|
|
344
|
+
expect(row.payload).toBeUndefined();
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
test("a runbook job carries Runbook provenance and starts Pending", async () => {
|
|
348
|
+
await RunnerJobService.enqueue(validArgsFor(RunbookStepType.SSH));
|
|
349
|
+
|
|
350
|
+
const row: RunnerJob = createdRow();
|
|
351
|
+
|
|
352
|
+
expect(row.origin).toBe(RunnerJobOrigin.Runbook);
|
|
353
|
+
expect(row.status).toBe(RunnerJobStatus.Pending);
|
|
354
|
+
expect(row.projectId?.toString()).toBe(PROJECT_ID.toString());
|
|
355
|
+
expect(row.runbookExecutionId?.toString()).toBe(EXECUTION_ID.toString());
|
|
356
|
+
expect(row.targetAgentId?.toString()).toBe(AGENT_ID.toString());
|
|
357
|
+
expect(row.stepId).toBe("step-1");
|
|
358
|
+
expect(row.timeoutInMs).toBe(30_000);
|
|
359
|
+
// Never pre-assigned: a job belongs to whoever wins the claim.
|
|
360
|
+
expect(row.assignedAgentId).toBeUndefined();
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
test("the claim deadline honours the configured claim timeout", async () => {
|
|
364
|
+
const before: number = Date.now();
|
|
365
|
+
|
|
366
|
+
await RunnerJobService.enqueue(
|
|
367
|
+
enqueueArgs({
|
|
368
|
+
stepType: RunbookStepType.SSH,
|
|
369
|
+
script: "",
|
|
370
|
+
payload: SSH_PAYLOAD,
|
|
371
|
+
claimTimeoutInMs: 300_000,
|
|
372
|
+
}),
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
const after: number = Date.now();
|
|
376
|
+
const deadlineMs: number = createdRow().claimDeadlineAt!.getTime();
|
|
377
|
+
|
|
378
|
+
expect(deadlineMs).toBeGreaterThanOrEqual(before + 300_000);
|
|
379
|
+
expect(deadlineMs).toBeLessThanOrEqual(after + 300_000);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("an SSH job row never carries credential material", async () => {
|
|
383
|
+
/*
|
|
384
|
+
* The reason the payload exists at all: the row is readable by anyone who
|
|
385
|
+
* can read the execution, so it references a credential by id and the
|
|
386
|
+
* secret is resolved at claim time, scoped to the claiming Runner.
|
|
387
|
+
*/
|
|
388
|
+
await RunnerJobService.enqueue(validArgsFor(RunbookStepType.SSH));
|
|
389
|
+
|
|
390
|
+
const serialized: string = JSON.stringify(createdRow());
|
|
391
|
+
|
|
392
|
+
for (const marker of [
|
|
393
|
+
"privateKey",
|
|
394
|
+
"passphrase",
|
|
395
|
+
"password",
|
|
396
|
+
"hostname",
|
|
397
|
+
"username",
|
|
398
|
+
"BEGIN OPENSSH PRIVATE KEY",
|
|
399
|
+
]) {
|
|
400
|
+
expect(serialized).not.toContain(marker);
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("the create is performed as root, since no user owns a dispatched job", async () => {
|
|
405
|
+
await RunnerJobService.enqueue(validArgsFor(RunbookStepType.SSH));
|
|
406
|
+
|
|
407
|
+
const args: { props: Record<string, unknown> } = createSpy.mock
|
|
408
|
+
.calls[0]![0] as { props: Record<string, unknown> };
|
|
409
|
+
|
|
410
|
+
expect(args.props).toEqual(expect.objectContaining({ isRoot: true }));
|
|
411
|
+
});
|
|
412
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { describe, expect, it } from "@jest/globals";
|
|
2
|
+
import {
|
|
3
|
+
SESSION_REPLAY_PRICE_IN_USD_PER_GB,
|
|
4
|
+
TELEMETRY_PRICE_IN_USD_PER_GB,
|
|
5
|
+
TELEMETRY_PRICE_RETENTION_IN_DAYS,
|
|
6
|
+
} from "../../../../Types/Billing/PayAsYouGoPricing";
|
|
7
|
+
import {
|
|
8
|
+
LogDataIngestMeteredPlan,
|
|
9
|
+
MetricsDataIngestMeteredPlan,
|
|
10
|
+
ProfilesDataIngestMeteredPlan,
|
|
11
|
+
SessionReplayDataIngestMeteredPlan,
|
|
12
|
+
TracesDataIngestMetredPlan,
|
|
13
|
+
} from "../../../../Server/Types/Billing/MeteredPlan/AllMeteredPlans";
|
|
14
|
+
import TelemetryMeteredPlan from "../../../../Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan";
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
* The dashboard now quotes the telemetry rate to Free plan users before they
|
|
18
|
+
* create an ingestion key. A quoted price that does not match the metered one
|
|
19
|
+
* is a promise we break on the invoice, so the two are pinned together here:
|
|
20
|
+
* the metered plans must charge exactly the advertised per-GB rate at the
|
|
21
|
+
* advertised retention.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/*
|
|
25
|
+
* Session replay rides on the same ingestion key at its own, much higher rate,
|
|
26
|
+
* so it is listed here with its own expected figure rather than left out - a
|
|
27
|
+
* plan missing from this table is a plan whose price the UI can misquote.
|
|
28
|
+
*/
|
|
29
|
+
const TELEMETRY_PLANS: Array<{
|
|
30
|
+
name: string;
|
|
31
|
+
plan: TelemetryMeteredPlan;
|
|
32
|
+
pricePerGB: number;
|
|
33
|
+
}> = [
|
|
34
|
+
{
|
|
35
|
+
name: "logs",
|
|
36
|
+
plan: LogDataIngestMeteredPlan,
|
|
37
|
+
pricePerGB: TELEMETRY_PRICE_IN_USD_PER_GB,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: "metrics",
|
|
41
|
+
plan: MetricsDataIngestMeteredPlan,
|
|
42
|
+
pricePerGB: TELEMETRY_PRICE_IN_USD_PER_GB,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "traces",
|
|
46
|
+
plan: TracesDataIngestMetredPlan,
|
|
47
|
+
pricePerGB: TELEMETRY_PRICE_IN_USD_PER_GB,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: "profiles",
|
|
51
|
+
plan: ProfilesDataIngestMeteredPlan,
|
|
52
|
+
pricePerGB: TELEMETRY_PRICE_IN_USD_PER_GB,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: "session replay",
|
|
56
|
+
plan: SessionReplayDataIngestMeteredPlan,
|
|
57
|
+
pricePerGB: SESSION_REPLAY_PRICE_IN_USD_PER_GB,
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
describe("Telemetry metered plans charge the advertised pay as you go rate", () => {
|
|
62
|
+
it.each(TELEMETRY_PLANS)(
|
|
63
|
+
"$name: 1 GB retained for the advertised window costs the advertised per-GB price",
|
|
64
|
+
({
|
|
65
|
+
plan,
|
|
66
|
+
pricePerGB,
|
|
67
|
+
}: {
|
|
68
|
+
plan: TelemetryMeteredPlan;
|
|
69
|
+
pricePerGB: number;
|
|
70
|
+
}) => {
|
|
71
|
+
expect(
|
|
72
|
+
plan.getTotalCostInUSD({
|
|
73
|
+
dataIngestedInGB: 1,
|
|
74
|
+
retentionInDays: TELEMETRY_PRICE_RETENTION_IN_DAYS,
|
|
75
|
+
}),
|
|
76
|
+
).toBeCloseTo(pricePerGB, 10);
|
|
77
|
+
},
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
it("prices session replay well above the other pillars, as intended", () => {
|
|
81
|
+
/*
|
|
82
|
+
* Not incidental: the gap is why in-app copy cannot quote one rate for
|
|
83
|
+
* "data sent with this ingestion key".
|
|
84
|
+
*/
|
|
85
|
+
expect(SESSION_REPLAY_PRICE_IN_USD_PER_GB).toBeGreaterThan(
|
|
86
|
+
TELEMETRY_PRICE_IN_USD_PER_GB,
|
|
87
|
+
);
|
|
88
|
+
expect(SESSION_REPLAY_PRICE_IN_USD_PER_GB).toBe(2);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it.each(TELEMETRY_PLANS)(
|
|
92
|
+
"$name: cost scales linearly with volume and retention",
|
|
93
|
+
({
|
|
94
|
+
plan,
|
|
95
|
+
pricePerGB,
|
|
96
|
+
}: {
|
|
97
|
+
plan: TelemetryMeteredPlan;
|
|
98
|
+
pricePerGB: number;
|
|
99
|
+
}) => {
|
|
100
|
+
expect(
|
|
101
|
+
plan.getTotalCostInUSD({
|
|
102
|
+
dataIngestedInGB: 10,
|
|
103
|
+
retentionInDays: TELEMETRY_PRICE_RETENTION_IN_DAYS,
|
|
104
|
+
}),
|
|
105
|
+
).toBeCloseTo(pricePerGB * 10, 10);
|
|
106
|
+
|
|
107
|
+
// Twice the advertised retention is twice the advertised price.
|
|
108
|
+
expect(
|
|
109
|
+
plan.getTotalCostInUSD({
|
|
110
|
+
dataIngestedInGB: 1,
|
|
111
|
+
retentionInDays: TELEMETRY_PRICE_RETENTION_IN_DAYS * 2,
|
|
112
|
+
}),
|
|
113
|
+
).toBeCloseTo(pricePerGB * 2, 10);
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
it("charges nothing for no data", () => {
|
|
118
|
+
expect(
|
|
119
|
+
LogDataIngestMeteredPlan.getTotalCostInUSD({
|
|
120
|
+
dataIngestedInGB: 0,
|
|
121
|
+
retentionInDays: TELEMETRY_PRICE_RETENTION_IN_DAYS,
|
|
122
|
+
}),
|
|
123
|
+
).toBe(0);
|
|
124
|
+
});
|
|
125
|
+
});
|