@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,368 @@
|
|
|
1
|
+
import Entities from "../../../../Models/DatabaseModels/Index";
|
|
2
|
+
import InventoryItem from "../../../../Models/DatabaseModels/InventoryItem";
|
|
3
|
+
import QueryUtil from "../../../../Server/Types/Database/QueryUtil";
|
|
4
|
+
import Query from "../../../../Types/BaseDatabase/Query";
|
|
5
|
+
import Includes from "../../../../Types/BaseDatabase/Includes";
|
|
6
|
+
import IncludesNone from "../../../../Types/BaseDatabase/IncludesNone";
|
|
7
|
+
import LessThan from "../../../../Types/BaseDatabase/LessThan";
|
|
8
|
+
import { JSONObject } from "../../../../Types/JSON";
|
|
9
|
+
import JSONFunctions from "../../../../Types/JSONFunctions";
|
|
10
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
11
|
+
import EntitySource from "../../../../Types/Telemetry/EntitySource";
|
|
12
|
+
import EntityType from "../../../../Types/Telemetry/EntityType";
|
|
13
|
+
import {
|
|
14
|
+
InventoryLiveness,
|
|
15
|
+
getInventoryLivenessState,
|
|
16
|
+
} from "../../../../Types/Telemetry/InventoryLiveness";
|
|
17
|
+
import { DataSource, FindOptionsWhere, QueryRunner } from "typeorm";
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
* Opt in with RUN_POSTGRES_INVENTORY_FACET_TESTS=true and config.env loaded.
|
|
21
|
+
* Tables live in a unique temporary schema; production rows are never read
|
|
22
|
+
* or changed. A transaction freezes CURRENT_TIMESTAMP for exact boundaries.
|
|
23
|
+
*/
|
|
24
|
+
const describePostgres: typeof describe =
|
|
25
|
+
process.env["RUN_POSTGRES_INVENTORY_FACET_TESTS"] === "true"
|
|
26
|
+
? describe
|
|
27
|
+
: describe.skip;
|
|
28
|
+
|
|
29
|
+
describePostgres("inventory status facets against Postgres", () => {
|
|
30
|
+
const schema: string = `inventory_facets_${ObjectID.generate().toString().replace(/-/g, "")}`;
|
|
31
|
+
const projectId: ObjectID = ObjectID.generate();
|
|
32
|
+
const otherProjectId: ObjectID = ObjectID.generate();
|
|
33
|
+
const ids: Record<string, string> = {};
|
|
34
|
+
let database: DataSource;
|
|
35
|
+
let runner: QueryRunner;
|
|
36
|
+
let now: Date;
|
|
37
|
+
|
|
38
|
+
beforeAll(async () => {
|
|
39
|
+
database = new DataSource({
|
|
40
|
+
type: "postgres",
|
|
41
|
+
host: process.env["INVENTORY_FACET_TEST_DATABASE_HOST"] || "localhost",
|
|
42
|
+
port: Number(process.env["INVENTORY_FACET_TEST_DATABASE_PORT"] || "5400"),
|
|
43
|
+
username: process.env["DATABASE_USERNAME"] || "postgres",
|
|
44
|
+
password: process.env["DATABASE_PASSWORD"] || "password",
|
|
45
|
+
database: process.env["DATABASE_NAME"] || "oneuptimedb",
|
|
46
|
+
entities: Entities,
|
|
47
|
+
schema,
|
|
48
|
+
synchronize: false,
|
|
49
|
+
extra: { options: `-c search_path=${schema},public` },
|
|
50
|
+
});
|
|
51
|
+
await database.initialize();
|
|
52
|
+
await database.query(`CREATE SCHEMA "${schema}"`);
|
|
53
|
+
/*
|
|
54
|
+
* A standalone temporary table lets this suite run on an empty Postgres
|
|
55
|
+
* instance too. Its columns are the physical inventory columns used by
|
|
56
|
+
* these queries; the status itself must never exist on disk.
|
|
57
|
+
*/
|
|
58
|
+
await database.query(`CREATE TABLE "${schema}"."InventoryItem" (
|
|
59
|
+
"_id" uuid PRIMARY KEY,
|
|
60
|
+
"projectId" uuid NOT NULL,
|
|
61
|
+
"displayName" varchar NOT NULL,
|
|
62
|
+
"entityType" varchar NOT NULL,
|
|
63
|
+
"source" varchar,
|
|
64
|
+
"lastSeenAt" timestamptz,
|
|
65
|
+
"isArchived" boolean NOT NULL DEFAULT false,
|
|
66
|
+
"deletedAt" timestamptz
|
|
67
|
+
)`);
|
|
68
|
+
runner = database.createQueryRunner();
|
|
69
|
+
await runner.connect();
|
|
70
|
+
await runner.startTransaction();
|
|
71
|
+
now = (await runner.query("SELECT CURRENT_TIMESTAMP AS now"))[0].now;
|
|
72
|
+
|
|
73
|
+
for (const [name, age] of [
|
|
74
|
+
["live-future", -60_000],
|
|
75
|
+
["live-now", 0],
|
|
76
|
+
["live-upper-boundary", 31 * 60_000 - 1],
|
|
77
|
+
["recent-lower-boundary", 31 * 60_000],
|
|
78
|
+
["recent-upper-boundary", 1441 * 60_000 - 1],
|
|
79
|
+
["stale-lower-boundary", 1441 * 60_000],
|
|
80
|
+
["stale-old", 3 * 1440 * 60_000],
|
|
81
|
+
] as Array<[string, number]>) {
|
|
82
|
+
await seed(name, EntitySource.Discovered, age);
|
|
83
|
+
}
|
|
84
|
+
await seed("never-seen", EntitySource.Discovered, null);
|
|
85
|
+
await seed("manual-old", EntitySource.Manual, 365 * 1440 * 60_000);
|
|
86
|
+
await seed("mirrored-no-heartbeat", EntitySource.Inventory, null);
|
|
87
|
+
await seed("unknown-source", "unknown", 365 * 1440 * 60_000);
|
|
88
|
+
await seed("missing-source", null, null);
|
|
89
|
+
await seed("archived-stale", EntitySource.Discovered, 3000 * 60_000, {
|
|
90
|
+
archived: true,
|
|
91
|
+
});
|
|
92
|
+
await seed("foreign-stale", EntitySource.Discovered, 3000 * 60_000, {
|
|
93
|
+
projectId: otherProjectId,
|
|
94
|
+
});
|
|
95
|
+
await seed("host-stale", EntitySource.Discovered, 3000 * 60_000, {
|
|
96
|
+
entityType: EntityType.Host,
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
afterAll(async () => {
|
|
101
|
+
if (runner) {
|
|
102
|
+
if (runner.isTransactionActive) {
|
|
103
|
+
await runner.rollbackTransaction();
|
|
104
|
+
}
|
|
105
|
+
await runner.release();
|
|
106
|
+
}
|
|
107
|
+
if (database?.isInitialized) {
|
|
108
|
+
await database.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`);
|
|
109
|
+
await database.destroy();
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
async function seed(
|
|
114
|
+
name: string,
|
|
115
|
+
source: string | null,
|
|
116
|
+
age: number | null,
|
|
117
|
+
options: {
|
|
118
|
+
archived?: boolean;
|
|
119
|
+
projectId?: ObjectID;
|
|
120
|
+
entityType?: EntityType;
|
|
121
|
+
} = {},
|
|
122
|
+
): Promise<void> {
|
|
123
|
+
const id: string = ObjectID.generate().toString();
|
|
124
|
+
ids[name] = id;
|
|
125
|
+
await runner.query(
|
|
126
|
+
`INSERT INTO "${schema}"."InventoryItem" ("_id", "projectId", "displayName", "entityType", "source", "lastSeenAt", "isArchived") VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
127
|
+
[
|
|
128
|
+
id,
|
|
129
|
+
(options.projectId || projectId).toString(),
|
|
130
|
+
name,
|
|
131
|
+
options.entityType || EntityType.Service,
|
|
132
|
+
source,
|
|
133
|
+
age === null ? null : new Date(now.getTime() - age),
|
|
134
|
+
Boolean(options.archived),
|
|
135
|
+
],
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function where(
|
|
140
|
+
query: Query<InventoryItem> = {},
|
|
141
|
+
): FindOptionsWhere<InventoryItem> {
|
|
142
|
+
return QueryUtil.serializeQuery(InventoryItem, {
|
|
143
|
+
projectId,
|
|
144
|
+
isArchived: false,
|
|
145
|
+
...query,
|
|
146
|
+
}) as unknown as FindOptionsWhere<InventoryItem>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function find(
|
|
150
|
+
query: Query<InventoryItem> = {},
|
|
151
|
+
): Promise<Array<InventoryItem>> {
|
|
152
|
+
return runner.manager.getRepository(InventoryItem).find({
|
|
153
|
+
select: {
|
|
154
|
+
_id: true,
|
|
155
|
+
displayName: true,
|
|
156
|
+
source: true,
|
|
157
|
+
lastSeenAt: true,
|
|
158
|
+
inventoryStatus: true,
|
|
159
|
+
},
|
|
160
|
+
where: where(query),
|
|
161
|
+
order: { displayName: "ASC" },
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function names(query: Query<InventoryItem>): Promise<Array<string>> {
|
|
166
|
+
return (await find(query)).map((item: InventoryItem): string => {
|
|
167
|
+
return item.displayName!;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
test("every SQL classification matches the shared badge rule at exact boundaries", async () => {
|
|
172
|
+
const rows: Array<InventoryItem> = await find();
|
|
173
|
+
expect(rows).toHaveLength(13);
|
|
174
|
+
for (const item of rows) {
|
|
175
|
+
expect({ name: item.displayName, status: item.inventoryStatus }).toEqual({
|
|
176
|
+
name: item.displayName,
|
|
177
|
+
status: getInventoryLivenessState({
|
|
178
|
+
source: item.source,
|
|
179
|
+
lastSeenAt: item.lastSeenAt,
|
|
180
|
+
now,
|
|
181
|
+
}),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test.each([
|
|
187
|
+
[
|
|
188
|
+
InventoryLiveness.Live,
|
|
189
|
+
["live-future", "live-now", "live-upper-boundary"],
|
|
190
|
+
],
|
|
191
|
+
[
|
|
192
|
+
InventoryLiveness.Recent,
|
|
193
|
+
["recent-lower-boundary", "recent-upper-boundary"],
|
|
194
|
+
],
|
|
195
|
+
[
|
|
196
|
+
InventoryLiveness.Stale,
|
|
197
|
+
["host-stale", "stale-lower-boundary", "stale-old"],
|
|
198
|
+
],
|
|
199
|
+
[InventoryLiveness.Never, ["never-seen"]],
|
|
200
|
+
[
|
|
201
|
+
InventoryLiveness.NotTracked,
|
|
202
|
+
[
|
|
203
|
+
"manual-old",
|
|
204
|
+
"mirrored-no-heartbeat",
|
|
205
|
+
"missing-source",
|
|
206
|
+
"unknown-source",
|
|
207
|
+
],
|
|
208
|
+
],
|
|
209
|
+
] as Array<[InventoryLiveness, Array<string>]>)(
|
|
210
|
+
"%s selects precisely the matching rows",
|
|
211
|
+
async (status: InventoryLiveness, expected: Array<string>) => {
|
|
212
|
+
expect(await names({ inventoryStatus: new Includes([status]) })).toEqual(
|
|
213
|
+
expected,
|
|
214
|
+
);
|
|
215
|
+
},
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
test("multiple statuses are a union and negation is its complement including untracked rows", async () => {
|
|
219
|
+
const selected: Array<string> = await names({
|
|
220
|
+
inventoryStatus: new Includes([
|
|
221
|
+
InventoryLiveness.Stale,
|
|
222
|
+
InventoryLiveness.Never,
|
|
223
|
+
]),
|
|
224
|
+
});
|
|
225
|
+
expect(selected).toEqual([
|
|
226
|
+
"host-stale",
|
|
227
|
+
"never-seen",
|
|
228
|
+
"stale-lower-boundary",
|
|
229
|
+
"stale-old",
|
|
230
|
+
]);
|
|
231
|
+
const excluded: Array<string> = await names({
|
|
232
|
+
inventoryStatus: new IncludesNone([
|
|
233
|
+
InventoryLiveness.Stale,
|
|
234
|
+
InventoryLiveness.Never,
|
|
235
|
+
]),
|
|
236
|
+
});
|
|
237
|
+
expect(excluded).toHaveLength(9);
|
|
238
|
+
expect(excluded).toEqual(
|
|
239
|
+
expect.arrayContaining(["manual-old", "missing-source"]),
|
|
240
|
+
);
|
|
241
|
+
expect(new Set([...selected, ...excluded])).toEqual(
|
|
242
|
+
new Set(await names({})),
|
|
243
|
+
);
|
|
244
|
+
expect(
|
|
245
|
+
selected.filter((name: string): boolean => {
|
|
246
|
+
return excluded.includes(name);
|
|
247
|
+
}),
|
|
248
|
+
).toEqual([]);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("status preserves type and source filters", async () => {
|
|
252
|
+
expect(
|
|
253
|
+
await names({
|
|
254
|
+
inventoryStatus: new Includes([InventoryLiveness.Stale]),
|
|
255
|
+
entityType: new Includes([EntityType.Host]),
|
|
256
|
+
}),
|
|
257
|
+
).toEqual(["host-stale"]);
|
|
258
|
+
expect(
|
|
259
|
+
await names({
|
|
260
|
+
inventoryStatus: new Includes([InventoryLiveness.Stale]),
|
|
261
|
+
source: EntitySource.Manual,
|
|
262
|
+
}),
|
|
263
|
+
).toEqual([]);
|
|
264
|
+
expect(
|
|
265
|
+
await names({
|
|
266
|
+
inventoryStatus: new Includes([InventoryLiveness.NotTracked]),
|
|
267
|
+
source: EntitySource.Manual,
|
|
268
|
+
}),
|
|
269
|
+
).toEqual(["manual-old"]);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("last seen dates intersect status without overwriting it", async () => {
|
|
273
|
+
expect(
|
|
274
|
+
await names({
|
|
275
|
+
inventoryStatus: new Includes([InventoryLiveness.Stale]),
|
|
276
|
+
lastSeenAt: new LessThan(
|
|
277
|
+
new Date(now.getTime() - 3500 * 60_000).toISOString(),
|
|
278
|
+
),
|
|
279
|
+
}),
|
|
280
|
+
).toEqual(["stale-old"]);
|
|
281
|
+
expect(
|
|
282
|
+
await names({
|
|
283
|
+
inventoryStatus: new Includes([InventoryLiveness.Live]),
|
|
284
|
+
lastSeenAt: new LessThan(
|
|
285
|
+
new Date(now.getTime() - 60 * 60_000).toISOString(),
|
|
286
|
+
),
|
|
287
|
+
}),
|
|
288
|
+
).toEqual([]);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("status preserves explicit ids, project isolation and archived views", async () => {
|
|
292
|
+
expect(
|
|
293
|
+
await names({
|
|
294
|
+
inventoryStatus: new Includes([InventoryLiveness.Stale]),
|
|
295
|
+
_id: ids["stale-old"]!,
|
|
296
|
+
}),
|
|
297
|
+
).toEqual(["stale-old"]);
|
|
298
|
+
expect(
|
|
299
|
+
await names({
|
|
300
|
+
inventoryStatus: new Includes([InventoryLiveness.Stale]),
|
|
301
|
+
_id: ids["foreign-stale"]!,
|
|
302
|
+
}),
|
|
303
|
+
).toEqual([]);
|
|
304
|
+
expect(
|
|
305
|
+
await names({
|
|
306
|
+
inventoryStatus: new Includes([InventoryLiveness.Stale]),
|
|
307
|
+
isArchived: true,
|
|
308
|
+
}),
|
|
309
|
+
).toEqual(["archived-stale"]);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test("count and pagination use the same status predicate", async () => {
|
|
313
|
+
const [rows, count]: [Array<InventoryItem>, number] = await runner.manager
|
|
314
|
+
.getRepository(InventoryItem)
|
|
315
|
+
.findAndCount({
|
|
316
|
+
select: { _id: true, displayName: true },
|
|
317
|
+
where: where({
|
|
318
|
+
inventoryStatus: new Includes([
|
|
319
|
+
InventoryLiveness.Live,
|
|
320
|
+
InventoryLiveness.Recent,
|
|
321
|
+
]),
|
|
322
|
+
}),
|
|
323
|
+
order: { displayName: "ASC" },
|
|
324
|
+
skip: 2,
|
|
325
|
+
take: 2,
|
|
326
|
+
});
|
|
327
|
+
expect(count).toBe(5);
|
|
328
|
+
expect(
|
|
329
|
+
rows.map((item: InventoryItem): string => {
|
|
330
|
+
return item.displayName!;
|
|
331
|
+
}),
|
|
332
|
+
).toEqual(["live-upper-boundary", "recent-lower-boundary"]);
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
test("a serialized API query applies the same status filter", async () => {
|
|
336
|
+
const serialized: JSONObject = JSON.parse(
|
|
337
|
+
JSON.stringify(
|
|
338
|
+
JSONFunctions.serialize({
|
|
339
|
+
inventoryStatus: new Includes([
|
|
340
|
+
InventoryLiveness.Never,
|
|
341
|
+
InventoryLiveness.Recent,
|
|
342
|
+
]),
|
|
343
|
+
entityType: new Includes([EntityType.Service]),
|
|
344
|
+
}),
|
|
345
|
+
),
|
|
346
|
+
);
|
|
347
|
+
expect(
|
|
348
|
+
await names(
|
|
349
|
+
JSONFunctions.deserialize(serialized) as Query<InventoryItem>,
|
|
350
|
+
),
|
|
351
|
+
).toEqual(["never-seen", "recent-lower-boundary", "recent-upper-boundary"]);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test("the empty schema needs no physical status column", async () => {
|
|
355
|
+
const columns: Array<{ column_name: string }> = await runner.query(
|
|
356
|
+
"SELECT column_name FROM information_schema.columns WHERE table_schema = $1 AND table_name = 'InventoryItem'",
|
|
357
|
+
[schema],
|
|
358
|
+
);
|
|
359
|
+
expect(
|
|
360
|
+
columns.map((column: { column_name: string }): string => {
|
|
361
|
+
return column.column_name;
|
|
362
|
+
}),
|
|
363
|
+
).not.toContain("inventoryStatus");
|
|
364
|
+
expect(
|
|
365
|
+
await names({ inventoryStatus: new Includes([InventoryLiveness.Never]) }),
|
|
366
|
+
).toEqual(["never-seen"]);
|
|
367
|
+
});
|
|
368
|
+
});
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import ReadPermission from "../../../../../Server/Types/Database/Permissions/ReadPermission";
|
|
2
|
+
import QueryUtil from "../../../../../Server/Types/Database/QueryUtil";
|
|
2
3
|
import Incident from "../../../../../Models/DatabaseModels/Incident";
|
|
3
4
|
import Monitor from "../../../../../Models/DatabaseModels/Monitor";
|
|
4
5
|
import DatabaseCommonInteractionProps from "../../../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
6
|
+
import Includes from "../../../../../Types/BaseDatabase/Includes";
|
|
7
|
+
import IncludesAnyOfGroups from "../../../../../Types/BaseDatabase/IncludesAnyOfGroups";
|
|
8
|
+
import IncludesNone from "../../../../../Types/BaseDatabase/IncludesNone";
|
|
9
|
+
import EqualTo from "../../../../../Types/BaseDatabase/EqualTo";
|
|
10
|
+
import NotEqual from "../../../../../Types/BaseDatabase/NotEqual";
|
|
11
|
+
import Search from "../../../../../Types/BaseDatabase/Search";
|
|
12
|
+
import BadDataException from "../../../../../Types/Exception/BadDataException";
|
|
5
13
|
import NotAuthorizedException from "../../../../../Types/Exception/NotAuthorizedException";
|
|
6
14
|
import ObjectID from "../../../../../Types/ObjectID";
|
|
7
15
|
import Permission, {
|
|
8
16
|
UserPermission,
|
|
9
17
|
UserTenantAccessPermission,
|
|
10
18
|
} from "../../../../../Types/Permission";
|
|
11
|
-
import { FindOperator } from "typeorm";
|
|
19
|
+
import { FindOperator, In } from "typeorm";
|
|
12
20
|
|
|
13
21
|
describe("ReadPermission.checkReadBlockPermission", () => {
|
|
14
22
|
const projectId: ObjectID = ObjectID.generate();
|
|
@@ -16,6 +24,26 @@ describe("ReadPermission.checkReadBlockPermission", () => {
|
|
|
16
24
|
const blockedLabelA: ObjectID = ObjectID.generate();
|
|
17
25
|
const blockedLabelB: ObjectID = ObjectID.generate();
|
|
18
26
|
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
jest
|
|
29
|
+
.spyOn(QueryUtil, "getManyToManyRelationMetadata")
|
|
30
|
+
.mockImplementation((modelType: any, column: string) => {
|
|
31
|
+
if (column !== "labels") {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
joinTableName:
|
|
36
|
+
modelType === Monitor ? "MonitorLabel" : "IncidentLabel",
|
|
37
|
+
ownerColumnName: modelType === Monitor ? "monitorId" : "incidentId",
|
|
38
|
+
relationColumnName: "labelId",
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
jest.restoreAllMocks();
|
|
45
|
+
});
|
|
46
|
+
|
|
19
47
|
function makeProps(
|
|
20
48
|
permissions: Array<UserPermission>,
|
|
21
49
|
): DatabaseCommonInteractionProps {
|
|
@@ -59,18 +87,21 @@ describe("ReadPermission.checkReadBlockPermission", () => {
|
|
|
59
87
|
}
|
|
60
88
|
|
|
61
89
|
/*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* counterpart, so read the rendered SQL back and return the ids it denies.
|
|
90
|
+
* Read the owner-row exclusion predicate, including the relation table it
|
|
91
|
+
* uses, so the test checks that label blocks are composed independently of
|
|
92
|
+
* the caller's relation filters.
|
|
66
93
|
*/
|
|
67
|
-
function deniedLabelIds(
|
|
94
|
+
function deniedLabelIds(
|
|
95
|
+
operator: any,
|
|
96
|
+
joinTableName: string = "IncidentLabel",
|
|
97
|
+
): Array<string> {
|
|
68
98
|
expect(operator).toBeInstanceOf(FindOperator);
|
|
69
99
|
expect(operator.type).toBe("raw");
|
|
70
100
|
|
|
71
|
-
const sql: string = operator.getSql("
|
|
72
|
-
expect(sql).toContain("NOT IN");
|
|
73
|
-
expect(sql).toContain(
|
|
101
|
+
const sql: string = operator.getSql("ownerId");
|
|
102
|
+
expect(sql).toContain("ownerId NOT IN (SELECT");
|
|
103
|
+
expect(sql).toContain(`FROM "${joinTableName}"`);
|
|
104
|
+
expect(sql).toContain(`WHERE "${joinTableName}"."labelId" IN (`);
|
|
74
105
|
|
|
75
106
|
return Object.values(
|
|
76
107
|
operator.objectLiteralParameters as Record<string, Array<string>>,
|
|
@@ -136,8 +167,8 @@ describe("ReadPermission.checkReadBlockPermission", () => {
|
|
|
136
167
|
makeProps([blockPermission(Permission.ProjectMember, [blockedLabelA])]),
|
|
137
168
|
);
|
|
138
169
|
|
|
139
|
-
expect(result.query.labels).
|
|
140
|
-
expect(deniedLabelIds(result.query.
|
|
170
|
+
expect(result.query.labels).toBeUndefined();
|
|
171
|
+
expect(deniedLabelIds(result.query._id)).toEqual([
|
|
141
172
|
blockedLabelA.toString(),
|
|
142
173
|
]);
|
|
143
174
|
// The caller's own filters survive the rewrite.
|
|
@@ -156,7 +187,7 @@ describe("ReadPermission.checkReadBlockPermission", () => {
|
|
|
156
187
|
]),
|
|
157
188
|
);
|
|
158
189
|
|
|
159
|
-
expect(deniedLabelIds(result.query.
|
|
190
|
+
expect(deniedLabelIds(result.query._id)).toEqual([
|
|
160
191
|
blockedLabelA.toString(),
|
|
161
192
|
blockedLabelB.toString(),
|
|
162
193
|
]);
|
|
@@ -204,9 +235,225 @@ describe("ReadPermission.checkReadBlockPermission", () => {
|
|
|
204
235
|
makeProps([blockPermission(Permission.ProjectMember, [blockedLabelA])]),
|
|
205
236
|
);
|
|
206
237
|
|
|
207
|
-
expect(result.query.labels).
|
|
208
|
-
expect(deniedLabelIds(result.query.
|
|
238
|
+
expect(result.query.labels).toBeUndefined();
|
|
239
|
+
expect(deniedLabelIds(result.query._id, "MonitorLabel")).toEqual([
|
|
240
|
+
blockedLabelA.toString(),
|
|
241
|
+
]);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("preserves an existing label selection while adding the block condition", async () => {
|
|
245
|
+
const labels: Includes = new Includes([ObjectID.generate().toString()]);
|
|
246
|
+
const query: any = { projectId, labels };
|
|
247
|
+
|
|
248
|
+
const result: any = await ReadPermission.checkReadBlockPermission(
|
|
249
|
+
Monitor,
|
|
250
|
+
query,
|
|
251
|
+
makeProps([blockPermission(Permission.ProjectMember, [blockedLabelA])]),
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
expect(result.query.labels).toBe(labels);
|
|
255
|
+
expect(result.query.projectId).toBe(projectId);
|
|
256
|
+
expect(deniedLabelIds(result.query._id, "MonitorLabel")).toEqual([
|
|
257
|
+
blockedLabelA.toString(),
|
|
258
|
+
]);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("preserves grouped dashboard labels, project, status, and the selected record through query serialization", async () => {
|
|
262
|
+
const monitorId: ObjectID = ObjectID.generate();
|
|
263
|
+
const fixedLabelId: string = ObjectID.generate().toString();
|
|
264
|
+
const selectedLabelId: string = ObjectID.generate().toString();
|
|
265
|
+
const labels: IncludesAnyOfGroups = new IncludesAnyOfGroups([
|
|
266
|
+
[fixedLabelId],
|
|
267
|
+
[selectedLabelId],
|
|
268
|
+
]);
|
|
269
|
+
const currentMonitorStatusId: ObjectID = ObjectID.generate();
|
|
270
|
+
const query: any = {
|
|
271
|
+
projectId,
|
|
272
|
+
_id: monitorId,
|
|
273
|
+
labels,
|
|
274
|
+
currentMonitorStatusId,
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const result: any = await ReadPermission.checkReadBlockPermission(
|
|
278
|
+
Monitor,
|
|
279
|
+
query,
|
|
280
|
+
makeProps([blockPermission(Permission.ProjectMember, [blockedLabelA])]),
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
expect(result.query.labels).toBe(labels);
|
|
284
|
+
expect(result.query.projectId).toBe(projectId);
|
|
285
|
+
expect(result.query.currentMonitorStatusId).toBe(currentMonitorStatusId);
|
|
286
|
+
expect(result.query._id.type).toBe("and");
|
|
287
|
+
expect(result.query._id.value[0].type).toBe("raw");
|
|
288
|
+
expect(
|
|
289
|
+
Object.values(result.query._id.value[0].objectLiteralParameters),
|
|
290
|
+
).toEqual([monitorId.toString()]);
|
|
291
|
+
expect(deniedLabelIds(result.query._id.value[1], "MonitorLabel")).toEqual([
|
|
209
292
|
blockedLabelA.toString(),
|
|
210
293
|
]);
|
|
294
|
+
|
|
295
|
+
const recordAndBlockCondition: FindOperator<any> = result.query._id;
|
|
296
|
+
const serialized: any = QueryUtil.serializeQuery(Monitor, result.query);
|
|
297
|
+
expect(serialized.labels).toBeUndefined();
|
|
298
|
+
expect(serialized._id.type).toBe("and");
|
|
299
|
+
const clauses: Array<FindOperator<any>> = serialized._id.value;
|
|
300
|
+
expect(clauses).toHaveLength(3);
|
|
301
|
+
expect(clauses[0]).toBe(recordAndBlockCondition);
|
|
302
|
+
expect(Object.values(clauses[1]!.objectLiteralParameters || {})).toEqual([
|
|
303
|
+
[fixedLabelId],
|
|
304
|
+
]);
|
|
305
|
+
expect(Object.values(clauses[2]!.objectLiteralParameters || {})).toEqual([
|
|
306
|
+
[selectedLabelId],
|
|
307
|
+
]);
|
|
308
|
+
expect(Object.values(serialized.projectId.objectLiteralParameters)).toEqual(
|
|
309
|
+
[projectId.toString()],
|
|
310
|
+
);
|
|
311
|
+
expect(
|
|
312
|
+
Object.values(serialized.currentMonitorStatusId.objectLiteralParameters),
|
|
313
|
+
).toEqual([currentMonitorStatusId.toString()]);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it("combines with an existing database ID condition without replacing it", async () => {
|
|
317
|
+
const idFilter: FindOperator<string> = In([ObjectID.generate().toString()]);
|
|
318
|
+
const query: any = { projectId, _id: idFilter };
|
|
319
|
+
|
|
320
|
+
const result: any = await ReadPermission.checkReadBlockPermission(
|
|
321
|
+
Incident,
|
|
322
|
+
query,
|
|
323
|
+
makeProps([blockPermission(Permission.ProjectMember, [blockedLabelA])]),
|
|
324
|
+
);
|
|
325
|
+
|
|
326
|
+
expect(result.query._id.type).toBe("and");
|
|
327
|
+
expect(result.query._id.value[0]).toBe(idFilter);
|
|
328
|
+
expect(deniedLabelIds(result.query._id.value[1])).toEqual([
|
|
329
|
+
blockedLabelA.toString(),
|
|
330
|
+
]);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const requestedId: string = ObjectID.generate().toString();
|
|
334
|
+
|
|
335
|
+
it.each([
|
|
336
|
+
{
|
|
337
|
+
name: "EqualTo",
|
|
338
|
+
idFilter: new EqualTo(requestedId),
|
|
339
|
+
expectedSql: "Monitor._id = :",
|
|
340
|
+
expectedParameters: [requestedId],
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
name: "IncludesNone",
|
|
344
|
+
idFilter: new IncludesNone([new ObjectID(requestedId)]),
|
|
345
|
+
expectedSql: "Monitor._id NOT IN (",
|
|
346
|
+
expectedParameters: [[requestedId]],
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
name: "NotEqual",
|
|
350
|
+
idFilter: new NotEqual(requestedId),
|
|
351
|
+
expectedSql: "Monitor._id != :",
|
|
352
|
+
expectedParameters: [requestedId],
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
name: "Search",
|
|
356
|
+
idFilter: new Search(requestedId),
|
|
357
|
+
expectedSql: "CAST(Monitor._id AS TEXT) ILIKE",
|
|
358
|
+
expectedParameters: [`%${requestedId}%`],
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
name: "Includes",
|
|
362
|
+
idFilter: new Includes([requestedId]),
|
|
363
|
+
expectedSql: "Monitor._id IN (",
|
|
364
|
+
expectedParameters: [[requestedId]],
|
|
365
|
+
},
|
|
366
|
+
])(
|
|
367
|
+
"preserves the $name ID filter when composing read label restrictions",
|
|
368
|
+
async ({
|
|
369
|
+
idFilter,
|
|
370
|
+
expectedSql,
|
|
371
|
+
expectedParameters,
|
|
372
|
+
}: {
|
|
373
|
+
idFilter: unknown;
|
|
374
|
+
expectedSql: string;
|
|
375
|
+
expectedParameters: Array<unknown>;
|
|
376
|
+
}): Promise<void> => {
|
|
377
|
+
const labels: IncludesAnyOfGroups = new IncludesAnyOfGroups([
|
|
378
|
+
[ObjectID.generate().toString()],
|
|
379
|
+
[ObjectID.generate().toString()],
|
|
380
|
+
]);
|
|
381
|
+
const query: any = { projectId, _id: idFilter, labels };
|
|
382
|
+
|
|
383
|
+
const result: any = await ReadPermission.checkReadBlockPermission(
|
|
384
|
+
Monitor,
|
|
385
|
+
query,
|
|
386
|
+
makeProps([blockPermission(Permission.ProjectMember, [blockedLabelA])]),
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
expect(result.query.labels).toBe(labels);
|
|
390
|
+
expect(result.query._id.type).toBe("and");
|
|
391
|
+
const callerCondition: FindOperator<unknown> = result.query._id.value[0];
|
|
392
|
+
expect(callerCondition.type).toBe("raw");
|
|
393
|
+
expect(callerCondition.getSql?.("Monitor._id")).toContain(expectedSql);
|
|
394
|
+
expect(
|
|
395
|
+
Object.values(callerCondition.objectLiteralParameters || {}),
|
|
396
|
+
).toEqual(expectedParameters);
|
|
397
|
+
expect(deniedLabelIds(result.query._id.value[1], "MonitorLabel")).toEqual(
|
|
398
|
+
[blockedLabelA.toString()],
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
const recordAndBlockCondition: FindOperator<unknown> = result.query._id;
|
|
402
|
+
const serialized: any = QueryUtil.serializeQuery(Monitor, result.query);
|
|
403
|
+
expect(serialized._id.value[0]).toBe(recordAndBlockCondition);
|
|
404
|
+
expect(serialized._id.value).toHaveLength(3);
|
|
405
|
+
},
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
it("rejects an unsupported ID condition before changing the caller's query", async () => {
|
|
409
|
+
const idFilter: Record<string, unknown> = { comparison: "unsupported" };
|
|
410
|
+
const labels: Includes = new Includes([ObjectID.generate().toString()]);
|
|
411
|
+
const query: any = { projectId, _id: idFilter, labels };
|
|
412
|
+
|
|
413
|
+
await expect(
|
|
414
|
+
ReadPermission.checkReadBlockPermission(
|
|
415
|
+
Monitor,
|
|
416
|
+
query,
|
|
417
|
+
makeProps([blockPermission(Permission.ProjectMember, [blockedLabelA])]),
|
|
418
|
+
),
|
|
419
|
+
).rejects.toThrow("unsupported ID filter");
|
|
420
|
+
expect(query).toEqual({ projectId, _id: idFilter, labels });
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it("reports missing relation metadata without changing the caller's filters", async () => {
|
|
424
|
+
jest
|
|
425
|
+
.spyOn(QueryUtil, "getManyToManyRelationMetadata")
|
|
426
|
+
.mockReturnValue(null);
|
|
427
|
+
const labels: IncludesAnyOfGroups = new IncludesAnyOfGroups([
|
|
428
|
+
[ObjectID.generate().toString()],
|
|
429
|
+
[ObjectID.generate().toString()],
|
|
430
|
+
]);
|
|
431
|
+
const monitorId: ObjectID = ObjectID.generate();
|
|
432
|
+
const query: any = { projectId, labels, _id: monitorId };
|
|
433
|
+
|
|
434
|
+
await expect(
|
|
435
|
+
ReadPermission.checkReadBlockPermission(
|
|
436
|
+
Monitor,
|
|
437
|
+
query,
|
|
438
|
+
makeProps([blockPermission(Permission.ProjectMember, [blockedLabelA])]),
|
|
439
|
+
),
|
|
440
|
+
).rejects.toThrow(BadDataException);
|
|
441
|
+
expect(query).toEqual({ projectId, labels, _id: monitorId });
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it("reports an unavailable access-control column before composing label restrictions", async () => {
|
|
445
|
+
jest
|
|
446
|
+
.spyOn(Monitor.prototype, "getAccessControlColumn")
|
|
447
|
+
.mockReturnValue(null);
|
|
448
|
+
const query: any = { projectId };
|
|
449
|
+
|
|
450
|
+
await expect(
|
|
451
|
+
ReadPermission.checkReadBlockPermission(
|
|
452
|
+
Monitor,
|
|
453
|
+
query,
|
|
454
|
+
makeProps([blockPermission(Permission.ProjectMember, [blockedLabelA])]),
|
|
455
|
+
),
|
|
456
|
+
).rejects.toThrow("access-control relation metadata");
|
|
457
|
+
expect(query).toEqual({ projectId });
|
|
211
458
|
});
|
|
212
459
|
});
|