@oneuptime/common 11.5.7 → 11.5.9
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/AIAgentTaskPullRequest.ts +15 -6
- package/Models/DatabaseModels/AIRun.ts +68 -0
- package/Models/DatabaseModels/AIRunEvent.ts +34 -1
- package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +215 -0
- package/Models/DatabaseModels/Project.ts +25 -0
- package/Models/DatabaseModels/StatusPage.ts +42 -0
- package/Server/API/AIAgentTaskAPI.ts +38 -12
- package/Server/API/AIAgentTaskLogAPI.ts +49 -0
- package/Server/API/AIChatAPI.ts +16 -0
- package/Server/API/AIReadinessAPI.ts +84 -0
- package/Server/API/CodeFixRunAPI.ts +190 -57
- package/Server/API/TelemetryExceptionAPI.ts +6 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784105912819-BackfillCodeFixTaskType.ts +41 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.ts +95 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.ts +52 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.ts +30 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.ts +49 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
- package/Server/Middleware/MasterAdminAuthorization.ts +1 -1
- package/Server/Middleware/ProjectAuthorization.ts +1 -1
- package/Server/Services/AIRunEventService.ts +15 -1
- package/Server/Services/AIRunService.ts +48 -12
- package/Server/Services/LlmProviderService.ts +2 -0
- package/Server/Services/ProjectService.ts +42 -0
- package/Server/Services/StatusPageService.ts +87 -0
- package/Server/Services/TelemetryExceptionService.ts +19 -90
- package/Server/Types/Markdown.ts +9 -9
- package/Server/Types/MarkdownSlugify.ts +47 -0
- package/Server/Utils/AI/AIRunPrivacyFilter.ts +106 -0
- package/Server/Utils/AI/AIRunTranscript.ts +240 -0
- package/Server/Utils/AI/Chat/ChatAgentRunner.ts +9 -0
- package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +119 -5
- package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +75 -0
- package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +235 -0
- package/Server/Utils/AI/SRE/Insights/FixRouting.ts +2 -3
- package/Server/Utils/AI/Toolbox/CodeTools.ts +883 -0
- package/Server/Utils/AI/Toolbox/CodeWriteTools.ts +519 -0
- package/Server/Utils/AI/Toolbox/Index.ts +27 -0
- package/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.ts +279 -0
- package/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.ts +19 -1
- package/Server/Utils/CodeRepository/GitHub/GitHub.ts +600 -0
- package/Server/Utils/CodeRepository/StackTraceRepoResolver.ts +7 -2
- package/Server/Utils/LLM/LLMService.ts +397 -18
- package/Server/Utils/Monitor/NetworkDeviceHydrationUtil.ts +4 -11
- package/Tests/Models/StatusPageEnableMcpServer.test.ts +125 -0
- package/Tests/Server/Services/StatusPageServiceMcp.test.ts +223 -0
- package/Tests/Server/Services/TelemetryExceptionAIFixReadiness.test.ts +26 -3
- package/Tests/Server/Services/TelemetryExceptionCodeFixRun.test.ts +12 -0
- package/Tests/Server/Types/Markdown.test.ts +106 -0
- package/Tests/Server/Utils/AI/AIRunPrivacyFilter.test.ts +295 -0
- package/Tests/Server/Utils/AI/AIRunTranscript.test.ts +182 -0
- package/Tests/Server/Utils/AI/CodeFixReadiness.test.ts +411 -0
- package/Tests/Server/Utils/AI/CodeTools.test.ts +596 -0
- package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +475 -0
- package/Tests/Server/Utils/AI/LLMServiceModelCompatibility.test.ts +548 -0
- package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +142 -0
- package/Tests/Server/Utils/AI/ScheduledMaintenanceTools.test.ts +176 -0
- package/Tests/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.test.ts +63 -0
- package/Tests/Server/Utils/GitHubGetFileContent.test.ts +197 -0
- package/Tests/Server/Utils/Monitor/NetworkDeviceHydrationUtil.test.ts +502 -0
- package/Tests/Types/AI/AIAgentTaskStatus.test.ts +7 -6
- package/Tests/Types/AI/AIChatPageContext.test.ts +0 -0
- package/Tests/Types/AI/AIRunStatus.test.ts +42 -0
- package/Tests/Types/Log/LogSeverity.test.ts +78 -0
- package/Tests/UI/Components/BasicFormDropdownNormalization.test.tsx +167 -0
- package/Tests/UI/Components/Charts/AxisTickColor.test.tsx +66 -0
- package/Tests/UI/Utils/AIChatExport/ChatWidgetData.test.ts +319 -0
- package/Tests/UI/Utils/AIChatExport/ChatWidgetMarkdown.test.ts +188 -0
- package/Types/AI/AIAgentTaskStatus.ts +13 -0
- package/Types/AI/AIChatPageContext.ts +137 -0
- package/Types/AI/AIChatTypes.ts +44 -0
- package/Types/AI/AIFixReadiness.ts +46 -0
- package/Types/AI/AIRunStatus.ts +11 -0
- package/Types/Email/EmailTemplateType.ts +2 -0
- package/Types/Log/LogSeverity.ts +61 -0
- package/Types/Monitor/SnmpMonitor/SnmpVersion.ts +40 -0
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +8 -2
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +3 -1
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +8 -2
- package/UI/Components/EditionLabel/EditionLabel.tsx +821 -331
- package/UI/Components/Forms/BasicForm.tsx +26 -8
- package/UI/Styles/Theme.css +1314 -0
- package/UI/Utils/AIChatExport/ChatWidgetData.ts +316 -0
- package/UI/Utils/AIChatExport/ChatWidgetMarkdown.ts +349 -0
- package/UI/Utils/AIChatExport/ConversationMarkdown.ts +177 -0
- package/UI/Utils/AIChatExport/MarkdownBlocks.ts +327 -0
- package/UI/Utils/AIChatExport/MarkdownSafety.ts +215 -0
- package/UI/Utils/DownloadFile.ts +54 -0
- package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js +15 -7
- package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AIRun.js +69 -0
- package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AIRunEvent.js +31 -0
- package/build/dist/Models/DatabaseModels/AIRunEvent.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +225 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +27 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +43 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/AIAgentTaskAPI.js +37 -11
- package/build/dist/Server/API/AIAgentTaskAPI.js.map +1 -1
- package/build/dist/Server/API/AIAgentTaskLogAPI.js +32 -7
- package/build/dist/Server/API/AIAgentTaskLogAPI.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +9 -0
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/AIReadinessAPI.js +57 -0
- package/build/dist/Server/API/AIReadinessAPI.js.map +1 -0
- package/build/dist/Server/API/CodeFixRunAPI.js +165 -51
- package/build/dist/Server/API/CodeFixRunAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryExceptionAPI.js +4 -0
- package/build/dist/Server/API/TelemetryExceptionAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784105912819-BackfillCodeFixTaskType.js +36 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784105912819-BackfillCodeFixTaskType.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.js +80 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.js +37 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.js +23 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/MasterAdminAuthorization.js +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +1 -1
- package/build/dist/Server/Services/AIRunEventService.js +8 -0
- package/build/dist/Server/Services/AIRunEventService.js.map +1 -1
- package/build/dist/Server/Services/AIRunService.js +39 -3
- package/build/dist/Server/Services/AIRunService.js.map +1 -1
- package/build/dist/Server/Services/LlmProviderService.js +2 -0
- package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +39 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +81 -0
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryExceptionService.js +14 -61
- package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +8 -8
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Types/MarkdownSlugify.js +47 -0
- package/build/dist/Server/Types/MarkdownSlugify.js.map +1 -0
- package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js +82 -0
- package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js.map +1 -0
- package/build/dist/Server/Utils/AI/AIRunTranscript.js +180 -0
- package/build/dist/Server/Utils/AI/AIRunTranscript.js.map +1 -0
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +1 -0
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +103 -5
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +55 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +211 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/Insights/FixRouting.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +654 -0
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js +389 -0
- package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js +19 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.js +239 -0
- package/build/dist/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.js.map +1 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.js +19 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +439 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/StackTraceRepoResolver.js +1 -1
- package/build/dist/Server/Utils/CodeRepository/StackTraceRepoResolver.js.map +1 -1
- package/build/dist/Server/Utils/LLM/LLMService.js +275 -19
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js +2 -11
- package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js.map +1 -1
- package/build/dist/Types/AI/AIAgentTaskStatus.js +12 -0
- package/build/dist/Types/AI/AIAgentTaskStatus.js.map +1 -1
- package/build/dist/Types/AI/AIChatPageContext.js +101 -0
- package/build/dist/Types/AI/AIChatPageContext.js.map +1 -0
- package/build/dist/Types/AI/AIChatTypes.js +2 -0
- package/build/dist/Types/AI/AIChatTypes.js.map +1 -1
- package/build/dist/Types/AI/AIFixReadiness.js +9 -0
- package/build/dist/Types/AI/AIFixReadiness.js.map +1 -0
- package/build/dist/Types/AI/AIRunStatus.js +11 -0
- package/build/dist/Types/AI/AIRunStatus.js.map +1 -1
- package/build/dist/Types/Email/EmailTemplateType.js +1 -0
- package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
- package/build/dist/Types/Log/LogSeverity.js +56 -0
- package/build/dist/Types/Log/LogSeverity.js.map +1 -1
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpVersion.js +38 -0
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpVersion.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +9 -2
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +3 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +9 -2
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +424 -139
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicForm.js +24 -6
- package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
- package/build/dist/UI/Utils/AIChatExport/ChatWidgetData.js +217 -0
- package/build/dist/UI/Utils/AIChatExport/ChatWidgetData.js.map +1 -0
- package/build/dist/UI/Utils/AIChatExport/ChatWidgetMarkdown.js +234 -0
- package/build/dist/UI/Utils/AIChatExport/ChatWidgetMarkdown.js.map +1 -0
- package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js +111 -0
- package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js.map +1 -0
- package/build/dist/UI/Utils/AIChatExport/MarkdownBlocks.js +233 -0
- package/build/dist/UI/Utils/AIChatExport/MarkdownBlocks.js.map +1 -0
- package/build/dist/UI/Utils/AIChatExport/MarkdownSafety.js +134 -0
- package/build/dist/UI/Utils/AIChatExport/MarkdownSafety.js.map +1 -0
- package/build/dist/UI/Utils/DownloadFile.js +41 -0
- package/build/dist/UI/Utils/DownloadFile.js.map +1 -0
- package/package.json +5 -1
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "@jest/globals";
|
|
2
|
+
import NetworkDeviceHydrationUtil, {
|
|
3
|
+
HydratableMonitor,
|
|
4
|
+
} from "../../../../Server/Utils/Monitor/NetworkDeviceHydrationUtil";
|
|
5
|
+
import NetworkDeviceService from "../../../../Server/Services/NetworkDeviceService";
|
|
6
|
+
import NetworkDevice from "../../../../Models/DatabaseModels/NetworkDevice";
|
|
7
|
+
import MonitorStep from "../../../../Types/Monitor/MonitorStep";
|
|
8
|
+
import MonitorSteps from "../../../../Types/Monitor/MonitorSteps";
|
|
9
|
+
import MonitorType from "../../../../Types/Monitor/MonitorType";
|
|
10
|
+
import MonitorStepSnmpMonitor from "../../../../Types/Monitor/MonitorStepSnmpMonitor";
|
|
11
|
+
import SnmpVersion from "../../../../Types/Monitor/SnmpMonitor/SnmpVersion";
|
|
12
|
+
import SnmpV3Auth from "../../../../Types/Monitor/SnmpMonitor/SnmpV3Auth";
|
|
13
|
+
import SnmpSecurityLevel from "../../../../Types/Monitor/SnmpMonitor/SnmpSecurityLevel";
|
|
14
|
+
import SnmpAuthProtocol from "../../../../Types/Monitor/SnmpMonitor/SnmpAuthProtocol";
|
|
15
|
+
import SnmpPrivProtocol from "../../../../Types/Monitor/SnmpMonitor/SnmpPrivProtocol";
|
|
16
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
17
|
+
|
|
18
|
+
/*
|
|
19
|
+
* Network Device monitors carry only a reference to a NetworkDevice; the probe
|
|
20
|
+
* is stateless and understands nothing but concrete SNMP config. This util is
|
|
21
|
+
* the single place that turns the stored device into the `snmpMonitor` block a
|
|
22
|
+
* probe can execute, so it is where an SNMP v3 device either gets its
|
|
23
|
+
* credentials or silently loses them.
|
|
24
|
+
*
|
|
25
|
+
* Customer report: "Test Metrics" (Test Monitor) errors for a monitor on a v3
|
|
26
|
+
* device while v2 works. The probe throws when a v3 step reaches it without a
|
|
27
|
+
* v3 username (SnmpMonitor.createSnmpSession fails loudly rather than
|
|
28
|
+
* downgrading to v2c/"public"), so these tests pin exactly when this util does
|
|
29
|
+
* and does not produce snmpV3Auth — and that it hydrates the MonitorTest shape
|
|
30
|
+
* used by Test Monitor, not just saved Monitors.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const DEVICE_ID: string = "8f2c1f0e-0000-4000-8000-0000000000aa";
|
|
34
|
+
|
|
35
|
+
/*
|
|
36
|
+
* Columns are assigned loosely so a test can pin a column to undefined, which
|
|
37
|
+
* Partial<NetworkDevice> forbids under exactOptionalPropertyTypes.
|
|
38
|
+
*/
|
|
39
|
+
type DeviceFields = Record<string, unknown>;
|
|
40
|
+
|
|
41
|
+
function buildDevice(fields: DeviceFields): NetworkDevice {
|
|
42
|
+
const device: NetworkDevice = new NetworkDevice();
|
|
43
|
+
device._id = DEVICE_ID;
|
|
44
|
+
device.hostname = "10.0.0.1";
|
|
45
|
+
Object.assign(device, fields);
|
|
46
|
+
|
|
47
|
+
return device;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function buildV3Device(fields?: DeviceFields): NetworkDevice {
|
|
51
|
+
return buildDevice({
|
|
52
|
+
snmpVersion: "V3",
|
|
53
|
+
snmpV3SecurityLevel: SnmpSecurityLevel.AuthPriv,
|
|
54
|
+
snmpV3Username: "monitoring",
|
|
55
|
+
snmpV3AuthProtocol: SnmpAuthProtocol.SHA,
|
|
56
|
+
snmpV3AuthKey: "auth-passphrase",
|
|
57
|
+
snmpV3PrivProtocol: SnmpPrivProtocol.AES,
|
|
58
|
+
snmpV3PrivKey: "priv-passphrase",
|
|
59
|
+
...fields,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/*
|
|
64
|
+
* Structural stand-in for both Monitor and MonitorTest — hydrateNetworkDeviceMonitors
|
|
65
|
+
* accepts either, which is what the Test Monitor path depends on.
|
|
66
|
+
*/
|
|
67
|
+
function buildMonitor(options?: {
|
|
68
|
+
monitorType?: MonitorType | undefined;
|
|
69
|
+
networkDeviceId?: string | undefined;
|
|
70
|
+
monitorInterfaces?: boolean | undefined;
|
|
71
|
+
}): HydratableMonitor {
|
|
72
|
+
const step: MonitorStep = new MonitorStep();
|
|
73
|
+
step.data = {
|
|
74
|
+
...step.data,
|
|
75
|
+
networkDeviceMonitor: {
|
|
76
|
+
networkDeviceId:
|
|
77
|
+
options && "networkDeviceId" in options
|
|
78
|
+
? options.networkDeviceId
|
|
79
|
+
: DEVICE_ID,
|
|
80
|
+
monitorInterfaces: options?.monitorInterfaces ?? true,
|
|
81
|
+
oids: [{ oid: "1.3.6.1.2.1.1.1.0", name: "sysDescr" }],
|
|
82
|
+
},
|
|
83
|
+
} as MonitorStep["data"];
|
|
84
|
+
|
|
85
|
+
const monitorSteps: MonitorSteps = new MonitorSteps();
|
|
86
|
+
monitorSteps.data = {
|
|
87
|
+
monitorStepsInstanceArray: [step],
|
|
88
|
+
defaultMonitorStatusId: undefined,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
id: ObjectID.generate(),
|
|
93
|
+
monitorType: options?.monitorType ?? MonitorType.NetworkDevice,
|
|
94
|
+
monitorSteps: monitorSteps,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function mockDevices(devices: Array<NetworkDevice>): void {
|
|
99
|
+
jest.spyOn(NetworkDeviceService, "findBy").mockResolvedValue(devices);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function hydratedStep(
|
|
103
|
+
monitor: HydratableMonitor,
|
|
104
|
+
): MonitorStepSnmpMonitor | undefined {
|
|
105
|
+
return monitor.monitorSteps?.data?.monitorStepsInstanceArray[0]?.data
|
|
106
|
+
?.snmpMonitor;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
describe("NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors", () => {
|
|
110
|
+
afterEach(() => {
|
|
111
|
+
jest.restoreAllMocks();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe("SNMP v3 credential assembly", () => {
|
|
115
|
+
test("a v3 device hands the probe a complete authPriv SnmpV3Auth", async () => {
|
|
116
|
+
mockDevices([buildV3Device()]);
|
|
117
|
+
const monitor: HydratableMonitor = buildMonitor();
|
|
118
|
+
|
|
119
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([monitor]);
|
|
120
|
+
|
|
121
|
+
const snmp: MonitorStepSnmpMonitor | undefined = hydratedStep(monitor);
|
|
122
|
+
|
|
123
|
+
expect(snmp?.snmpVersion).toBe(SnmpVersion.V3);
|
|
124
|
+
expect(snmp?.snmpV3Auth).toEqual({
|
|
125
|
+
securityLevel: SnmpSecurityLevel.AuthPriv,
|
|
126
|
+
username: "monitoring",
|
|
127
|
+
authProtocol: SnmpAuthProtocol.SHA,
|
|
128
|
+
authKey: "auth-passphrase",
|
|
129
|
+
privProtocol: SnmpPrivProtocol.AES,
|
|
130
|
+
privKey: "priv-passphrase",
|
|
131
|
+
} as SnmpV3Auth);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
/*
|
|
135
|
+
* The probe refuses to open a v3 session without a username. If this util
|
|
136
|
+
* ever hands back a v3 step with no auth, Test Monitor surfaces that
|
|
137
|
+
* refusal as the error the customer reported — so a fully configured
|
|
138
|
+
* device must never produce one.
|
|
139
|
+
*/
|
|
140
|
+
test("a fully configured v3 device never yields a v3 step without credentials", async () => {
|
|
141
|
+
mockDevices([buildV3Device()]);
|
|
142
|
+
const monitor: HydratableMonitor = buildMonitor();
|
|
143
|
+
|
|
144
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([monitor]);
|
|
145
|
+
|
|
146
|
+
const snmp: MonitorStepSnmpMonitor | undefined = hydratedStep(monitor);
|
|
147
|
+
|
|
148
|
+
expect(snmp?.snmpVersion).toBe(SnmpVersion.V3);
|
|
149
|
+
expect(snmp?.snmpV3Auth?.username).toBeTruthy();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("an authNoPriv device carries auth but no privacy material", async () => {
|
|
153
|
+
mockDevices([
|
|
154
|
+
buildV3Device({
|
|
155
|
+
snmpV3SecurityLevel: SnmpSecurityLevel.AuthNoPriv,
|
|
156
|
+
snmpV3PrivProtocol: undefined,
|
|
157
|
+
snmpV3PrivKey: undefined,
|
|
158
|
+
}),
|
|
159
|
+
]);
|
|
160
|
+
const monitor: HydratableMonitor = buildMonitor();
|
|
161
|
+
|
|
162
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([monitor]);
|
|
163
|
+
|
|
164
|
+
const auth: SnmpV3Auth | undefined = hydratedStep(monitor)?.snmpV3Auth;
|
|
165
|
+
|
|
166
|
+
expect(auth?.securityLevel).toBe(SnmpSecurityLevel.AuthNoPriv);
|
|
167
|
+
expect(auth?.authKey).toBe("auth-passphrase");
|
|
168
|
+
expect(auth?.privProtocol).toBeUndefined();
|
|
169
|
+
expect(auth?.privKey).toBeUndefined();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("a noAuthNoPriv device carries only the username", async () => {
|
|
173
|
+
mockDevices([
|
|
174
|
+
buildV3Device({
|
|
175
|
+
snmpV3SecurityLevel: SnmpSecurityLevel.NoAuthNoPriv,
|
|
176
|
+
snmpV3AuthProtocol: undefined,
|
|
177
|
+
snmpV3AuthKey: undefined,
|
|
178
|
+
snmpV3PrivProtocol: undefined,
|
|
179
|
+
snmpV3PrivKey: undefined,
|
|
180
|
+
}),
|
|
181
|
+
]);
|
|
182
|
+
const monitor: HydratableMonitor = buildMonitor();
|
|
183
|
+
|
|
184
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([monitor]);
|
|
185
|
+
|
|
186
|
+
const auth: SnmpV3Auth | undefined = hydratedStep(monitor)?.snmpV3Auth;
|
|
187
|
+
|
|
188
|
+
expect(auth?.securityLevel).toBe(SnmpSecurityLevel.NoAuthNoPriv);
|
|
189
|
+
expect(auth?.username).toBe("monitoring");
|
|
190
|
+
expect(auth?.authKey).toBeUndefined();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("a v3 device with no username yields no auth at all", async () => {
|
|
194
|
+
mockDevices([
|
|
195
|
+
buildDevice({ snmpVersion: "V3", snmpV3Username: undefined }),
|
|
196
|
+
]);
|
|
197
|
+
const monitor: HydratableMonitor = buildMonitor();
|
|
198
|
+
|
|
199
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([monitor]);
|
|
200
|
+
|
|
201
|
+
expect(hydratedStep(monitor)?.snmpV3Auth).toBeUndefined();
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("a v3 device with an unset security level defaults to noAuthNoPriv", async () => {
|
|
205
|
+
mockDevices([
|
|
206
|
+
buildDevice({ snmpVersion: "V3", snmpV3Username: "monitoring" }),
|
|
207
|
+
]);
|
|
208
|
+
const monitor: HydratableMonitor = buildMonitor();
|
|
209
|
+
|
|
210
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([monitor]);
|
|
211
|
+
|
|
212
|
+
expect(hydratedStep(monitor)?.snmpV3Auth?.securityLevel).toBe(
|
|
213
|
+
SnmpSecurityLevel.NoAuthNoPriv,
|
|
214
|
+
);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe("legacy snmpV3Auth JSON column", () => {
|
|
219
|
+
test("a device predating the flattened columns still authenticates", async () => {
|
|
220
|
+
mockDevices([
|
|
221
|
+
buildDevice({
|
|
222
|
+
snmpVersion: "V3",
|
|
223
|
+
snmpV3Auth: {
|
|
224
|
+
securityLevel: SnmpSecurityLevel.AuthPriv,
|
|
225
|
+
username: "legacy-user",
|
|
226
|
+
authProtocol: SnmpAuthProtocol.MD5,
|
|
227
|
+
authKey: "legacy-auth",
|
|
228
|
+
privProtocol: SnmpPrivProtocol.DES,
|
|
229
|
+
privKey: "legacy-priv",
|
|
230
|
+
},
|
|
231
|
+
}),
|
|
232
|
+
]);
|
|
233
|
+
const monitor: HydratableMonitor = buildMonitor();
|
|
234
|
+
|
|
235
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([monitor]);
|
|
236
|
+
|
|
237
|
+
expect(hydratedStep(monitor)?.snmpV3Auth?.username).toBe("legacy-user");
|
|
238
|
+
expect(hydratedStep(monitor)?.snmpV3Auth?.authProtocol).toBe(
|
|
239
|
+
SnmpAuthProtocol.MD5,
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("the flattened columns win over a stale legacy blob", async () => {
|
|
244
|
+
mockDevices([
|
|
245
|
+
buildV3Device({
|
|
246
|
+
snmpV3Auth: {
|
|
247
|
+
securityLevel: SnmpSecurityLevel.NoAuthNoPriv,
|
|
248
|
+
username: "stale-legacy-user",
|
|
249
|
+
},
|
|
250
|
+
}),
|
|
251
|
+
]);
|
|
252
|
+
const monitor: HydratableMonitor = buildMonitor();
|
|
253
|
+
|
|
254
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([monitor]);
|
|
255
|
+
|
|
256
|
+
expect(hydratedStep(monitor)?.snmpV3Auth?.username).toBe("monitoring");
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("a legacy blob with no username is ignored", async () => {
|
|
260
|
+
mockDevices([
|
|
261
|
+
buildDevice({
|
|
262
|
+
snmpVersion: "V3",
|
|
263
|
+
snmpV3Auth: { securityLevel: SnmpSecurityLevel.AuthPriv },
|
|
264
|
+
}),
|
|
265
|
+
]);
|
|
266
|
+
const monitor: HydratableMonitor = buildMonitor();
|
|
267
|
+
|
|
268
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([monitor]);
|
|
269
|
+
|
|
270
|
+
expect(hydratedStep(monitor)?.snmpV3Auth).toBeUndefined();
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
describe("SNMP version parsing from the stored column", () => {
|
|
275
|
+
/*
|
|
276
|
+
* The column is free text and both spellings exist in the wild: the
|
|
277
|
+
* dropdown writes enum keys, other writers and older defaults use the enum
|
|
278
|
+
* values. The probe branches on SnmpVersion, so both must map through.
|
|
279
|
+
*/
|
|
280
|
+
test.each([
|
|
281
|
+
["V3", SnmpVersion.V3],
|
|
282
|
+
["3", SnmpVersion.V3],
|
|
283
|
+
["v3", SnmpVersion.V3],
|
|
284
|
+
["V1", SnmpVersion.V1],
|
|
285
|
+
["1", SnmpVersion.V1],
|
|
286
|
+
["v1", SnmpVersion.V1],
|
|
287
|
+
["V2c", SnmpVersion.V2c],
|
|
288
|
+
["2c", SnmpVersion.V2c],
|
|
289
|
+
["v2c", SnmpVersion.V2c],
|
|
290
|
+
])(
|
|
291
|
+
"a device storing snmpVersion=%s polls as %s",
|
|
292
|
+
async (stored: string, expected: SnmpVersion) => {
|
|
293
|
+
mockDevices([buildV3Device({ snmpVersion: stored })]);
|
|
294
|
+
const monitor: HydratableMonitor = buildMonitor();
|
|
295
|
+
|
|
296
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([
|
|
297
|
+
monitor,
|
|
298
|
+
]);
|
|
299
|
+
|
|
300
|
+
expect(hydratedStep(monitor)?.snmpVersion).toBe(expected);
|
|
301
|
+
},
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
test("an unset or unrecognised version falls back to v2c", async () => {
|
|
305
|
+
for (const stored of [undefined, "", "nonsense"]) {
|
|
306
|
+
mockDevices([buildDevice({ snmpVersion: stored })]);
|
|
307
|
+
const monitor: HydratableMonitor = buildMonitor();
|
|
308
|
+
|
|
309
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([
|
|
310
|
+
monitor,
|
|
311
|
+
]);
|
|
312
|
+
|
|
313
|
+
expect(hydratedStep(monitor)?.snmpVersion).toBe(SnmpVersion.V2c);
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
/*
|
|
318
|
+
* A v3 device whose version is stored in enum-value form must still reach
|
|
319
|
+
* the probe as v3 WITH its credentials — parsing the version and building
|
|
320
|
+
* the auth are independent, and getting only one right is what downgrades
|
|
321
|
+
* a device to a cleartext v2c poll.
|
|
322
|
+
*/
|
|
323
|
+
test('a v3 device stored as "3" keeps both its version and its credentials', async () => {
|
|
324
|
+
mockDevices([buildV3Device({ snmpVersion: "3" })]);
|
|
325
|
+
const monitor: HydratableMonitor = buildMonitor();
|
|
326
|
+
|
|
327
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([monitor]);
|
|
328
|
+
|
|
329
|
+
expect(hydratedStep(monitor)?.snmpVersion).toBe(SnmpVersion.V3);
|
|
330
|
+
expect(hydratedStep(monitor)?.snmpV3Auth?.username).toBe("monitoring");
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
describe("v1/v2c community polling", () => {
|
|
335
|
+
test("a v2c device carries its community string and no v3 auth", async () => {
|
|
336
|
+
mockDevices([
|
|
337
|
+
buildDevice({ snmpVersion: "V2c", snmpCommunityString: "s3cret" }),
|
|
338
|
+
]);
|
|
339
|
+
const monitor: HydratableMonitor = buildMonitor();
|
|
340
|
+
|
|
341
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([monitor]);
|
|
342
|
+
|
|
343
|
+
const snmp: MonitorStepSnmpMonitor | undefined = hydratedStep(monitor);
|
|
344
|
+
|
|
345
|
+
expect(snmp?.snmpVersion).toBe(SnmpVersion.V2c);
|
|
346
|
+
expect(snmp?.communityString).toBe("s3cret");
|
|
347
|
+
expect(snmp?.snmpV3Auth).toBeUndefined();
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test("a v2c device with no community string leaves it unset for the probe to default", async () => {
|
|
351
|
+
mockDevices([buildDevice({ snmpVersion: "V2c" })]);
|
|
352
|
+
const monitor: HydratableMonitor = buildMonitor();
|
|
353
|
+
|
|
354
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([monitor]);
|
|
355
|
+
|
|
356
|
+
expect(hydratedStep(monitor)?.communityString).toBeUndefined();
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
describe("connection details and step options", () => {
|
|
361
|
+
test("hostname, port, oids and interface flag come from the device and step", async () => {
|
|
362
|
+
mockDevices([buildV3Device({ hostname: "192.168.5.9", snmpPort: 1610 })]);
|
|
363
|
+
const monitor: HydratableMonitor = buildMonitor();
|
|
364
|
+
|
|
365
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([monitor]);
|
|
366
|
+
|
|
367
|
+
const snmp: MonitorStepSnmpMonitor | undefined = hydratedStep(monitor);
|
|
368
|
+
|
|
369
|
+
expect(snmp?.hostname).toBe("192.168.5.9");
|
|
370
|
+
expect(snmp?.port).toBe(1610);
|
|
371
|
+
expect(snmp?.oids).toEqual([
|
|
372
|
+
{ oid: "1.3.6.1.2.1.1.1.0", name: "sysDescr" },
|
|
373
|
+
]);
|
|
374
|
+
expect(snmp?.monitorInterfaces).toBe(true);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test("a device with no port falls back to 161", async () => {
|
|
378
|
+
mockDevices([buildV3Device({ snmpPort: undefined })]);
|
|
379
|
+
const monitor: HydratableMonitor = buildMonitor();
|
|
380
|
+
|
|
381
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([monitor]);
|
|
382
|
+
|
|
383
|
+
expect(hydratedStep(monitor)?.port).toBe(161);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
test("monitorInterfaces=false is respected rather than defaulted on", async () => {
|
|
387
|
+
mockDevices([buildV3Device()]);
|
|
388
|
+
const monitor: HydratableMonitor = buildMonitor({
|
|
389
|
+
monitorInterfaces: false,
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([monitor]);
|
|
393
|
+
|
|
394
|
+
expect(hydratedStep(monitor)?.monitorInterfaces).toBe(false);
|
|
395
|
+
});
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
describe("guards", () => {
|
|
399
|
+
test("a non-NetworkDevice monitor is left untouched", async () => {
|
|
400
|
+
const findBy: jest.SpyInstance = jest
|
|
401
|
+
.spyOn(NetworkDeviceService, "findBy")
|
|
402
|
+
.mockResolvedValue([buildV3Device()]);
|
|
403
|
+
|
|
404
|
+
const monitor: HydratableMonitor = buildMonitor({
|
|
405
|
+
monitorType: MonitorType.Website,
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([monitor]);
|
|
409
|
+
|
|
410
|
+
expect(hydratedStep(monitor)).toBeUndefined();
|
|
411
|
+
expect(findBy).not.toHaveBeenCalled();
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
test("a step referencing a deleted device is not hydrated", async () => {
|
|
415
|
+
mockDevices([]);
|
|
416
|
+
const monitor: HydratableMonitor = buildMonitor();
|
|
417
|
+
|
|
418
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([monitor]);
|
|
419
|
+
|
|
420
|
+
expect(hydratedStep(monitor)).toBeUndefined();
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test("a step with no device reference is skipped without a lookup", async () => {
|
|
424
|
+
const findBy: jest.SpyInstance = jest
|
|
425
|
+
.spyOn(NetworkDeviceService, "findBy")
|
|
426
|
+
.mockResolvedValue([]);
|
|
427
|
+
|
|
428
|
+
const monitor: HydratableMonitor = buildMonitor({
|
|
429
|
+
networkDeviceId: undefined,
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([monitor]);
|
|
433
|
+
|
|
434
|
+
expect(hydratedStep(monitor)).toBeUndefined();
|
|
435
|
+
expect(findBy).not.toHaveBeenCalled();
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test("a device with no hostname is not hydrated", async () => {
|
|
439
|
+
mockDevices([buildV3Device({ hostname: undefined })]);
|
|
440
|
+
const monitor: HydratableMonitor = buildMonitor();
|
|
441
|
+
|
|
442
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([monitor]);
|
|
443
|
+
|
|
444
|
+
expect(hydratedStep(monitor)).toBeUndefined();
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test("an empty monitor list performs no lookup", async () => {
|
|
448
|
+
const findBy: jest.SpyInstance = jest
|
|
449
|
+
.spyOn(NetworkDeviceService, "findBy")
|
|
450
|
+
.mockResolvedValue([]);
|
|
451
|
+
|
|
452
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([]);
|
|
453
|
+
|
|
454
|
+
expect(findBy).not.toHaveBeenCalled();
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
describe("the Test Monitor path", () => {
|
|
459
|
+
/*
|
|
460
|
+
* Test Monitor posts a MonitorTest — a different model from Monitor, but
|
|
461
|
+
* structurally hydratable. It must receive the same v3 credentials the
|
|
462
|
+
* scheduled monitor does, or the test errors while live polling works.
|
|
463
|
+
*/
|
|
464
|
+
test("a MonitorTest-shaped object gets the same v3 credentials as a Monitor", async () => {
|
|
465
|
+
mockDevices([buildV3Device()]);
|
|
466
|
+
|
|
467
|
+
const monitorTest: HydratableMonitor = buildMonitor();
|
|
468
|
+
const monitor: HydratableMonitor = buildMonitor();
|
|
469
|
+
|
|
470
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([
|
|
471
|
+
monitorTest,
|
|
472
|
+
]);
|
|
473
|
+
mockDevices([buildV3Device()]);
|
|
474
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors([monitor]);
|
|
475
|
+
|
|
476
|
+
expect(hydratedStep(monitorTest)).toEqual(hydratedStep(monitor));
|
|
477
|
+
expect(hydratedStep(monitorTest)?.snmpV3Auth?.username).toBe(
|
|
478
|
+
"monitoring",
|
|
479
|
+
);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
test("many monitors sharing one device are all hydrated from a single lookup", async () => {
|
|
483
|
+
const findBy: jest.SpyInstance = jest
|
|
484
|
+
.spyOn(NetworkDeviceService, "findBy")
|
|
485
|
+
.mockResolvedValue([buildV3Device()]);
|
|
486
|
+
|
|
487
|
+
const monitors: Array<HydratableMonitor> = [
|
|
488
|
+
buildMonitor(),
|
|
489
|
+
buildMonitor(),
|
|
490
|
+
buildMonitor(),
|
|
491
|
+
];
|
|
492
|
+
|
|
493
|
+
await NetworkDeviceHydrationUtil.hydrateNetworkDeviceMonitors(monitors);
|
|
494
|
+
|
|
495
|
+
expect(findBy).toHaveBeenCalledTimes(1);
|
|
496
|
+
|
|
497
|
+
for (const monitor of monitors) {
|
|
498
|
+
expect(hydratedStep(monitor)?.snmpV3Auth?.username).toBe("monitoring");
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
});
|
|
502
|
+
});
|
|
@@ -69,12 +69,13 @@ describe("AIAgentTaskStatusHelper", () => {
|
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
describe("isTerminalStatus", () => {
|
|
72
|
-
test.each([
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
72
|
+
test.each([
|
|
73
|
+
AIAgentTaskStatus.Completed,
|
|
74
|
+
AIAgentTaskStatus.NoFixFound,
|
|
75
|
+
AIAgentTaskStatus.Error,
|
|
76
|
+
])("returns true for terminal status %s", (status: AIAgentTaskStatus) => {
|
|
77
|
+
expect(AIAgentTaskStatusHelper.isTerminalStatus(status)).toBe(true);
|
|
78
|
+
});
|
|
78
79
|
|
|
79
80
|
test.each([AIAgentTaskStatus.Scheduled, AIAgentTaskStatus.InProgress])(
|
|
80
81
|
"returns false for non-terminal status %s",
|
|
Binary file
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import AIRunStatus, { AIRunStatusHelper } from "../../../Types/AI/AIRunStatus";
|
|
2
|
+
|
|
3
|
+
describe("AIRunStatusHelper", () => {
|
|
4
|
+
describe("isTerminalStatus", () => {
|
|
5
|
+
test.each([
|
|
6
|
+
AIRunStatus.Completed,
|
|
7
|
+
AIRunStatus.NoFixFound,
|
|
8
|
+
AIRunStatus.Error,
|
|
9
|
+
AIRunStatus.Cancelled,
|
|
10
|
+
AIRunStatus.Stale,
|
|
11
|
+
])("returns true for terminal status %s", (status: AIRunStatus) => {
|
|
12
|
+
expect(AIRunStatusHelper.isTerminalStatus(status)).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test.each([
|
|
16
|
+
AIRunStatus.Queued,
|
|
17
|
+
AIRunStatus.Running,
|
|
18
|
+
AIRunStatus.WaitingForApproval,
|
|
19
|
+
])("returns false for non-terminal status %s", (status: AIRunStatus) => {
|
|
20
|
+
expect(AIRunStatusHelper.isTerminalStatus(status)).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
/*
|
|
24
|
+
* A run that finished with no fix is DONE. Were it treated as non-terminal,
|
|
25
|
+
* the exception page would report a task still in flight forever: no retry
|
|
26
|
+
* offered, and the duplicate guard would refuse to start a new run.
|
|
27
|
+
*/
|
|
28
|
+
test("NoFixFound is terminal — it is a result, not an in-flight run", () => {
|
|
29
|
+
expect(AIRunStatusHelper.isTerminalStatus(AIRunStatus.NoFixFound)).toBe(
|
|
30
|
+
true,
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("every status in the enum is classified", () => {
|
|
35
|
+
for (const status of Object.values(AIRunStatus)) {
|
|
36
|
+
expect(typeof AIRunStatusHelper.isTerminalStatus(status)).toBe(
|
|
37
|
+
"boolean",
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import LogSeverity, {
|
|
2
|
+
LogSeverityNumber,
|
|
3
|
+
normalizeLogSeverity,
|
|
4
|
+
} from "../../../Types/Log/LogSeverity";
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* severityText on a log row is not what the client sent — ingest throws that
|
|
8
|
+
* away and re-derives it from severityNumber into one of these seven enum
|
|
9
|
+
* members. Filter comparisons are case-sensitive, so anything that writes or
|
|
10
|
+
* offers a severity-shaped string ("INFO", "warn") produces a value that
|
|
11
|
+
* matches nothing and reports no error. These tests pin the two things that
|
|
12
|
+
* prevent that: the enum is the only vocabulary, and the number always agrees
|
|
13
|
+
* with the text.
|
|
14
|
+
*/
|
|
15
|
+
describe("LogSeverity", () => {
|
|
16
|
+
describe("LogSeverityNumber agrees with the OTLP ranges ingest buckets on", () => {
|
|
17
|
+
test.each([
|
|
18
|
+
[LogSeverity.Unspecified, 0],
|
|
19
|
+
[LogSeverity.Trace, 1],
|
|
20
|
+
[LogSeverity.Debug, 5],
|
|
21
|
+
[LogSeverity.Information, 9],
|
|
22
|
+
[LogSeverity.Warning, 13],
|
|
23
|
+
[LogSeverity.Error, 17],
|
|
24
|
+
[LogSeverity.Fatal, 21],
|
|
25
|
+
])("%s -> %i", (severity: LogSeverity, expected: number) => {
|
|
26
|
+
expect(LogSeverityNumber[severity]).toBe(expected);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("every enum member has a number", () => {
|
|
30
|
+
for (const severity of Object.values(LogSeverity)) {
|
|
31
|
+
expect(LogSeverityNumber[severity]).toBeDefined();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe("normalizeLogSeverity rescues the values the old dropdowns emitted", () => {
|
|
37
|
+
/*
|
|
38
|
+
* These six are exactly what the filter builder and the severity remapper
|
|
39
|
+
* used to write. INFO and WARNING are the dangerous ones: they differ from
|
|
40
|
+
* the stored value by more than case, so no amount of case-insensitive
|
|
41
|
+
* comparison would have saved them.
|
|
42
|
+
*/
|
|
43
|
+
test.each([
|
|
44
|
+
["TRACE", LogSeverity.Trace],
|
|
45
|
+
["DEBUG", LogSeverity.Debug],
|
|
46
|
+
["INFO", LogSeverity.Information],
|
|
47
|
+
["WARNING", LogSeverity.Warning],
|
|
48
|
+
["ERROR", LogSeverity.Error],
|
|
49
|
+
["FATAL", LogSeverity.Fatal],
|
|
50
|
+
])("legacy %s -> %s", (legacy: string, expected: LogSeverity) => {
|
|
51
|
+
expect(normalizeLogSeverity(legacy)).toBe(expected);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test.each([
|
|
55
|
+
["info", LogSeverity.Information],
|
|
56
|
+
["Information", LogSeverity.Information],
|
|
57
|
+
["warn", LogSeverity.Warning],
|
|
58
|
+
[" Error ", LogSeverity.Error],
|
|
59
|
+
["err", LogSeverity.Error],
|
|
60
|
+
["Unspecified", LogSeverity.Unspecified],
|
|
61
|
+
])("%s -> %s", (input: string, expected: LogSeverity) => {
|
|
62
|
+
expect(normalizeLogSeverity(input)).toBe(expected);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("every enum member normalizes to itself", () => {
|
|
66
|
+
for (const severity of Object.values(LogSeverity)) {
|
|
67
|
+
expect(normalizeLogSeverity(severity)).toBe(severity);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test.each([[""], [" "], ["nonsense"], ["INFORMATIONAL"], ["3"]])(
|
|
72
|
+
"%s -> null rather than a guess",
|
|
73
|
+
(input: string) => {
|
|
74
|
+
expect(normalizeLogSeverity(input)).toBeNull();
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
});
|