@oneuptime/common 12.0.2 → 12.0.3
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 +14 -0
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/LogDropFilter.ts +9 -20
- package/Models/DatabaseModels/StatusPageMonitorRule.ts +911 -0
- package/Models/DatabaseModels/StatusPageResource.ts +73 -0
- package/Models/DatabaseModels/TraceDropFilter.ts +9 -20
- package/Server/API/BaseAPI.ts +13 -2
- package/Server/API/DashboardAPI.ts +39 -0
- package/Server/EnvironmentConfig.ts +31 -1
- package/Server/Infrastructure/GlobalCache.ts +115 -0
- package/Server/Infrastructure/Postgres/DataSourceOptions.ts +16 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.ts +123 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/AlertStateTimelineService.ts +20 -0
- package/Server/Services/CephClusterService.ts +26 -86
- package/Server/Services/CloudResourceService.ts +23 -83
- package/Server/Services/DatabaseService.ts +111 -0
- package/Server/Services/DockerHostService.ts +19 -79
- package/Server/Services/DockerSwarmClusterService.ts +27 -88
- package/Server/Services/HostService.ts +46 -99
- package/Server/Services/IncidentStateTimelineService.ts +20 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/IoTFleetService.ts +19 -79
- package/Server/Services/KubernetesClusterService.ts +17 -78
- package/Server/Services/LabelService.ts +57 -10
- package/Server/Services/MetricTypeService.ts +84 -0
- package/Server/Services/MonitorService.ts +89 -14
- package/Server/Services/MonitorStatusTimelineService.ts +29 -0
- package/Server/Services/MonitorTemplateService.ts +65 -0
- package/Server/Services/PodmanHostService.ts +19 -79
- package/Server/Services/ProxmoxClusterService.ts +23 -83
- package/Server/Services/RumApplicationService.ts +19 -80
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +21 -0
- package/Server/Services/ServerlessFunctionService.ts +24 -85
- package/Server/Services/ServiceService.ts +299 -73
- package/Server/Services/StatusPageMonitorRuleEngineService.ts +752 -0
- package/Server/Services/StatusPageMonitorRuleService.ts +471 -0
- package/Server/Services/TelemetryEntityRelationshipService.ts +1 -0
- package/Server/Services/TelemetryEntityService.ts +1 -0
- package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +170 -39
- package/Server/Utils/Monitor/MonitorResource.ts +63 -36
- package/Server/Utils/Monitor/MonitorStatusTimeline.ts +36 -0
- package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +148 -127
- package/Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts +434 -0
- package/Server/Utils/SingleFlight.ts +83 -0
- package/Server/Utils/StatusPage/MonitorRulePatternValidator.ts +46 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +107 -26
- package/Server/Utils/Telemetry/ResourceHeartbeat.ts +314 -0
- package/Server/Utils/Telemetry/Telemetry.ts +351 -122
- package/Tests/App/Dashboard/WidgetCatalog.test.ts +2 -0
- package/Tests/Models/DatabaseModels/ColumnTransformerInvariants.test.ts +306 -0
- package/Tests/Models/DatabaseModels/NumberColumnDeserialization.test.ts +333 -0
- package/Tests/Models/DatabaseModels/StatusPageMonitorRuleModel.test.ts +190 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +26 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +226 -0
- package/Tests/Server/Infrastructure/PostgresLockTimeoutOptions.test.ts +181 -0
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlocked.test.ts +342 -0
- package/Tests/Server/Services/DropFilterSaveValidation.test.ts +245 -0
- package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +163 -15
- package/Tests/Server/Services/MetricTypeAttachServices.test.ts +222 -0
- package/Tests/Server/Services/MissingReferenceWriteGuard.test.ts +614 -0
- package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +23 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +305 -56
- package/Tests/Server/Services/ServiceUpdateLastSeenSplitGates.test.ts +705 -0
- package/Tests/Server/Services/StatusPageMonitorRuleEngineService.test.ts +1719 -0
- package/Tests/Server/Services/StatusPageMonitorRuleHooks.test.ts +1212 -0
- package/Tests/Server/Services/StatusPageMonitorRuleMonitorCreateHook.test.ts +228 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +267 -0
- package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +260 -13
- package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +302 -0
- package/Tests/Server/Utils/MetricTypeIndexConvergence.test.ts +598 -0
- package/Tests/Server/Utils/Monitor/MonitorStatusTimelineUnusableStatus.test.ts +154 -0
- package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +638 -92
- package/Tests/Server/Utils/Monitor/MonitorStepsReferenceExtractor.test.ts +724 -0
- package/Tests/Server/Utils/SingleFlight.test.ts +221 -0
- package/Tests/Types/Dashboard/NetworkDashboardTemplate.test.ts +793 -0
- package/Tests/Types/Database/BigIntColumnTransformer.test.ts +147 -0
- package/Tests/Types/Database/DatabasePropertyTransformer.test.ts +140 -0
- package/Tests/Types/Database/NumericColumnValue.test.ts +127 -0
- package/Tests/Types/Decimal.test.ts +37 -0
- package/Tests/Types/Monitor/SnmpVersionParsing.test.ts +152 -0
- package/Tests/Types/Port.test.ts +38 -0
- package/Tests/UI/Components/LogsHistogram.test.tsx +276 -0
- package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +489 -0
- package/Tests/UI/Components/UseLogsHistogram.test.tsx +500 -0
- package/Tests/Utils/Dashboard/Components/DashboardComponentsUtil.test.ts +120 -0
- package/Tests/Utils/Dashboard/DashboardNetworkMapComponent.test.ts +327 -0
- package/Types/Dashboard/DashboardComponentType.ts +1 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.ts +40 -0
- package/Types/Dashboard/DashboardTemplates.ts +397 -0
- package/Types/Database/BigIntColumnTransformer.ts +74 -0
- package/Types/Database/DatabaseProperty.ts +26 -0
- package/Types/Database/NumericColumnValue.ts +128 -0
- package/Types/Decimal.ts +16 -4
- package/Types/Permission.ts +48 -0
- package/Types/Port.ts +16 -4
- package/UI/Components/LogsViewer/useLiveLogsRefresh.ts +86 -0
- package/UI/Components/LogsViewer/useLogsHistogram.ts +113 -0
- package/Utils/Dashboard/Components/DashboardNetworkMapComponent.ts +136 -0
- package/Utils/Dashboard/Components/Index.ts +7 -0
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +12 -0
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LogDropFilter.js +9 -20
- package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js +925 -0
- package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/StatusPageResource.js +72 -0
- package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TraceDropFilter.js +9 -20
- package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +11 -1
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +39 -0
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +27 -1
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/GlobalCache.js +91 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +5 -3
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js +48 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AlertStateTimelineService.js +19 -0
- package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +26 -76
- package/build/dist/Server/Services/CephClusterService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceService.js +23 -73
- package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +86 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +19 -69
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js +27 -78
- package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +58 -102
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/IoTFleetService.js +19 -69
- package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +17 -68
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/LabelService.js +42 -7
- package/build/dist/Server/Services/LabelService.js.map +1 -1
- package/build/dist/Server/Services/MetricTypeService.js +74 -0
- package/build/dist/Server/Services/MetricTypeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +81 -9
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusTimelineService.js +28 -0
- package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +60 -0
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostService.js +19 -69
- package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js +23 -73
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationService.js +19 -70
- package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +20 -0
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionService.js +24 -75
- package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
- package/build/dist/Server/Services/ServiceService.js +236 -61
- package/build/dist/Server/Services/ServiceService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +603 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleService.js +401 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleService.js.map +1 -0
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +1 -0
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryEntityService.js +1 -0
- package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +105 -29
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +56 -32
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +30 -3
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +110 -98
- package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js +312 -0
- package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js.map +1 -0
- package/build/dist/Server/Utils/SingleFlight.js +76 -0
- package/build/dist/Server/Utils/SingleFlight.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js +29 -0
- package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +78 -19
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +160 -0
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +279 -104
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
- 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/DashboardComponents/DashboardNetworkMapComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js +368 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Database/BigIntColumnTransformer.js +66 -0
- package/build/dist/Types/Database/BigIntColumnTransformer.js.map +1 -0
- package/build/dist/Types/Database/DatabaseProperty.js +25 -0
- package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
- package/build/dist/Types/Database/NumericColumnValue.js +97 -0
- package/build/dist/Types/Database/NumericColumnValue.js.map +1 -0
- package/build/dist/Types/Decimal.js +14 -3
- package/build/dist/Types/Decimal.js.map +1 -1
- package/build/dist/Types/Permission.js +42 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Port.js +15 -5
- package/build/dist/Types/Port.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js +44 -0
- package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +75 -0
- package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js +114 -0
- package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
- package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { getBigIntDatabaseTransformer } from "../../../Types/Database/BigIntColumnTransformer";
|
|
2
|
+
import { ValueTransformer } from "typeorm/decorator/options/ValueTransformer";
|
|
3
|
+
import { describe, expect, it } from "@jest/globals";
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* Contract under test — the Postgres bigint round-trip used by counter
|
|
7
|
+
* columns.
|
|
8
|
+
*
|
|
9
|
+
* The interesting half is `to(undefined)`. TypeORM applies a column
|
|
10
|
+
* transformer BEFORE it decides whether the column was supplied at all:
|
|
11
|
+
*
|
|
12
|
+
* InsertQueryBuilder.createColumnValueExpression
|
|
13
|
+
* -> driver.preparePersistentValue (runs transformer.to)
|
|
14
|
+
* -> if (value === undefined) expression += "DEFAULT"
|
|
15
|
+
*
|
|
16
|
+
* so a transformer that answers `null` for `undefined` has already turned
|
|
17
|
+
* "the caller did not set this column" into "the caller set it to NULL" by
|
|
18
|
+
* the time that check runs, and the column DEFAULT is unreachable. On the
|
|
19
|
+
* drop-filter `droppedCount` columns — NOT NULL DEFAULT 0, written only by
|
|
20
|
+
* the ingest path — that made every single insert fail:
|
|
21
|
+
*
|
|
22
|
+
* null value in column "droppedCount" of relation "LogDropFilter"
|
|
23
|
+
* violates not-null constraint
|
|
24
|
+
*
|
|
25
|
+
* i.e. creating any log drop filter in the dashboard returned HTTP 500.
|
|
26
|
+
* github.com/OneUptime/oneuptime/issues/3026
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const transformer: ValueTransformer = getBigIntDatabaseTransformer();
|
|
30
|
+
|
|
31
|
+
describe("getBigIntDatabaseTransformer().to", () => {
|
|
32
|
+
/*
|
|
33
|
+
* The regression. `undefined` and `null` are NOT interchangeable here:
|
|
34
|
+
* one means "use the column default", the other means "write NULL".
|
|
35
|
+
*/
|
|
36
|
+
it("passes undefined through so the column DEFAULT applies", () => {
|
|
37
|
+
expect(transformer.to(undefined)).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("does not confuse undefined with null", () => {
|
|
41
|
+
expect(transformer.to(undefined)).not.toBeNull();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("keeps an explicit null as null", () => {
|
|
45
|
+
expect(transformer.to(null)).toBeNull();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("stringifies a number, because the pg driver binds bigint as text", () => {
|
|
49
|
+
expect(transformer.to(0)).toBe("0");
|
|
50
|
+
expect(transformer.to(1)).toBe("1");
|
|
51
|
+
expect(transformer.to(42)).toBe("42");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("truncates rather than emitting a decimal Postgres would reject", () => {
|
|
55
|
+
expect(transformer.to(10.9)).toBe("10");
|
|
56
|
+
expect(transformer.to(-10.9)).toBe("-10");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
/*
|
|
60
|
+
* The reason these columns are bigint at all: a sample filter on a
|
|
61
|
+
* high-volume project passes 2^31 well within a year, and a silently
|
|
62
|
+
* wrapped diagnostic counter is worse than no counter.
|
|
63
|
+
*/
|
|
64
|
+
it("carries a value past the 32-bit range without losing precision", () => {
|
|
65
|
+
expect(transformer.to(4_294_967_296)).toBe("4294967296");
|
|
66
|
+
expect(transformer.to(Number.MAX_SAFE_INTEGER)).toBe("9007199254740991");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/*
|
|
70
|
+
* `Math.trunc(NaN).toString()` is the string "NaN", which Postgres
|
|
71
|
+
* rejects with a syntax error naming neither the column nor the caller.
|
|
72
|
+
*/
|
|
73
|
+
it("refuses to emit a non-finite value as text", () => {
|
|
74
|
+
expect(transformer.to(NaN)).toBeNull();
|
|
75
|
+
expect(transformer.to(Infinity)).toBeNull();
|
|
76
|
+
expect(transformer.to(-Infinity)).toBeNull();
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("getBigIntDatabaseTransformer().from", () => {
|
|
81
|
+
/*
|
|
82
|
+
* node-postgres hands bigint back as a string — it does not fit a JS
|
|
83
|
+
* number in the general case. Every value stored in these counters is
|
|
84
|
+
* inside MAX_SAFE_INTEGER, and the dashboard renders them as numbers.
|
|
85
|
+
*/
|
|
86
|
+
it("parses the string the driver returns into a number", () => {
|
|
87
|
+
expect(transformer.from("0")).toBe(0);
|
|
88
|
+
expect(transformer.from("42")).toBe(42);
|
|
89
|
+
expect(transformer.from("9007199254740991")).toBe(9007199254740991);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("accepts a number unchanged, in case a driver already parsed it", () => {
|
|
93
|
+
expect(transformer.from(7)).toBe(7);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("maps a null column to null rather than 0", () => {
|
|
97
|
+
expect(transformer.from(null)).toBeNull();
|
|
98
|
+
expect(transformer.from(undefined)).toBeNull();
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("maps unparsable text to null rather than NaN", () => {
|
|
102
|
+
expect(transformer.from("not a number")).toBeNull();
|
|
103
|
+
expect(transformer.from("")).toBeNull();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("round-trips a counter value", () => {
|
|
107
|
+
for (const value of [0, 1, 999, 4_294_967_296]) {
|
|
108
|
+
expect(transformer.from(transformer.to(value))).toBe(value);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
/*
|
|
114
|
+
* A miniature of TypeORM's own decision, so the consequence of the
|
|
115
|
+
* transformer's answer is asserted rather than left to a comment. Mirrors
|
|
116
|
+
* PostgresDriver.preparePersistentValue followed by
|
|
117
|
+
* InsertQueryBuilder.createColumnValueExpression.
|
|
118
|
+
*/
|
|
119
|
+
describe("the INSERT expression TypeORM derives from this transformer", () => {
|
|
120
|
+
type InsertExpression = "DEFAULT" | "NULL" | string;
|
|
121
|
+
|
|
122
|
+
function insertExpressionFor(value: number | null | undefined): string {
|
|
123
|
+
const prepared: unknown = transformer.to(value);
|
|
124
|
+
|
|
125
|
+
if (prepared === undefined) {
|
|
126
|
+
return "DEFAULT";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (prepared === null) {
|
|
130
|
+
return "NULL";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return String(prepared);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
it("emits DEFAULT for a counter the caller never set", () => {
|
|
137
|
+
const expression: InsertExpression = insertExpressionFor(undefined);
|
|
138
|
+
|
|
139
|
+
expect(expression).toBe("DEFAULT");
|
|
140
|
+
// The old inline transformer produced this, and NOT NULL rejected it.
|
|
141
|
+
expect(expression).not.toBe("NULL");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("emits the value for a counter the caller did set", () => {
|
|
145
|
+
expect(insertExpressionFor(5)).toBe("5");
|
|
146
|
+
});
|
|
147
|
+
});
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import Recurring from "../../../Types/Events/Recurring";
|
|
2
|
+
import RestrictionTimes from "../../../Types/OnCallDutyPolicy/RestrictionTimes";
|
|
3
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
4
|
+
import Email from "../../../Types/Email";
|
|
5
|
+
import Color from "../../../Types/Color";
|
|
6
|
+
import EventInterval from "../../../Types/Events/EventInterval";
|
|
7
|
+
import PositiveNumber from "../../../Types/PositiveNumber";
|
|
8
|
+
import { ValueTransformer } from "typeorm/decorator/options/ValueTransformer";
|
|
9
|
+
import { describe, expect, it } from "@jest/globals";
|
|
10
|
+
|
|
11
|
+
/*
|
|
12
|
+
* Contract under test — `DatabaseProperty.getDatabaseTransformer()`, the one
|
|
13
|
+
* transformer behind nearly every non-primitive column in the schema.
|
|
14
|
+
*
|
|
15
|
+
* Its `to` has to keep `undefined` and `null` apart. TypeORM applies the
|
|
16
|
+
* transformer BEFORE deciding whether the caller supplied the column:
|
|
17
|
+
*
|
|
18
|
+
* InsertQueryBuilder.createColumnValueExpression
|
|
19
|
+
* -> driver.preparePersistentValue (runs transformer.to)
|
|
20
|
+
* -> if (value === undefined) expression += "DEFAULT"
|
|
21
|
+
*
|
|
22
|
+
* Every `toDatabase` implementation returns null for a falsy input, so
|
|
23
|
+
* before this guard an omitted column arrived as an explicit NULL and the
|
|
24
|
+
* column's DEFAULT was unreachable. On a NOT NULL column with a default that
|
|
25
|
+
* is a not-null constraint violation — an HTTP 500 — on every create that
|
|
26
|
+
* does not mention the column. That is the mechanism behind
|
|
27
|
+
* github.com/OneUptime/oneuptime/issues/3026 (the drop-filter counters), and
|
|
28
|
+
* `OnCallDutyPolicyScheduleLayer.rotation` / `.restrictionTimes` are the same
|
|
29
|
+
* shape: NOT NULL, with a JSON default, written through this transformer.
|
|
30
|
+
*
|
|
31
|
+
* UPDATE is unaffected in both directions — UpdateQueryBuilder drops
|
|
32
|
+
* undefined properties before the transformer runs — so this only ever
|
|
33
|
+
* changes an INSERT from "write NULL" to "use the DEFAULT".
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
interface TransformerCase {
|
|
37
|
+
name: string;
|
|
38
|
+
transformer: ValueTransformer;
|
|
39
|
+
sample: unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const CASES: Array<TransformerCase> = [
|
|
43
|
+
{
|
|
44
|
+
name: "Recurring",
|
|
45
|
+
transformer: Recurring.getDatabaseTransformer(),
|
|
46
|
+
sample: Recurring.getDefault(),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "RestrictionTimes",
|
|
50
|
+
transformer: RestrictionTimes.getDatabaseTransformer(),
|
|
51
|
+
sample: RestrictionTimes.getDefault(),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: "ObjectID",
|
|
55
|
+
transformer: ObjectID.getDatabaseTransformer(),
|
|
56
|
+
sample: new ObjectID("11111111-1111-4111-8111-111111111111"),
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "Email",
|
|
60
|
+
transformer: Email.getDatabaseTransformer(),
|
|
61
|
+
sample: new Email("someone@oneuptime.com"),
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "Color",
|
|
65
|
+
transformer: Color.getDatabaseTransformer(),
|
|
66
|
+
sample: new Color("#ff0000"),
|
|
67
|
+
},
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
describe.each(CASES)(
|
|
71
|
+
"$name.getDatabaseTransformer()",
|
|
72
|
+
({ transformer, sample }: TransformerCase) => {
|
|
73
|
+
it("passes undefined through so the column DEFAULT applies", () => {
|
|
74
|
+
expect(transformer.to(undefined)).toBeUndefined();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("does not collapse undefined into null", () => {
|
|
78
|
+
expect(transformer.to(undefined)).not.toBeNull();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
* An explicit null is a different instruction — "store nothing here" —
|
|
83
|
+
* and still has to reach the column as NULL.
|
|
84
|
+
*/
|
|
85
|
+
it("keeps an explicit null as null", () => {
|
|
86
|
+
expect(transformer.to(null)).toBeNull();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("still serializes a real value", () => {
|
|
90
|
+
expect(transformer.to(sample)).not.toBeUndefined();
|
|
91
|
+
expect(transformer.to(sample)).not.toBeNull();
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
describe("the INSERT expression TypeORM derives for a NOT NULL column", () => {
|
|
97
|
+
/*
|
|
98
|
+
* A miniature of PostgresDriver.preparePersistentValue followed by
|
|
99
|
+
* InsertQueryBuilder.createColumnValueExpression, so the consequence is
|
|
100
|
+
* asserted rather than described.
|
|
101
|
+
*/
|
|
102
|
+
function insertExpressionFor(
|
|
103
|
+
transformer: ValueTransformer,
|
|
104
|
+
value: unknown,
|
|
105
|
+
): string {
|
|
106
|
+
const prepared: unknown = transformer.to(value);
|
|
107
|
+
|
|
108
|
+
if (prepared === undefined) {
|
|
109
|
+
return "DEFAULT";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (prepared === null) {
|
|
113
|
+
return "NULL";
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return "?";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
it("uses the schedule layer's rotation default instead of NULL", () => {
|
|
120
|
+
expect(
|
|
121
|
+
insertExpressionFor(Recurring.getDatabaseTransformer(), undefined),
|
|
122
|
+
).toBe("DEFAULT");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("uses the schedule layer's restriction default instead of NULL", () => {
|
|
126
|
+
expect(
|
|
127
|
+
insertExpressionFor(RestrictionTimes.getDatabaseTransformer(), undefined),
|
|
128
|
+
).toBe("DEFAULT");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("still binds a supplied value", () => {
|
|
132
|
+
const rotation: Recurring = new Recurring();
|
|
133
|
+
rotation.intervalType = EventInterval.Week;
|
|
134
|
+
rotation.intervalCount = new PositiveNumber(2);
|
|
135
|
+
|
|
136
|
+
expect(
|
|
137
|
+
insertExpressionFor(Recurring.getDatabaseTransformer(), rotation),
|
|
138
|
+
).toBe("?");
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import {
|
|
2
|
+
coerceNumericColumnValue,
|
|
3
|
+
isNumericTableColumnType,
|
|
4
|
+
} from "../../../Types/Database/NumericColumnValue";
|
|
5
|
+
import TableColumnType from "../../../Types/Database/TableColumnType";
|
|
6
|
+
import { describe, expect, it } from "@jest/globals";
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* Contract under test — normalizing a JSON value bound for a number column.
|
|
10
|
+
*
|
|
11
|
+
* HTML has no numeric input event: `<input type="number">` hands back
|
|
12
|
+
* `e.target.value`, a string. So the dashboard posted the JSON string "10"
|
|
13
|
+
* for every number field, and Postgres quietly coerced '10' to 10 on the way
|
|
14
|
+
* in — which is why nobody noticed until a server-side check read the value
|
|
15
|
+
* BEFORE it was stored:
|
|
16
|
+
*
|
|
17
|
+
* BadDataException: Sample percentage is required when the action is
|
|
18
|
+
* "Sample". Enter the percentage of matching logs to keep, between 1 and 99.
|
|
19
|
+
*
|
|
20
|
+
* on a form where the user had typed a percentage. HTTP 400 on a valid
|
|
21
|
+
* submission. github.com/OneUptime/oneuptime/issues/3027
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
describe("isNumericTableColumnType", () => {
|
|
25
|
+
it("covers every column type stored as a plain JS number", () => {
|
|
26
|
+
for (const type of [
|
|
27
|
+
TableColumnType.Number,
|
|
28
|
+
TableColumnType.SmallNumber,
|
|
29
|
+
TableColumnType.BigNumber,
|
|
30
|
+
TableColumnType.PositiveNumber,
|
|
31
|
+
TableColumnType.SmallPositiveNumber,
|
|
32
|
+
TableColumnType.BigPositiveNumber,
|
|
33
|
+
]) {
|
|
34
|
+
expect(isNumericTableColumnType(type)).toBe(true);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/*
|
|
39
|
+
* The types that would be destroyed by coercion. `Port` and `Version` are
|
|
40
|
+
* wrapped objects and `Date` is a Date, however numeric they look.
|
|
41
|
+
*/
|
|
42
|
+
it("excludes types that only look numeric", () => {
|
|
43
|
+
for (const type of [
|
|
44
|
+
TableColumnType.Port,
|
|
45
|
+
TableColumnType.Version,
|
|
46
|
+
TableColumnType.Date,
|
|
47
|
+
TableColumnType.ShortText,
|
|
48
|
+
TableColumnType.LongText,
|
|
49
|
+
TableColumnType.Boolean,
|
|
50
|
+
TableColumnType.ObjectID,
|
|
51
|
+
TableColumnType.Entity,
|
|
52
|
+
TableColumnType.EntityArray,
|
|
53
|
+
TableColumnType.JSON,
|
|
54
|
+
]) {
|
|
55
|
+
expect(isNumericTableColumnType(type)).toBe(false);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("treats a missing type as not numeric", () => {
|
|
60
|
+
expect(isNumericTableColumnType(undefined)).toBe(false);
|
|
61
|
+
expect(isNumericTableColumnType(null)).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("coerceNumericColumnValue", () => {
|
|
66
|
+
it("converts the string an <input type=number> produces", () => {
|
|
67
|
+
expect(coerceNumericColumnValue("10")).toBe(10);
|
|
68
|
+
expect(coerceNumericColumnValue("1")).toBe(1);
|
|
69
|
+
expect(coerceNumericColumnValue("99")).toBe(99);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("handles the shapes a browser can put in that string", () => {
|
|
73
|
+
expect(coerceNumericColumnValue("0")).toBe(0);
|
|
74
|
+
expect(coerceNumericColumnValue("-5")).toBe(-5);
|
|
75
|
+
expect(coerceNumericColumnValue("2.5")).toBe(2.5);
|
|
76
|
+
expect(coerceNumericColumnValue(" 42 ")).toBe(42);
|
|
77
|
+
expect(coerceNumericColumnValue("1e3")).toBe(1000);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("leaves a value that is already a number alone", () => {
|
|
81
|
+
expect(coerceNumericColumnValue(10)).toBe(10);
|
|
82
|
+
expect(coerceNumericColumnValue(0)).toBe(0);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("leaves null and undefined alone", () => {
|
|
86
|
+
expect(coerceNumericColumnValue(null)).toBeNull();
|
|
87
|
+
expect(coerceNumericColumnValue(undefined)).toBeUndefined();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
/*
|
|
91
|
+
* `Number("")` is 0. Writing a real 0 for a field the user cleared would
|
|
92
|
+
* be a silent data change, and for a sample percentage specifically, 0 is
|
|
93
|
+
* the value that used to mean "throw away half". Leave it untouched and
|
|
94
|
+
* let the field's own validation speak.
|
|
95
|
+
*/
|
|
96
|
+
it("does not turn a cleared field into zero", () => {
|
|
97
|
+
expect(coerceNumericColumnValue("")).toBe("");
|
|
98
|
+
expect(coerceNumericColumnValue(" ")).toBe(" ");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("leaves unparsable text alone so validation can reject it by name", () => {
|
|
102
|
+
for (const garbage of ["abc", "10abc", "NaN", "Infinity", "--1", "1,000"]) {
|
|
103
|
+
expect(coerceNumericColumnValue(garbage)).toBe(garbage);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("never invents a non-finite number", () => {
|
|
108
|
+
const results: Array<unknown> = ["Infinity", "-Infinity", "NaN"].map(
|
|
109
|
+
(value: string) => {
|
|
110
|
+
return coerceNumericColumnValue(value);
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
for (const result of results) {
|
|
115
|
+
expect(typeof result).toBe("string");
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("passes non-string, non-number values through untouched", () => {
|
|
120
|
+
const object: Record<string, string> = { a: "1" };
|
|
121
|
+
const array: Array<number> = [1];
|
|
122
|
+
|
|
123
|
+
expect(coerceNumericColumnValue(object)).toBe(object);
|
|
124
|
+
expect(coerceNumericColumnValue(array)).toBe(array);
|
|
125
|
+
expect(coerceNumericColumnValue(true)).toBe(true);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -89,4 +89,41 @@ describe("Decimal", () => {
|
|
|
89
89
|
expect(restored.equals(original)).toBe(true);
|
|
90
90
|
});
|
|
91
91
|
});
|
|
92
|
+
|
|
93
|
+
/*
|
|
94
|
+
* A decimal column is declared `TableColumnType.Number`, so whatever
|
|
95
|
+
* reaches the transformer may be a Decimal, a string, or a plain number
|
|
96
|
+
* depending on how far the request body got through deserialization.
|
|
97
|
+
*/
|
|
98
|
+
describe("getDatabaseTransformer().to", () => {
|
|
99
|
+
const transformer: ReturnType<typeof Decimal.getDatabaseTransformer> =
|
|
100
|
+
Decimal.getDatabaseTransformer();
|
|
101
|
+
|
|
102
|
+
test("stores a Decimal instance as its string form", () => {
|
|
103
|
+
expect(transformer.to(new Decimal(125.5))).toBe("125.5");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("stores a raw string", () => {
|
|
107
|
+
expect(transformer.to("125.5")).toBe("125.5");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
test("stores a raw number", () => {
|
|
111
|
+
expect(transformer.to(125.5)).toBe("125.5");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
/*
|
|
115
|
+
* `0` is a perfectly good decimal, and both of its columns are NOT NULL.
|
|
116
|
+
* A truthiness check used to send it to null, which the column rejects.
|
|
117
|
+
*/
|
|
118
|
+
test("stores zero rather than turning it into null", () => {
|
|
119
|
+
expect(transformer.to(0)).toBe("0");
|
|
120
|
+
expect(transformer.to("0")).toBe("0");
|
|
121
|
+
expect(transformer.to(new Decimal(0))).toBe("0");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("keeps undefined distinct from null so a column DEFAULT applies", () => {
|
|
125
|
+
expect(transformer.to(undefined)).toBeUndefined();
|
|
126
|
+
expect(transformer.to(null)).toBeNull();
|
|
127
|
+
});
|
|
128
|
+
});
|
|
92
129
|
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import SnmpVersion, {
|
|
3
|
+
SnmpVersionUtil,
|
|
4
|
+
} from "../../../Types/Monitor/SnmpMonitor/SnmpVersion";
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* snmpVersion is a free-text column with two live spellings: the enum VALUES
|
|
8
|
+
* ("1"/"2c"/"3") that the probe contract uses, and the enum KEYS ("V1"/"V2c"/
|
|
9
|
+
* "V3") that every form writes and the column default holds. SnmpVersionUtil is
|
|
10
|
+
* the seam that reconciles them. Reading the column any other way silently takes
|
|
11
|
+
* the wrong branch — a stored "V3" is not === SnmpVersion.V3 ("3"), so a v3
|
|
12
|
+
* device would read as v2c and be polled in cleartext.
|
|
13
|
+
*
|
|
14
|
+
* Unlike the SnmpV3 protocol parsers, parse() cannot return undefined: the probe
|
|
15
|
+
* must pick exactly one session type, so an unreadable value falls back to v2c
|
|
16
|
+
* rather than refusing. These tests pin both the recognized spellings and that
|
|
17
|
+
* deliberate default so a future edit cannot widen or narrow it unnoticed.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
describe("SnmpVersionUtil.parse", () => {
|
|
21
|
+
test.each([
|
|
22
|
+
["1", SnmpVersion.V1],
|
|
23
|
+
["2c", SnmpVersion.V2c],
|
|
24
|
+
["3", SnmpVersion.V3],
|
|
25
|
+
])(
|
|
26
|
+
"reads the probe-contract value spelling %j",
|
|
27
|
+
(stored: string, expected: SnmpVersion) => {
|
|
28
|
+
expect(SnmpVersionUtil.parse(stored)).toBe(expected);
|
|
29
|
+
},
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
/*
|
|
33
|
+
* The stored spelling — what forms write and the column default holds. This
|
|
34
|
+
* is the spelling the bare-cast bug got wrong, so it is the one that most
|
|
35
|
+
* needs a test.
|
|
36
|
+
*/
|
|
37
|
+
test.each([
|
|
38
|
+
["V1", SnmpVersion.V1],
|
|
39
|
+
["V2c", SnmpVersion.V2c],
|
|
40
|
+
["V3", SnmpVersion.V3],
|
|
41
|
+
])(
|
|
42
|
+
"reads the stored enum-key spelling %j",
|
|
43
|
+
(stored: string, expected: SnmpVersion) => {
|
|
44
|
+
expect(SnmpVersionUtil.parse(stored)).toBe(expected);
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
test.each([
|
|
49
|
+
["V1", SnmpVersion.V1],
|
|
50
|
+
["v1", SnmpVersion.V1],
|
|
51
|
+
[" 1 ", SnmpVersion.V1],
|
|
52
|
+
["\tv3\n", SnmpVersion.V3],
|
|
53
|
+
["V3", SnmpVersion.V3],
|
|
54
|
+
["v3", SnmpVersion.V3],
|
|
55
|
+
])(
|
|
56
|
+
"tolerates case and surrounding whitespace in %j",
|
|
57
|
+
(stored: string, expected: SnmpVersion) => {
|
|
58
|
+
expect(SnmpVersionUtil.parse(stored)).toBe(expected);
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
/*
|
|
63
|
+
* v2c is the fallback branch, so every v2c spelling AND every value the parser
|
|
64
|
+
* does not recognize must land here. The two are indistinguishable by design:
|
|
65
|
+
* there is no "undefined version", the probe always needs a session type, and
|
|
66
|
+
* v2c is the safe historic default. Anything that stops mapping to v2c here is
|
|
67
|
+
* a behavior change, not a bug fix.
|
|
68
|
+
*/
|
|
69
|
+
test.each([
|
|
70
|
+
["2c", "an exact value"],
|
|
71
|
+
["V2c", "the stored key"],
|
|
72
|
+
["v2c", "lowercased key"],
|
|
73
|
+
["2", "the version number without the c"],
|
|
74
|
+
["V2", "the key without the c"],
|
|
75
|
+
[undefined, "an undefined column"],
|
|
76
|
+
[null, "a null column"],
|
|
77
|
+
["", "an empty string"],
|
|
78
|
+
[" ", "whitespace only"],
|
|
79
|
+
["nonsense", "garbage"],
|
|
80
|
+
["v4", "a version that does not exist"],
|
|
81
|
+
["0", "a zero"],
|
|
82
|
+
])(
|
|
83
|
+
"falls back to v2c for %j (%s)",
|
|
84
|
+
(stored: string | undefined | null, _description: string) => {
|
|
85
|
+
expect(SnmpVersionUtil.parse(stored)).toBe(SnmpVersion.V2c);
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe("SnmpVersionUtil.isV3", () => {
|
|
91
|
+
test.each([["3"], ["v3"], ["V3"], [" V3 "], ["\tv3\n"]])(
|
|
92
|
+
"is true for the v3 spelling %j",
|
|
93
|
+
(stored: string) => {
|
|
94
|
+
expect(SnmpVersionUtil.isV3(stored)).toBe(true);
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
/*
|
|
99
|
+
* The consequential direction: everything that is not v3 must read as not-v3,
|
|
100
|
+
* because isV3 gates whether auth/priv credentials are applied. A false
|
|
101
|
+
* positive would be harmless; a false negative strands a v3 device in a v2c
|
|
102
|
+
* branch and polls it without credentials.
|
|
103
|
+
*/
|
|
104
|
+
test.each([
|
|
105
|
+
["1"],
|
|
106
|
+
["V1"],
|
|
107
|
+
["2c"],
|
|
108
|
+
["V2c"],
|
|
109
|
+
["v2c"],
|
|
110
|
+
["2"],
|
|
111
|
+
[undefined],
|
|
112
|
+
[null],
|
|
113
|
+
[""],
|
|
114
|
+
[" "],
|
|
115
|
+
["nonsense"],
|
|
116
|
+
])(
|
|
117
|
+
"is false for the non-v3 value %j",
|
|
118
|
+
(stored: string | undefined | null) => {
|
|
119
|
+
expect(SnmpVersionUtil.isV3(stored)).toBe(false);
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
test("agrees with parse() for every recognized spelling", () => {
|
|
124
|
+
for (const spelling of ["1", "v1", "V1", "2c", "V2c", "3", "v3", "V3"]) {
|
|
125
|
+
expect(SnmpVersionUtil.isV3(spelling)).toBe(
|
|
126
|
+
SnmpVersionUtil.parse(spelling) === SnmpVersion.V3,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("every enum member survives a round trip", () => {
|
|
133
|
+
/*
|
|
134
|
+
* Exhaustive over the enum rather than a fixed list, so adding a version
|
|
135
|
+
* without teaching parse() its value AND key spelling fails here instead of
|
|
136
|
+
* silently collapsing to v2c in production.
|
|
137
|
+
*/
|
|
138
|
+
test("each value spelling parses back to itself", () => {
|
|
139
|
+
for (const member of Object.values(SnmpVersion)) {
|
|
140
|
+
expect(SnmpVersionUtil.parse(member)).toBe(member);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("each key spelling parses to its member", () => {
|
|
145
|
+
for (const key of Object.keys(SnmpVersion)) {
|
|
146
|
+
// Keys are "V1"/"V2c"/"V3"; the member is looked up by that key.
|
|
147
|
+
const expected: SnmpVersion =
|
|
148
|
+
SnmpVersion[key as keyof typeof SnmpVersion];
|
|
149
|
+
expect(SnmpVersionUtil.parse(key)).toBe(expected);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
package/Tests/Types/Port.test.ts
CHANGED
|
@@ -36,4 +36,42 @@ describe("Testing class port", () => {
|
|
|
36
36
|
const value: Port = new Port("6000");
|
|
37
37
|
expect(typeof value.port.positiveNumber).toBe("number");
|
|
38
38
|
});
|
|
39
|
+
|
|
40
|
+
/*
|
|
41
|
+
* A port column is declared `TableColumnType.Number`, so whatever reaches
|
|
42
|
+
* the transformer may be a Port, a string, or a plain number depending on
|
|
43
|
+
* how far the request body got through deserialization. A raw number used
|
|
44
|
+
* to fall off the end of `toDatabase` and return null, which wrote NULL
|
|
45
|
+
* over an SMTP port the user had just typed — silently, since null is a
|
|
46
|
+
* legal value for the column.
|
|
47
|
+
*/
|
|
48
|
+
describe("getDatabaseTransformer().to", () => {
|
|
49
|
+
const transformer: ReturnType<typeof Port.getDatabaseTransformer> =
|
|
50
|
+
Port.getDatabaseTransformer();
|
|
51
|
+
|
|
52
|
+
test("stores a Port instance as its number", () => {
|
|
53
|
+
expect(transformer.to(new Port(587))).toBe(587);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("stores a raw string port as a number", () => {
|
|
57
|
+
expect(transformer.to("587")).toBe(587);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("stores a raw number port rather than dropping it", () => {
|
|
61
|
+
expect(transformer.to(587)).toBe(587);
|
|
62
|
+
expect(transformer.to(0)).toBe(0);
|
|
63
|
+
expect(transformer.to(65535)).toBe(65535);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("keeps undefined distinct from null so a column DEFAULT applies", () => {
|
|
67
|
+
expect(transformer.to(undefined)).toBeUndefined();
|
|
68
|
+
expect(transformer.to(null)).toBeNull();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("rejects a port outside the valid range instead of storing null", () => {
|
|
72
|
+
expect(() => {
|
|
73
|
+
return transformer.to(70000);
|
|
74
|
+
}).toThrow(BadDataException);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
39
77
|
});
|