@oneuptime/common 12.0.12 → 12.0.13
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/DatabaseBaseModel/DatabaseBaseModel.ts +32 -5
- package/Models/DatabaseModels/NetworkDevice.ts +64 -0
- package/Models/DatabaseModels/StatusPage.ts +59 -0
- package/Models/DatabaseModels/StatusPageOidc.ts +2 -0
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +19 -2
- package/Server/API/AIChatAPI.ts +55 -56
- package/Server/API/CommonAPI.ts +89 -0
- package/Server/API/MicrosoftTeamsAPI.ts +20 -9
- package/Server/API/SlackAPI.ts +13 -10
- package/Server/API/StatusPageAPI.ts +2197 -2128
- package/Server/API/TelemetryAPI.ts +72 -3
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.ts +30 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.ts +47 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/UserAuthorization.ts +11 -2
- package/Server/Services/ApiKeyPermissionService.ts +116 -2
- package/Server/Services/DatabaseService.ts +17 -6
- package/Server/Services/LogAggregationService.ts +34 -0
- package/Server/Services/LogService.ts +21 -0
- package/Server/Services/NetworkSiteService.ts +12 -0
- package/Server/Services/SpanService.ts +21 -0
- package/Server/Services/TraceAggregationService.ts +15 -0
- package/Server/Services/WorkspaceNotificationRuleService.ts +172 -220
- package/Server/Types/AnalyticsDatabase/ModelPermission.ts +9 -0
- package/Server/Types/Database/Permissions/AccessControlPermission.ts +47 -16
- package/Server/Utils/APIKey/AccessPermission.ts +16 -40
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +144 -0
- package/Server/Utils/LogRedaction.ts +576 -0
- package/Server/Utils/Logger.ts +123 -46
- package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +94 -23
- package/Server/Utils/Monitor/MonitorResource.ts +29 -9
- package/Server/Utils/Monitor/NetworkInventoryUtil.ts +28 -7
- package/Server/Utils/SessionReplay/SessionReplayGateCacheStore.ts +56 -10
- package/Server/Utils/StartServer.ts +30 -9
- package/Server/Utils/StatusPageSearchEngineIndexing.ts +33 -0
- package/Server/Utils/Telemetry/ResourceEntityFilter.ts +441 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +488 -46
- package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +300 -0
- package/Tests/App/StatusPage/StatusPageIndexPageRobotsMeta.test.ts +130 -0
- package/Tests/Models/DatabaseModels/DatabaseBaseModelToJSONObjectCache.test.ts +180 -0
- package/Tests/Models/StatusPageEnableSearchEngineIndexing.test.ts +157 -0
- package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +23 -1
- package/Tests/Server/API/AIChatRouteAuthorization.test.ts +785 -0
- package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +21 -8
- package/Tests/Server/API/CommonAPIAuthGuard.test.ts +290 -0
- package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +7 -1
- package/Tests/Server/API/StatusPageOverviewCache.test.ts +474 -0
- package/Tests/Server/API/StatusPageSeoSearchEngineIndexing.test.ts +189 -0
- package/Tests/Server/Infrastructure/InMemoryTTLCache.test.ts +158 -0
- package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +9 -60
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +24 -15
- package/Tests/Server/Middleware/UserAuthorization.test.ts +79 -0
- package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +332 -0
- package/Tests/Server/Services/ApiKeyPermissionService.test.ts +347 -0
- package/Tests/Server/Services/DatabaseServiceUpdateDebugLogging.test.ts +282 -0
- package/Tests/Server/Services/NetworkSiteService.test.ts +56 -2
- package/Tests/Server/Services/TelemetryResourceFacetFilters.test.ts +267 -0
- package/Tests/Server/Services/WorkspaceNotificationRuleTestRuleChannels.test.ts +702 -0
- package/Tests/Server/Types/Database/Permissions/AccessControlPermission.test.ts +165 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +48 -36
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +213 -0
- package/Tests/Server/Utils/LogRedaction.test.ts +415 -0
- package/Tests/Server/Utils/LoggerCredentialLeak.test.ts +245 -0
- package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +374 -4
- package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +455 -0
- package/Tests/Server/Utils/Monitor/NetworkDeviceReachabilityRoundTrip.test.ts +369 -0
- package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +86 -8
- package/Tests/Server/Utils/PrivacyFilterUtil.test.ts +64 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayGateCacheStore.test.ts +207 -0
- package/Tests/Server/Utils/StartServerBodyVerify.test.ts +335 -0
- package/Tests/Server/Utils/StatusPageSearchEngineIndexing.test.ts +60 -0
- package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +435 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +607 -62
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsInstalledTeamIdSpace.test.ts +778 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +44 -10
- package/Tests/Types/API/HostnameFromAuthority.test.ts +164 -0
- package/Tests/Types/Database/AccessControl/ColumnAccessControlCache.test.ts +344 -0
- package/Tests/Types/Database/DatabasePropertyFindOperator.test.ts +518 -0
- package/Tests/Types/Database/TableColumnMetadataCache.test.ts +286 -0
- package/Tests/Types/Monitor/MonitorStep.test.ts +100 -0
- package/Tests/Types/Monitor/MonitorTypeKeywords.test.ts +360 -0
- package/Tests/Types/StatusPage/SearchEngineIndexing.test.ts +86 -0
- package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +254 -0
- package/Tests/UI/Components/CardSelect.test.tsx +1021 -0
- package/Tests/UI/Components/MasterPage.test.tsx +36 -1
- package/Tests/Utils/Monitor/MonitorMetricType.test.ts +66 -1
- package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +115 -6
- package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +610 -0
- package/Tests/Utils/NetworkSite/SiteStatusRollupUtil.test.ts +114 -45
- package/Types/API/Hostname.ts +79 -0
- package/Types/Database/AccessControl/ColumnAccessControl.ts +33 -11
- package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +33 -11
- package/Types/Database/DatabaseProperty.ts +38 -0
- package/Types/Database/TableColumn.ts +34 -7
- package/Types/Events/Recurring.ts +16 -1
- package/Types/Monitor/MonitorStep.ts +13 -1
- package/Types/Monitor/MonitorType.ts +345 -40
- package/Types/Monitor/SSLMonitor/SslMonitorResponse.ts +43 -0
- package/Types/StatusPage/SearchEngineIndexing.ts +61 -0
- package/Types/Telemetry/ResourceEntityFacet.ts +211 -0
- package/UI/Components/CardSelect/CardSelect.tsx +714 -105
- package/UI/Components/EmptyState/EmptyState.tsx +8 -1
- package/UI/Components/Footer/Footer.tsx +32 -6
- package/UI/Components/Forms/Fields/FormField.tsx +3 -0
- package/UI/Components/Forms/Types/Field.ts +8 -0
- package/UI/Components/Loader/PageLoader.tsx +29 -8
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +26 -4
- package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +17 -1
- package/UI/Components/MasterPage/MasterPage.tsx +24 -11
- package/Utils/Monitor/MonitorMetricType.ts +16 -1
- package/Utils/Monitor/NetworkTopologyUtil.ts +60 -8
- package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +262 -0
- package/Utils/NetworkSite/SiteStatusRollupUtil.ts +41 -24
- package/Utils/Telemetry/CrossSignalScope.ts +75 -4
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +20 -4
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDevice.js +66 -0
- package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +60 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageOidc.js +2 -0
- package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +48 -35
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/CommonAPI.js +59 -0
- package/build/dist/Server/API/CommonAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +20 -6
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/SlackAPI.js +13 -10
- package/build/dist/Server/API/SlackAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +745 -673
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryAPI.js +34 -3
- package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js +23 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +6 -2
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/ApiKeyPermissionService.js +90 -1
- package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +15 -6
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +2 -0
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/LogService.js +16 -0
- package/build/dist/Server/Services/LogService.js.map +1 -1
- package/build/dist/Server/Services/NetworkSiteService.js +12 -0
- package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
- package/build/dist/Server/Services/SpanService.js +16 -0
- package/build/dist/Server/Services/SpanService.js.map +1 -1
- package/build/dist/Server/Services/TraceAggregationService.js +2 -0
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +137 -164
- package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +9 -0
- package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js +21 -13
- package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js.map +1 -1
- package/build/dist/Server/Utils/APIKey/AccessPermission.js +9 -32
- package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +87 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/LogRedaction.js +449 -0
- package/build/dist/Server/Utils/LogRedaction.js.map +1 -0
- package/build/dist/Server/Utils/Logger.js +98 -45
- package/build/dist/Server/Utils/Logger.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +65 -13
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +20 -3
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +27 -7
- package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js +29 -10
- package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +10 -7
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js +24 -0
- package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +311 -0
- package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +366 -37
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/API/Hostname.js +63 -0
- package/build/dist/Types/API/Hostname.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +20 -6
- package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
- package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +20 -6
- package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
- package/build/dist/Types/Database/DatabaseProperty.js +38 -0
- package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
- package/build/dist/Types/Database/TableColumn.js +24 -6
- package/build/dist/Types/Database/TableColumn.js.map +1 -1
- package/build/dist/Types/Events/Recurring.js +13 -1
- package/build/dist/Types/Events/Recurring.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +9 -1
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorType.js +330 -31
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/StatusPage/SearchEngineIndexing.js +55 -0
- package/build/dist/Types/StatusPage/SearchEngineIndexing.js.map +1 -0
- package/build/dist/Types/Telemetry/ResourceEntityFacet.js +156 -0
- package/build/dist/Types/Telemetry/ResourceEntityFacet.js.map +1 -0
- package/build/dist/UI/Components/CardSelect/CardSelect.js +360 -40
- package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
- package/build/dist/UI/Components/EmptyState/EmptyState.js +1 -1
- package/build/dist/UI/Components/EmptyState/EmptyState.js.map +1 -1
- package/build/dist/UI/Components/Footer/Footer.js +7 -4
- package/build/dist/UI/Components/Footer/Footer.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Loader/PageLoader.js +15 -5
- package/build/dist/UI/Components/Loader/PageLoader.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +16 -3
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js +13 -1
- package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
- package/build/dist/UI/Components/MasterPage/MasterPage.js +10 -2
- package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +13 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +34 -7
- package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +177 -0
- package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -0
- package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js +23 -27
- package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js.map +1 -1
- package/build/dist/Utils/Telemetry/CrossSignalScope.js +44 -4
- package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
2
|
+
import NetworkInventoryUtil from "../../../../Server/Utils/Monitor/NetworkInventoryUtil";
|
|
3
|
+
import NetworkDeviceService from "../../../../Server/Services/NetworkDeviceService";
|
|
4
|
+
import NetworkEndpointService from "../../../../Server/Services/NetworkEndpointService";
|
|
5
|
+
import NetworkInterfaceService from "../../../../Server/Services/NetworkInterfaceService";
|
|
6
|
+
import NetworkDevice from "../../../../Models/DatabaseModels/NetworkDevice";
|
|
7
|
+
import NetworkInterface from "../../../../Models/DatabaseModels/NetworkInterface";
|
|
8
|
+
import DeviceReachabilityUtil, {
|
|
9
|
+
NetworkDeviceReachability,
|
|
10
|
+
} from "../../../../Utils/NetworkDevice/DeviceReachabilityUtil";
|
|
11
|
+
import SiteStatusRollupUtil, {
|
|
12
|
+
RollupStatusOption,
|
|
13
|
+
} from "../../../../Utils/NetworkSite/SiteStatusRollupUtil";
|
|
14
|
+
import NetworkTopologyUtil, {
|
|
15
|
+
TopologyBuildResult,
|
|
16
|
+
} from "../../../../Utils/Monitor/NetworkTopologyUtil";
|
|
17
|
+
import { NetworkTopologyNode } from "../../../../Types/Monitor/SnmpMonitor/NetworkTopology";
|
|
18
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
19
|
+
import OneUptimeDate from "../../../../Types/Date";
|
|
20
|
+
import SnmpInterface from "../../../../Types/Monitor/SnmpMonitor/SnmpInterface";
|
|
21
|
+
import SnmpMonitorResponse from "../../../../Types/Monitor/SnmpMonitor/SnmpMonitorResponse";
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* Issue #3220, end to end.
|
|
25
|
+
*
|
|
26
|
+
* "Network Device shown as Down (unreachable) despite device responding to
|
|
27
|
+
* ping and interfaces showing Up." A router that answered SNMP fine — its
|
|
28
|
+
* own Interfaces tab listed 14 ports up, written by that very walk — was
|
|
29
|
+
* listed as Down, with "last seen 21 minutes ago" underneath. 323 of 980
|
|
30
|
+
* devices in that project were in the same state, and the topology graph
|
|
31
|
+
* and network map drew all of them red.
|
|
32
|
+
*
|
|
33
|
+
* The two halves of the cause:
|
|
34
|
+
*
|
|
35
|
+
* 1. a probe is handed a bounded batch of devices per one-minute fetch,
|
|
36
|
+
* so a fleet of ~1000 devices took ~20 minutes to poll all the way
|
|
37
|
+
* round however short the configured interval was; and
|
|
38
|
+
* 2. every reader decided up/down from `lastSeenAt` being newer than a
|
|
39
|
+
* fixed 15 minutes, which a 20-minute cycle can never satisfy.
|
|
40
|
+
*
|
|
41
|
+
* The unit suites cover each layer on its own. What this file does is join
|
|
42
|
+
* them: it drives the REAL write path with a walk exactly like the reported
|
|
43
|
+
* one, and then asks every reader — the device pill, the site rollup and
|
|
44
|
+
* the topology graph — what it makes of the row that was actually written.
|
|
45
|
+
* A regression in either the writer or any single reader shows up here.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
const PROJECT_ID: string = "1c9d4a7b-0000-4000-8000-000000000011";
|
|
49
|
+
const DEVICE_ID: string = "8f2c1f0e-0000-4000-8000-0000000000aa";
|
|
50
|
+
|
|
51
|
+
// The moment the probe finally got round to UN1234WANRTR01.
|
|
52
|
+
const WALKED_AT: Date = new Date("2026-08-17T18:12:00.000Z");
|
|
53
|
+
// The moment the operator was looking at the screen: 21 minutes later.
|
|
54
|
+
const OBSERVED_AT: Date = new Date("2026-08-17T18:33:00.000Z");
|
|
55
|
+
|
|
56
|
+
const OPERATIONAL: RollupStatusOption = {
|
|
57
|
+
monitorStatusId: "status-operational",
|
|
58
|
+
priority: 1,
|
|
59
|
+
};
|
|
60
|
+
const OFFLINE: RollupStatusOption = {
|
|
61
|
+
monitorStatusId: "status-offline",
|
|
62
|
+
priority: 3,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
let deviceUpdateSpy: jest.SpyInstance;
|
|
66
|
+
|
|
67
|
+
function mockServices(): void {
|
|
68
|
+
const ownedDevice: NetworkDevice = new NetworkDevice();
|
|
69
|
+
ownedDevice.id = new ObjectID(DEVICE_ID);
|
|
70
|
+
|
|
71
|
+
jest
|
|
72
|
+
.spyOn(NetworkDeviceService, "findOneBy")
|
|
73
|
+
.mockResolvedValue(ownedDevice as never);
|
|
74
|
+
deviceUpdateSpy = jest
|
|
75
|
+
.spyOn(NetworkDeviceService, "updateOneById")
|
|
76
|
+
.mockResolvedValue(1 as never);
|
|
77
|
+
jest.spyOn(NetworkInterfaceService, "findBy").mockResolvedValue([] as never);
|
|
78
|
+
jest
|
|
79
|
+
.spyOn(NetworkInterfaceService, "updateOneById")
|
|
80
|
+
.mockResolvedValue(1 as never);
|
|
81
|
+
jest
|
|
82
|
+
.spyOn(NetworkInterfaceService, "create")
|
|
83
|
+
.mockResolvedValue(new NetworkInterface() as never);
|
|
84
|
+
jest
|
|
85
|
+
.spyOn(NetworkEndpointService, "upsertDiscoveredEndpoints")
|
|
86
|
+
.mockResolvedValue(undefined as never);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 14 ports up and 12 down — the mix the reported device's Interfaces tab showed.
|
|
90
|
+
function reportedInterfaces(): Array<SnmpInterface> {
|
|
91
|
+
const interfaces: Array<SnmpInterface> = [];
|
|
92
|
+
|
|
93
|
+
for (let index: number = 1; index <= 26; index++) {
|
|
94
|
+
interfaces.push({
|
|
95
|
+
interfaceIndex: index,
|
|
96
|
+
name: `Gi0/${index}`,
|
|
97
|
+
isAdministrativelyUp: true,
|
|
98
|
+
isOperationallyUp: index <= 14,
|
|
99
|
+
} as SnmpInterface);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return interfaces;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/*
|
|
106
|
+
* Runs the real writer and returns the device row as it would then be read
|
|
107
|
+
* back — the columns the update wrote, nothing invented.
|
|
108
|
+
*/
|
|
109
|
+
async function walkAndReadBackDevice(data: {
|
|
110
|
+
isOnline: boolean;
|
|
111
|
+
interfaces: Array<SnmpInterface>;
|
|
112
|
+
}): Promise<Record<string, unknown>> {
|
|
113
|
+
const snmpResponse: SnmpMonitorResponse = {
|
|
114
|
+
isOnline: data.isOnline,
|
|
115
|
+
responseTimeInMs: 274,
|
|
116
|
+
failureCause: "",
|
|
117
|
+
oidResponses: [],
|
|
118
|
+
interfaces: data.interfaces,
|
|
119
|
+
systemInfo: { sysName: "UN1234WANRTR01" },
|
|
120
|
+
} as SnmpMonitorResponse;
|
|
121
|
+
|
|
122
|
+
await NetworkInventoryUtil.updateFromWalk({
|
|
123
|
+
projectId: new ObjectID(PROJECT_ID),
|
|
124
|
+
deviceId: new ObjectID(DEVICE_ID),
|
|
125
|
+
snmpResponse: snmpResponse,
|
|
126
|
+
isOnline: data.isOnline,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
expect(deviceUpdateSpy).toHaveBeenCalled();
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
// The device's configured schedule, unchanged by the walk.
|
|
133
|
+
pollingIntervalInMinutes: 5,
|
|
134
|
+
...(deviceUpdateSpy.mock.calls[0]![0].data as Record<string, unknown>),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
beforeEach(() => {
|
|
139
|
+
jest.useFakeTimers({
|
|
140
|
+
doNotFake: [
|
|
141
|
+
"performance",
|
|
142
|
+
"hrtime",
|
|
143
|
+
"queueMicrotask",
|
|
144
|
+
"requestAnimationFrame",
|
|
145
|
+
"cancelAnimationFrame",
|
|
146
|
+
"requestIdleCallback",
|
|
147
|
+
"cancelIdleCallback",
|
|
148
|
+
"setImmediate",
|
|
149
|
+
"clearImmediate",
|
|
150
|
+
"setInterval",
|
|
151
|
+
"clearInterval",
|
|
152
|
+
"setTimeout",
|
|
153
|
+
"clearTimeout",
|
|
154
|
+
],
|
|
155
|
+
});
|
|
156
|
+
// The walk lands at 18:12; every reader below looks at 18:33.
|
|
157
|
+
jest.setSystemTime(WALKED_AT);
|
|
158
|
+
mockServices();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
afterEach(() => {
|
|
162
|
+
jest.useRealTimers();
|
|
163
|
+
jest.restoreAllMocks();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
describe("issue #3220 — a device that answered 21 minutes ago", () => {
|
|
167
|
+
test("the walk records that it answered, and when it was asked", async () => {
|
|
168
|
+
const row: Record<string, unknown> = await walkAndReadBackDevice({
|
|
169
|
+
isOnline: true,
|
|
170
|
+
interfaces: reportedInterfaces(),
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(row["isReachable"]).toBe(true);
|
|
174
|
+
expect(row["lastPolledAt"]).toEqual(WALKED_AT);
|
|
175
|
+
expect(row["lastSeenAt"]).toEqual(WALKED_AT);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("the same walk is what put 14 up / 12 down on the Interfaces tab", async () => {
|
|
179
|
+
const row: Record<string, unknown> = await walkAndReadBackDevice({
|
|
180
|
+
isOnline: true,
|
|
181
|
+
interfaces: reportedInterfaces(),
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
/*
|
|
185
|
+
* The contradiction the report was really about: these counts and the
|
|
186
|
+
* reachability verdict come out of ONE walk, so they cannot honestly
|
|
187
|
+
* disagree.
|
|
188
|
+
*/
|
|
189
|
+
expect(row["interfacesUp"]).toBe(14);
|
|
190
|
+
expect(row["interfacesDown"]).toBe(12);
|
|
191
|
+
expect(row["isReachable"]).toBe(true);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("the device pill reads Up, 21 minutes later", async () => {
|
|
195
|
+
const row: Record<string, unknown> = await walkAndReadBackDevice({
|
|
196
|
+
isOnline: true,
|
|
197
|
+
interfaces: reportedInterfaces(),
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
expect(DeviceReachabilityUtil.getStatus(row, OBSERVED_AT)).toBe(
|
|
201
|
+
NetworkDeviceReachability.Up,
|
|
202
|
+
);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("the site rollup above it stays operational", async () => {
|
|
206
|
+
const row: Record<string, unknown> = await walkAndReadBackDevice({
|
|
207
|
+
isOnline: true,
|
|
208
|
+
interfaces: reportedInterfaces(),
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(
|
|
212
|
+
SiteStatusRollupUtil.worstStatus({
|
|
213
|
+
deviceStates: [row],
|
|
214
|
+
operationalStatus: OPERATIONAL,
|
|
215
|
+
offlineStatus: OFFLINE,
|
|
216
|
+
now: OBSERVED_AT,
|
|
217
|
+
}),
|
|
218
|
+
).toBe(OPERATIONAL.monitorStatusId);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("the topology graph draws it up, not red", async () => {
|
|
222
|
+
const row: Record<string, unknown> = await walkAndReadBackDevice({
|
|
223
|
+
isOnline: true,
|
|
224
|
+
interfaces: reportedInterfaces(),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
|
|
228
|
+
[
|
|
229
|
+
{
|
|
230
|
+
id: DEVICE_ID,
|
|
231
|
+
name: "UN1234WANRTR01",
|
|
232
|
+
hostname: "10.15.128.165",
|
|
233
|
+
isReachable: row["isReachable"] as boolean,
|
|
234
|
+
lastPolledAt: row["lastPolledAt"] as Date,
|
|
235
|
+
lastSeenAt: row["lastSeenAt"] as Date,
|
|
236
|
+
pollingIntervalInMinutes: 5,
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
OBSERVED_AT,
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
const node: NetworkTopologyNode | undefined = result.nodes.find(
|
|
243
|
+
(candidate: NetworkTopologyNode) => {
|
|
244
|
+
return candidate.id === DEVICE_ID;
|
|
245
|
+
},
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
expect(node?.status).toBe("up");
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
/*
|
|
253
|
+
* The fix must not have bought its way out of false positives with false
|
|
254
|
+
* negatives. A device that genuinely stops answering has to go down — and
|
|
255
|
+
* go down on the very next poll, not after a timeout.
|
|
256
|
+
*/
|
|
257
|
+
describe("a device that genuinely stops answering still goes down", () => {
|
|
258
|
+
test("the failing walk records the failure and leaves lastSeenAt behind", async () => {
|
|
259
|
+
const row: Record<string, unknown> = await walkAndReadBackDevice({
|
|
260
|
+
isOnline: false,
|
|
261
|
+
interfaces: [],
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
expect(row["isReachable"]).toBe(false);
|
|
265
|
+
expect(row["lastPolledAt"]).toEqual(WALKED_AT);
|
|
266
|
+
expect(row).not.toHaveProperty("lastSeenAt");
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("every reader calls it down, immediately", async () => {
|
|
270
|
+
const written: Record<string, unknown> = await walkAndReadBackDevice({
|
|
271
|
+
isOnline: false,
|
|
272
|
+
interfaces: [],
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// The row as it now stands: last answered before the failing poll.
|
|
276
|
+
const row: Record<string, unknown> = {
|
|
277
|
+
...written,
|
|
278
|
+
lastSeenAt: OneUptimeDate.addRemoveMinutes(WALKED_AT, -5),
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// One second after the failed walk — no waiting for a window to expire.
|
|
282
|
+
const justAfter: Date = new Date(WALKED_AT.getTime() + 1000);
|
|
283
|
+
|
|
284
|
+
expect(DeviceReachabilityUtil.getStatus(row, justAfter)).toBe(
|
|
285
|
+
NetworkDeviceReachability.Down,
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
expect(
|
|
289
|
+
SiteStatusRollupUtil.worstStatus({
|
|
290
|
+
deviceStates: [row],
|
|
291
|
+
operationalStatus: OPERATIONAL,
|
|
292
|
+
offlineStatus: OFFLINE,
|
|
293
|
+
now: justAfter,
|
|
294
|
+
}),
|
|
295
|
+
).toBe(OFFLINE.monitorStatusId);
|
|
296
|
+
|
|
297
|
+
const result: TopologyBuildResult = NetworkTopologyUtil.buildTopology(
|
|
298
|
+
[
|
|
299
|
+
{
|
|
300
|
+
id: DEVICE_ID,
|
|
301
|
+
name: "UN1234WANRTR01",
|
|
302
|
+
isReachable: row["isReachable"] as boolean,
|
|
303
|
+
lastPolledAt: row["lastPolledAt"] as Date,
|
|
304
|
+
lastSeenAt: row["lastSeenAt"] as Date,
|
|
305
|
+
pollingIntervalInMinutes: 5,
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
justAfter,
|
|
309
|
+
);
|
|
310
|
+
|
|
311
|
+
expect(
|
|
312
|
+
result.nodes.find((candidate: NetworkTopologyNode) => {
|
|
313
|
+
return candidate.id === DEVICE_ID;
|
|
314
|
+
})?.status,
|
|
315
|
+
).toBe("down");
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
/*
|
|
320
|
+
* And the polling pipeline going silent. The fleet keeps its last known
|
|
321
|
+
* verdicts — manufacturing an outage nobody observed is the failure this
|
|
322
|
+
* change removes — but every device is flagged stale, which is what sends
|
|
323
|
+
* the operator to the probe instead of to 300 devices.
|
|
324
|
+
*/
|
|
325
|
+
describe("a fleet whose probe has gone silent is flagged, not condemned", () => {
|
|
326
|
+
test("a good verdict nobody has refreshed for hours stays Up and reads stale", async () => {
|
|
327
|
+
const row: Record<string, unknown> = await walkAndReadBackDevice({
|
|
328
|
+
isOnline: true,
|
|
329
|
+
interfaces: reportedInterfaces(),
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
const hoursLater: Date = new Date(WALKED_AT.getTime() + 4 * 60 * 60 * 1000);
|
|
333
|
+
|
|
334
|
+
const reachability: {
|
|
335
|
+
status: NetworkDeviceReachability;
|
|
336
|
+
isStale: boolean;
|
|
337
|
+
} = DeviceReachabilityUtil.getReachability(row, hoursLater);
|
|
338
|
+
|
|
339
|
+
expect(reachability.status).toBe(NetworkDeviceReachability.Up);
|
|
340
|
+
/*
|
|
341
|
+
* isStale is what puts the amber "Stale" pill next to the verdict and
|
|
342
|
+
* points the tooltip at the probe — a different investigation from
|
|
343
|
+
* "this device is down", and the reason it is not expressed as Down.
|
|
344
|
+
*/
|
|
345
|
+
expect(reachability.isStale).toBe(true);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
/*
|
|
349
|
+
* The property that keeps the device list self-consistent: the summary
|
|
350
|
+
* tiles and the Status chip are SQL over `isReachable`, so whatever the
|
|
351
|
+
* clock says, the pill has to be reproducible from that column alone.
|
|
352
|
+
*/
|
|
353
|
+
test("the verdict stays reproducible from the stored column alone", async () => {
|
|
354
|
+
const row: Record<string, unknown> = await walkAndReadBackDevice({
|
|
355
|
+
isOnline: true,
|
|
356
|
+
interfaces: reportedInterfaces(),
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
for (const hoursLater of [0, 1, 4, 24, 24 * 30]) {
|
|
360
|
+
const at: Date = new Date(WALKED_AT.getTime() + hoursLater * 3600 * 1000);
|
|
361
|
+
|
|
362
|
+
expect(DeviceReachabilityUtil.getStatus(row, at)).toBe(
|
|
363
|
+
row["isReachable"] === true
|
|
364
|
+
? NetworkDeviceReachability.Up
|
|
365
|
+
: NetworkDeviceReachability.Down,
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
});
|
|
@@ -236,8 +236,33 @@ describe("NetworkInventoryUtil.updateFromWalk — project-membership guard", ()
|
|
|
236
236
|
});
|
|
237
237
|
});
|
|
238
238
|
|
|
239
|
-
|
|
240
|
-
|
|
239
|
+
/*
|
|
240
|
+
* The three columns this util writes on every walk are what the whole
|
|
241
|
+
* up/down story rests on, so they get their own block:
|
|
242
|
+
*
|
|
243
|
+
* lastPolledAt - when we ASKED (always)
|
|
244
|
+
* isReachable - what we got back (always)
|
|
245
|
+
* lastSeenAt - when it ANSWERED (successful walks only)
|
|
246
|
+
*
|
|
247
|
+
* The bug this pins (issue #3220) was that only lastSeenAt existed, so a
|
|
248
|
+
* device that answered its last poll 21 minutes ago was indistinguishable
|
|
249
|
+
* from one that had failed its last poll — and the UI, having to guess,
|
|
250
|
+
* called both of them Down.
|
|
251
|
+
*/
|
|
252
|
+
describe("NetworkInventoryUtil.updateFromWalk — reachability recording", () => {
|
|
253
|
+
test("a reachable poll stamps all three columns", async () => {
|
|
254
|
+
mockServices();
|
|
255
|
+
|
|
256
|
+
await runWalk();
|
|
257
|
+
|
|
258
|
+
const update: DeviceUpdatePayload = deviceUpdatePayload();
|
|
259
|
+
|
|
260
|
+
expect(update["lastPolledAt"]).toEqual(NOW);
|
|
261
|
+
expect(update["isReachable"]).toBe(true);
|
|
262
|
+
expect(update["lastSeenAt"]).toEqual(NOW);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("an unreachable poll with no walk data still records the attempt", async () => {
|
|
241
266
|
mockServices();
|
|
242
267
|
|
|
243
268
|
await runWalk(
|
|
@@ -249,8 +274,17 @@ describe("NetworkInventoryUtil.updateFromWalk — reachability gating", () => {
|
|
|
249
274
|
{ isOnline: false },
|
|
250
275
|
);
|
|
251
276
|
|
|
252
|
-
|
|
253
|
-
|
|
277
|
+
const update: DeviceUpdatePayload = deviceUpdatePayload();
|
|
278
|
+
|
|
279
|
+
/*
|
|
280
|
+
* This write used to be skipped entirely ("nothing worth writing"),
|
|
281
|
+
* which is exactly what left the reader unable to tell a failing device
|
|
282
|
+
* from one nothing had got round to polling.
|
|
283
|
+
*/
|
|
284
|
+
expect(update["lastPolledAt"]).toEqual(NOW);
|
|
285
|
+
expect(update["isReachable"]).toBe(false);
|
|
286
|
+
// The device did not answer, so its last contact must not move.
|
|
287
|
+
expect(update).not.toHaveProperty("lastSeenAt");
|
|
254
288
|
});
|
|
255
289
|
|
|
256
290
|
test("an unreachable poll with walk data still enriches but never stamps lastSeenAt", async () => {
|
|
@@ -267,15 +301,54 @@ describe("NetworkInventoryUtil.updateFromWalk — reachability gating", () => {
|
|
|
267
301
|
const update: DeviceUpdatePayload = deviceUpdatePayload();
|
|
268
302
|
|
|
269
303
|
expect(update["sysName"]).toBe("core-sw-01");
|
|
304
|
+
expect(update["isReachable"]).toBe(false);
|
|
305
|
+
expect(update["lastPolledAt"]).toEqual(NOW);
|
|
270
306
|
expect(update).not.toHaveProperty("lastSeenAt");
|
|
271
307
|
});
|
|
272
308
|
|
|
273
|
-
test("a walk that reports no reachability at all is treated as
|
|
309
|
+
test("a walk that reports no reachability at all is treated as answered", async () => {
|
|
274
310
|
mockServices();
|
|
275
311
|
|
|
276
312
|
await runWalk(undefined, { isOnline: undefined });
|
|
277
313
|
|
|
278
|
-
|
|
314
|
+
const update: DeviceUpdatePayload = deviceUpdatePayload();
|
|
315
|
+
|
|
316
|
+
expect(update["isReachable"]).toBe(true);
|
|
317
|
+
expect(update["lastPolledAt"]).toEqual(NOW);
|
|
318
|
+
expect(update["lastSeenAt"]).toEqual(NOW);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
test("lastPolledAt and lastSeenAt are the SAME instant on a good walk", async () => {
|
|
322
|
+
mockServices();
|
|
323
|
+
|
|
324
|
+
await runWalk();
|
|
325
|
+
|
|
326
|
+
const update: DeviceUpdatePayload = deviceUpdatePayload();
|
|
327
|
+
|
|
328
|
+
/*
|
|
329
|
+
* The reader treats "polled later than seen" as evidence of a failed
|
|
330
|
+
* attempt, so a successful walk must not leave even a millisecond of
|
|
331
|
+
* skew between the two.
|
|
332
|
+
*/
|
|
333
|
+
expect((update["lastPolledAt"] as Date).getTime()).toBe(
|
|
334
|
+
(update["lastSeenAt"] as Date).getTime(),
|
|
335
|
+
);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("a failed poll after a successful one leaves lastSeenAt behind lastPolledAt", async () => {
|
|
339
|
+
mockServices();
|
|
340
|
+
|
|
341
|
+
await runWalk();
|
|
342
|
+
const good: DeviceUpdatePayload = deviceUpdatePayload();
|
|
343
|
+
|
|
344
|
+
deviceUpdateSpy.mockClear();
|
|
345
|
+
await runWalk({ isOnline: false }, { isOnline: false });
|
|
346
|
+
const bad: DeviceUpdatePayload = deviceUpdatePayload();
|
|
347
|
+
|
|
348
|
+
expect(bad["isReachable"]).toBe(false);
|
|
349
|
+
expect(bad).not.toHaveProperty("lastSeenAt");
|
|
350
|
+
// The previous success stays the device's last contact.
|
|
351
|
+
expect(good["lastSeenAt"]).toEqual(NOW);
|
|
279
352
|
});
|
|
280
353
|
});
|
|
281
354
|
|
|
@@ -690,7 +763,7 @@ describe("NetworkInventoryUtil.updateFromWalk — walks without interfaces", ()
|
|
|
690
763
|
expect(interfaceCreateSpy).not.toHaveBeenCalled();
|
|
691
764
|
});
|
|
692
765
|
|
|
693
|
-
test("a walk with no snmpResponse at all still
|
|
766
|
+
test("a walk with no snmpResponse at all still records the poll", async () => {
|
|
694
767
|
mockServices();
|
|
695
768
|
|
|
696
769
|
await NetworkInventoryUtil.updateFromWalk({
|
|
@@ -700,7 +773,12 @@ describe("NetworkInventoryUtil.updateFromWalk — walks without interfaces", ()
|
|
|
700
773
|
isOnline: true,
|
|
701
774
|
});
|
|
702
775
|
|
|
703
|
-
|
|
776
|
+
// Exactly the reachability columns and nothing else — no walk, no data.
|
|
777
|
+
expect(deviceUpdatePayload()).toEqual({
|
|
778
|
+
lastPolledAt: NOW,
|
|
779
|
+
isReachable: true,
|
|
780
|
+
lastSeenAt: NOW,
|
|
781
|
+
});
|
|
704
782
|
expect(interfaceFindSpy).not.toHaveBeenCalled();
|
|
705
783
|
});
|
|
706
784
|
});
|
|
@@ -38,6 +38,70 @@ describe("combineWithPrivacyClause", () => {
|
|
|
38
38
|
expect(children[1]).toBe(clause);
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
+
it("keeps nested ObjectID values as UUID strings after database transformation", () => {
|
|
42
|
+
/*
|
|
43
|
+
* The clause this function builds lands on an ObjectID column
|
|
44
|
+
* (incidentId, alertId, the episode ids, an ownedThrough FK), and those
|
|
45
|
+
* columns carry ObjectID.getDatabaseTransformer(). TypeORM hands each
|
|
46
|
+
* child of the And() to that transformer, so a transformer that cannot
|
|
47
|
+
* cope with an operator replaces the caller's own equality with
|
|
48
|
+
* "[object Object]" — which Postgres then rejects for a uuid column with
|
|
49
|
+
* 22P02, i.e. a 500 on the incident/alert Feed, Internal Notes and Owners
|
|
50
|
+
* reads for anyone who is not a project owner or admin.
|
|
51
|
+
* https://github.com/OneUptime/oneuptime/issues/2497
|
|
52
|
+
*/
|
|
53
|
+
const incidentId: ObjectID = ObjectID.generate();
|
|
54
|
+
const clause: FindOperatorType = makePrivacyClause();
|
|
55
|
+
const combined: any = combineWithPrivacyClause(incidentId, clause as any);
|
|
56
|
+
|
|
57
|
+
const transformed: any = ObjectID.getDatabaseTransformer().to(combined);
|
|
58
|
+
|
|
59
|
+
expect(transformed).toBeInstanceOf(FindOperator);
|
|
60
|
+
expect(transformed.type).toBe("and");
|
|
61
|
+
expect(transformed.value[0]).toBeInstanceOf(FindOperator);
|
|
62
|
+
expect(transformed.value[0].type).toBe("equal");
|
|
63
|
+
expect(transformed.value[0].value).toBe(incidentId.toString());
|
|
64
|
+
expect(transformed.value[0].value).not.toBe("[object Object]");
|
|
65
|
+
expect(transformed.value[1]).toBe(clause);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("survives the transformation twice, as findBy then countBy would", () => {
|
|
69
|
+
/*
|
|
70
|
+
* BaseAPI.getList hands the same query object to findBy and then countBy,
|
|
71
|
+
* and the transformer mutates the operator in place.
|
|
72
|
+
*/
|
|
73
|
+
const incidentId: ObjectID = ObjectID.generate();
|
|
74
|
+
const clause: FindOperatorType = makePrivacyClause();
|
|
75
|
+
const combined: any = combineWithPrivacyClause(incidentId, clause as any);
|
|
76
|
+
|
|
77
|
+
ObjectID.getDatabaseTransformer().to(combined);
|
|
78
|
+
const transformed: any = ObjectID.getDatabaseTransformer().to(combined);
|
|
79
|
+
|
|
80
|
+
expect(transformed.value[0].value).toBe(incidentId.toString());
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("keeps a re-combined clause intact when the privacy filter runs twice", () => {
|
|
84
|
+
/*
|
|
85
|
+
* countBy's hook sees the And that findBy's hook already built and wraps
|
|
86
|
+
* it again, so the operator handed to the transformer is And(And(...)).
|
|
87
|
+
*/
|
|
88
|
+
const incidentId: ObjectID = ObjectID.generate();
|
|
89
|
+
const firstClause: FindOperatorType = makePrivacyClause();
|
|
90
|
+
const secondClause: FindOperatorType = makePrivacyClause();
|
|
91
|
+
|
|
92
|
+
const once: any = combineWithPrivacyClause(incidentId, firstClause as any);
|
|
93
|
+
const twice: any = combineWithPrivacyClause(once, secondClause as any);
|
|
94
|
+
|
|
95
|
+
const transformed: any = ObjectID.getDatabaseTransformer().to(twice);
|
|
96
|
+
|
|
97
|
+
expect(transformed.type).toBe("and");
|
|
98
|
+
expect(transformed.value[0]).toBe(once);
|
|
99
|
+
expect(transformed.value[0].type).toBe("and");
|
|
100
|
+
expect(transformed.value[0].value[0].value).toBe(incidentId.toString());
|
|
101
|
+
expect(transformed.value[0].value[1]).toBe(firstClause);
|
|
102
|
+
expect(transformed.value[1]).toBe(secondClause);
|
|
103
|
+
});
|
|
104
|
+
|
|
41
105
|
it("ANDs an existing string equality with the privacy clause", () => {
|
|
42
106
|
const clause: FindOperatorType = makePrivacyClause();
|
|
43
107
|
const combined: any = combineWithPrivacyClause("some-id", clause as any);
|