@oneuptime/common 12.0.1 → 12.0.2
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/Host.ts +12 -4
- package/Models/DatabaseModels/KubernetesContainer.ts +5 -4
- package/Models/DatabaseModels/KubernetesResource.ts +8 -7
- package/Models/DatabaseModels/PodmanResource.ts +6 -6
- package/Models/DatabaseModels/ServiceLevelObjective.ts +91 -0
- package/Models/DatabaseModels/StatusPage.ts +95 -0
- package/Server/API/TeamMemberAPI.ts +85 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.ts +84 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.ts +131 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Middleware/ProjectAuthorization.ts +66 -4
- package/Server/Services/BillingService.ts +600 -51
- package/Server/Services/CephClusterService.ts +47 -5
- package/Server/Services/CephResourceService.ts +43 -4
- package/Server/Services/CloudResourceService.ts +47 -5
- package/Server/Services/DatabaseService.ts +87 -6
- package/Server/Services/DockerHostService.ts +47 -5
- package/Server/Services/DockerResourceService.ts +62 -8
- package/Server/Services/DockerSwarmClusterService.ts +48 -5
- package/Server/Services/DockerSwarmResourceService.ts +53 -4
- package/Server/Services/HostService.ts +48 -5
- package/Server/Services/IoTDeviceService.ts +1 -17
- package/Server/Services/IoTFleetService.ts +47 -5
- package/Server/Services/KubernetesClusterService.ts +48 -5
- package/Server/Services/KubernetesContainerService.ts +50 -9
- package/Server/Services/KubernetesResourceService.ts +53 -7
- package/Server/Services/LabelService.ts +106 -1
- package/Server/Services/MonitorService.ts +57 -0
- package/Server/Services/PodmanHostService.ts +47 -5
- package/Server/Services/PodmanResourceService.ts +62 -8
- package/Server/Services/ProjectService.ts +165 -35
- package/Server/Services/ProxmoxClusterService.ts +47 -5
- package/Server/Services/ProxmoxResourceService.ts +43 -4
- package/Server/Services/RumApplicationService.ts +48 -5
- package/Server/Services/ServerlessFunctionService.ts +48 -5
- package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +426 -0
- package/Server/Services/ServiceLevelObjectiveService.ts +39 -0
- package/Server/Services/StatusPageService.ts +95 -36
- package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +18 -1
- package/Server/Services/StatusPageSubscriberService.ts +3 -0
- package/Server/Types/Database/JSONColumnQuery.ts +621 -0
- package/Server/Types/Database/Permissions/PublicPermission.ts +9 -2
- package/Server/Types/Database/QueryHelper.ts +17 -32
- package/Server/Utils/Database/TruncateColumnValue.ts +84 -0
- package/Tests/App/Dashboard/UsersTableGroupsByPerson.test.tsx +448 -0
- package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +704 -0
- package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +303 -0
- package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +350 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyHeader.test.ts +362 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +687 -0
- package/Tests/Server/Services/BillingService.test.ts +78 -3
- package/Tests/Server/Services/BillingServiceChangePlanCancel.test.ts +645 -0
- package/Tests/Server/Services/BillingServiceTrialPlanChange.test.ts +1005 -0
- package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +503 -0
- package/Tests/Server/Services/HostIpAddressesColumnWidth.test.ts +198 -0
- package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +510 -0
- package/Tests/Server/Services/InventoryResourceNameColumnWidth.test.ts +500 -0
- package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +586 -0
- package/Tests/Server/Services/InventoryUpsertTruncationGuard.test.ts +1242 -0
- package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +650 -0
- package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +1 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +387 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +770 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +130 -0
- package/Tests/Server/Services/SloMonitorLabelRuleHooks.test.ts +352 -0
- package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +29 -13
- package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +69 -8
- package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +593 -0
- package/Tests/Server/Types/Database/Permissions/PublicPermission.test.ts +495 -0
- package/Tests/Server/Types/Database/QueryUtil.test.ts +113 -0
- package/Tests/Server/Utils/Database/TruncateColumnValue.test.ts +179 -0
- package/Tests/Types/Events/Recurring.test.ts +157 -0
- package/Tests/UI/Components/ModelTable/ModelTableGroupsProjectUsers.test.tsx +375 -0
- package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +196 -0
- package/Tests/UI/Utils/ProjectUsersModelAPI.test.ts +739 -0
- package/Tests/UI/Utils/TeamMembersByUser.test.ts +633 -0
- package/Tests/Utils/StatusPage/ReportPeriod.test.ts +218 -0
- package/Tests/Utils/Telemetry/HostIpAddresses.test.ts +291 -0
- package/Types/CustomField/CustomFieldDefinition.ts +24 -0
- package/Types/Date.ts +112 -0
- package/Types/Events/Recurring.ts +99 -1
- package/Types/StatusPage/StatusPageReport.ts +11 -0
- package/Types/StatusPage/StatusPageReportPeriodType.ts +21 -0
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +2 -6
- package/UI/Components/ModelTable/useCustomFieldColumns.ts +21 -2
- package/UI/Utils/ModelAPI/ProjectUsersModelAPI.ts +305 -0
- package/UI/Utils/TeamMembersByUser.ts +302 -0
- package/Utils/StatusPage/ReportPeriod.ts +236 -0
- package/Utils/Telemetry/HostIpAddresses.ts +98 -0
- package/build/dist/Models/DatabaseModels/Host.js +12 -4
- package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesContainer.js +4 -4
- package/build/dist/Models/DatabaseModels/KubernetesContainer.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesResource.js +7 -7
- package/build/dist/Models/DatabaseModels/KubernetesResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/PodmanResource.js +6 -6
- package/build/dist/Models/DatabaseModels/PodmanResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js +87 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +98 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/TeamMemberAPI.js +57 -2
- package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js +45 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js +31 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js +90 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +59 -4
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Services/BillingService.js +466 -36
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +42 -5
- package/build/dist/Server/Services/CephClusterService.js.map +1 -1
- package/build/dist/Server/Services/CephResourceService.js +28 -3
- package/build/dist/Server/Services/CephResourceService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceService.js +42 -5
- package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +69 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +42 -5
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/DockerResourceService.js +34 -4
- package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js +43 -5
- package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmResourceService.js +28 -3
- package/build/dist/Server/Services/DockerSwarmResourceService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +43 -5
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceService.js +1 -8
- package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
- package/build/dist/Server/Services/IoTFleetService.js +42 -5
- package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +43 -5
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesContainerService.js +29 -3
- package/build/dist/Server/Services/KubernetesContainerService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesResourceService.js +32 -4
- package/build/dist/Server/Services/KubernetesResourceService.js.map +1 -1
- package/build/dist/Server/Services/LabelService.js +92 -0
- package/build/dist/Server/Services/LabelService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +47 -0
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostService.js +42 -5
- package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
- package/build/dist/Server/Services/PodmanResourceService.js +34 -4
- package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +135 -21
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js +42 -5
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxResourceService.js +28 -3
- package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationService.js +43 -5
- package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionService.js +43 -5
- package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +347 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js +33 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +69 -46
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +17 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +3 -0
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Types/Database/JSONColumnQuery.js +402 -0
- package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -0
- package/build/dist/Server/Types/Database/Permissions/PublicPermission.js +9 -2
- package/build/dist/Server/Types/Database/Permissions/PublicPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +17 -27
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/Database/TruncateColumnValue.js +32 -0
- package/build/dist/Server/Utils/Database/TruncateColumnValue.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldDefinition.js +2 -0
- package/build/dist/Types/CustomField/CustomFieldDefinition.js.map +1 -0
- package/build/dist/Types/Date.js +68 -0
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Events/Recurring.js +63 -0
- package/build/dist/Types/Events/Recurring.js.map +1 -1
- package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js +22 -0
- package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +18 -2
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js +213 -0
- package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js.map +1 -0
- package/build/dist/UI/Utils/TeamMembersByUser.js +195 -0
- package/build/dist/UI/Utils/TeamMembersByUser.js.map +1 -0
- package/build/dist/Utils/StatusPage/ReportPeriod.js +131 -0
- package/build/dist/Utils/StatusPage/ReportPeriod.js.map +1 -0
- package/build/dist/Utils/Telemetry/HostIpAddresses.js +82 -0
- package/build/dist/Utils/Telemetry/HostIpAddresses.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/tsconfig.json +12 -1
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
import CephClusterService from "../../../Server/Services/CephClusterService";
|
|
2
|
+
import CloudResourceService from "../../../Server/Services/CloudResourceService";
|
|
3
|
+
import DatabaseService from "../../../Server/Services/DatabaseService";
|
|
4
|
+
import DockerHostService from "../../../Server/Services/DockerHostService";
|
|
5
|
+
import DockerSwarmClusterService from "../../../Server/Services/DockerSwarmClusterService";
|
|
6
|
+
import HostService from "../../../Server/Services/HostService";
|
|
7
|
+
import IoTFleetService from "../../../Server/Services/IoTFleetService";
|
|
8
|
+
import KubernetesClusterService from "../../../Server/Services/KubernetesClusterService";
|
|
9
|
+
import PodmanHostService from "../../../Server/Services/PodmanHostService";
|
|
10
|
+
import ProxmoxClusterService from "../../../Server/Services/ProxmoxClusterService";
|
|
11
|
+
import RumApplicationService from "../../../Server/Services/RumApplicationService";
|
|
12
|
+
import ServerlessFunctionService from "../../../Server/Services/ServerlessFunctionService";
|
|
13
|
+
import CephCluster from "../../../Models/DatabaseModels/CephCluster";
|
|
14
|
+
import CloudResource from "../../../Models/DatabaseModels/CloudResource";
|
|
15
|
+
import DatabaseBaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
16
|
+
import DockerHost from "../../../Models/DatabaseModels/DockerHost";
|
|
17
|
+
import DockerSwarmCluster from "../../../Models/DatabaseModels/DockerSwarmCluster";
|
|
18
|
+
import Host from "../../../Models/DatabaseModels/Host";
|
|
19
|
+
import IoTFleet from "../../../Models/DatabaseModels/IoTFleet";
|
|
20
|
+
import KubernetesCluster from "../../../Models/DatabaseModels/KubernetesCluster";
|
|
21
|
+
import PodmanHost from "../../../Models/DatabaseModels/PodmanHost";
|
|
22
|
+
import ProxmoxCluster from "../../../Models/DatabaseModels/ProxmoxCluster";
|
|
23
|
+
import RumApplication from "../../../Models/DatabaseModels/RumApplication";
|
|
24
|
+
import ServerlessFunction from "../../../Models/DatabaseModels/ServerlessFunction";
|
|
25
|
+
import LogDropFilter from "../../../Models/DatabaseModels/LogDropFilter";
|
|
26
|
+
import LogDropFilterService from "../../../Server/Services/LogDropFilterService";
|
|
27
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
28
|
+
import { getMaxLengthFromTableColumnType } from "../../../Types/Database/ColumnLength";
|
|
29
|
+
import { TableColumnMetadata } from "../../../Types/Database/TableColumn";
|
|
30
|
+
import { getJestSpyOn } from "../../Spy";
|
|
31
|
+
import {
|
|
32
|
+
afterEach,
|
|
33
|
+
beforeEach,
|
|
34
|
+
describe,
|
|
35
|
+
expect,
|
|
36
|
+
jest,
|
|
37
|
+
test,
|
|
38
|
+
} from "@jest/globals";
|
|
39
|
+
|
|
40
|
+
/*
|
|
41
|
+
* Follow-up to issue #3006, one layer below where it was first fixed.
|
|
42
|
+
*
|
|
43
|
+
* The raw write paths — updateColumnsByIdWithoutHooks and
|
|
44
|
+
* atomicAddToColumnsByIdWithoutHooks — deliberately skip the whole hook
|
|
45
|
+
* pipeline, and with it checkMaxLengthOfFields. That made every caller
|
|
46
|
+
* individually responsible for not handing Postgres an over-long string,
|
|
47
|
+
* and eleven telemetry services independently forgot to be. When one
|
|
48
|
+
* oversized OpenTelemetry resource attribute reached Postgres raw, the
|
|
49
|
+
* ENTIRE statement was rejected — including the lastSeenAt /
|
|
50
|
+
* otelCollectorStatus columns riding along with it — and markDisconnected*
|
|
51
|
+
* stranded a healthy resource as "disconnected" 15 minutes later while its
|
|
52
|
+
* telemetry kept arriving.
|
|
53
|
+
*
|
|
54
|
+
* The clamp now lives inside those two methods, so a caller cannot forget
|
|
55
|
+
* it and a service added tomorrow inherits it. This suite pins that:
|
|
56
|
+
*
|
|
57
|
+
* 1. the clamp really runs inside the raw write path (asserted on the
|
|
58
|
+
* values BOUND to the SQL, not on what the caller passed),
|
|
59
|
+
* 2. it clamps to each column's own declared width, model by model,
|
|
60
|
+
* for every string column the updateLastSeen paths can write,
|
|
61
|
+
* 3. it touches nothing it should not — text columns, non-strings, and
|
|
62
|
+
* the caller's own object.
|
|
63
|
+
*
|
|
64
|
+
* The repository is faked, so no Postgres and no Redis; the column
|
|
65
|
+
* metadata, the clamp and the SQL generation are all real.
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
const ROW_ID: ObjectID = new ObjectID("11111111-1111-4111-8111-111111111111");
|
|
69
|
+
|
|
70
|
+
interface CapturedQuery {
|
|
71
|
+
sql: string;
|
|
72
|
+
params: Array<unknown>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* A stand-in for the TypeORM repository that is just faithful enough for
|
|
77
|
+
* the raw write paths: real column names come from the model itself, and
|
|
78
|
+
* values are captured rather than sent anywhere.
|
|
79
|
+
*/
|
|
80
|
+
function fakeRepository(
|
|
81
|
+
model: DatabaseBaseModel,
|
|
82
|
+
captured: Array<CapturedQuery>,
|
|
83
|
+
): unknown {
|
|
84
|
+
return {
|
|
85
|
+
metadata: {
|
|
86
|
+
tableName: model.tableName || "FakeTable",
|
|
87
|
+
findColumnWithPropertyName: (
|
|
88
|
+
propertyName: string,
|
|
89
|
+
): { databaseName: string } | undefined => {
|
|
90
|
+
return model.hasColumn(propertyName)
|
|
91
|
+
? { databaseName: propertyName }
|
|
92
|
+
: undefined;
|
|
93
|
+
},
|
|
94
|
+
updateDateColumn: { databaseName: "updatedAt" },
|
|
95
|
+
primaryColumns: [{ databaseName: "_id" }],
|
|
96
|
+
},
|
|
97
|
+
manager: {
|
|
98
|
+
connection: {
|
|
99
|
+
driver: {
|
|
100
|
+
preparePersistentValue: (value: unknown): unknown => {
|
|
101
|
+
return value;
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
query: async (
|
|
106
|
+
sql: string,
|
|
107
|
+
params: Array<unknown>,
|
|
108
|
+
): Promise<Array<unknown>> => {
|
|
109
|
+
captured.push({ sql, params });
|
|
110
|
+
return [];
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** The width the column declares, or undefined for an unbounded one. */
|
|
117
|
+
function declaredMaxLength(
|
|
118
|
+
model: DatabaseBaseModel,
|
|
119
|
+
column: string,
|
|
120
|
+
): number | undefined {
|
|
121
|
+
const metadata: TableColumnMetadata = model.getTableColumnMetadata(column);
|
|
122
|
+
return metadata.type
|
|
123
|
+
? getMaxLengthFromTableColumnType(metadata.type)
|
|
124
|
+
: undefined;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/*
|
|
128
|
+
* Every string column the eleven telemetry updateLastSeen paths can write.
|
|
129
|
+
* Widths are NOT hardcoded — each case reads the column's own declaration,
|
|
130
|
+
* so widening a column keeps the suite honest instead of breaking it.
|
|
131
|
+
*/
|
|
132
|
+
type ClampCase = {
|
|
133
|
+
name: string;
|
|
134
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
135
|
+
service: DatabaseService<any>;
|
|
136
|
+
model: DatabaseBaseModel;
|
|
137
|
+
columns: Array<string>;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/*
|
|
141
|
+
* `PartialEntity<any>` degenerates to a type that rejects plain strings and
|
|
142
|
+
* dates, so the model-agnostic cases below go through this one cast rather
|
|
143
|
+
* than sprinkling casts across every call.
|
|
144
|
+
*/
|
|
145
|
+
async function rawWrite(
|
|
146
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
147
|
+
service: DatabaseService<any>,
|
|
148
|
+
data: Record<string, unknown>,
|
|
149
|
+
): Promise<void> {
|
|
150
|
+
await service.updateColumnsByIdWithoutHooks({
|
|
151
|
+
id: ROW_ID,
|
|
152
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
153
|
+
data: data as any,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const CLAMP_CASES: Array<ClampCase> = [
|
|
158
|
+
{
|
|
159
|
+
name: "HostService",
|
|
160
|
+
service: HostService,
|
|
161
|
+
model: new Host(),
|
|
162
|
+
columns: [
|
|
163
|
+
"osType",
|
|
164
|
+
"osVersion",
|
|
165
|
+
"hostId",
|
|
166
|
+
"hostArch",
|
|
167
|
+
"hostType",
|
|
168
|
+
"containerRuntime",
|
|
169
|
+
"agentVersion",
|
|
170
|
+
"deploymentEnvironment",
|
|
171
|
+
"runtimeName",
|
|
172
|
+
"runtimeVersion",
|
|
173
|
+
"cloudProvider",
|
|
174
|
+
"cloudPlatform",
|
|
175
|
+
"cloudRegion",
|
|
176
|
+
"cloudAccountId",
|
|
177
|
+
],
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
name: "DockerHostService",
|
|
181
|
+
service: DockerHostService,
|
|
182
|
+
model: new DockerHost(),
|
|
183
|
+
columns: ["osType", "osVersion", "agentVersion"],
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "PodmanHostService",
|
|
187
|
+
service: PodmanHostService,
|
|
188
|
+
model: new PodmanHost(),
|
|
189
|
+
columns: ["osType", "osVersion", "agentVersion"],
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: "KubernetesClusterService",
|
|
193
|
+
service: KubernetesClusterService,
|
|
194
|
+
model: new KubernetesCluster(),
|
|
195
|
+
columns: ["agentVersion"],
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: "ProxmoxClusterService",
|
|
199
|
+
service: ProxmoxClusterService,
|
|
200
|
+
model: new ProxmoxCluster(),
|
|
201
|
+
columns: ["pveVersion", "agentVersion"],
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
name: "IoTFleetService",
|
|
205
|
+
service: IoTFleetService,
|
|
206
|
+
model: new IoTFleet(),
|
|
207
|
+
columns: ["agentVersion"],
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: "DockerSwarmClusterService",
|
|
211
|
+
service: DockerSwarmClusterService,
|
|
212
|
+
model: new DockerSwarmCluster(),
|
|
213
|
+
columns: ["dockerVersion", "swarmId", "agentVersion"],
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: "CephClusterService",
|
|
217
|
+
service: CephClusterService,
|
|
218
|
+
model: new CephCluster(),
|
|
219
|
+
columns: ["cephVersion", "fsid", "agentVersion"],
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
name: "ServerlessFunctionService",
|
|
223
|
+
service: ServerlessFunctionService,
|
|
224
|
+
model: new ServerlessFunction(),
|
|
225
|
+
columns: [
|
|
226
|
+
"agentVersion",
|
|
227
|
+
"cloudPlatform",
|
|
228
|
+
"cloudProvider",
|
|
229
|
+
"cloudRegion",
|
|
230
|
+
"cloudAccountId",
|
|
231
|
+
"functionVersion",
|
|
232
|
+
"runtimeName",
|
|
233
|
+
"runtimeVersion",
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: "CloudResourceService",
|
|
238
|
+
service: CloudResourceService,
|
|
239
|
+
model: new CloudResource(),
|
|
240
|
+
columns: [
|
|
241
|
+
"agentVersion",
|
|
242
|
+
"cloudPlatform",
|
|
243
|
+
"cloudProvider",
|
|
244
|
+
"cloudRegion",
|
|
245
|
+
"cloudAccountId",
|
|
246
|
+
"runtimeName",
|
|
247
|
+
"runtimeVersion",
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: "RumApplicationService",
|
|
252
|
+
service: RumApplicationService,
|
|
253
|
+
model: new RumApplication(),
|
|
254
|
+
columns: ["agentVersion", "clientType", "sdkLanguage"],
|
|
255
|
+
},
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
describe("updateColumnsByIdWithoutHooks clamps oversized strings", () => {
|
|
259
|
+
let captured: Array<CapturedQuery>;
|
|
260
|
+
|
|
261
|
+
beforeEach(() => {
|
|
262
|
+
captured = [];
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
afterEach(() => {
|
|
266
|
+
jest.restoreAllMocks();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
/** The value bound for `column` in the single captured statement. */
|
|
270
|
+
function boundValue(column: string, columns: Array<string>): unknown {
|
|
271
|
+
/*
|
|
272
|
+
* Values are bound in Object.entries order, which for these writes is
|
|
273
|
+
* insertion order — the same order the caller built the payload in.
|
|
274
|
+
*/
|
|
275
|
+
return captured[0]!.params[columns.indexOf(column)];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
describe.each(CLAMP_CASES)(
|
|
279
|
+
"$name",
|
|
280
|
+
({ service, model, columns }: ClampCase) => {
|
|
281
|
+
beforeEach(() => {
|
|
282
|
+
getJestSpyOn(service, "getRepository").mockReturnValue(
|
|
283
|
+
fakeRepository(model, captured),
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test.each(columns)(
|
|
288
|
+
"clamps an oversized %s to the width its column declares",
|
|
289
|
+
async (column: string) => {
|
|
290
|
+
const maxLength: number | undefined = declaredMaxLength(
|
|
291
|
+
model,
|
|
292
|
+
column,
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
/*
|
|
296
|
+
* A column with no declared width (text) cannot overflow, so
|
|
297
|
+
* there is nothing to clamp — that case is covered separately.
|
|
298
|
+
*/
|
|
299
|
+
expect(maxLength).toBeDefined();
|
|
300
|
+
|
|
301
|
+
await rawWrite(service, {
|
|
302
|
+
[column]: "x".repeat(maxLength! + 250),
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
expect(boundValue(column, [column])).toHaveLength(maxLength!);
|
|
306
|
+
},
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
test("clamping one bad column never costs the liveness columns", async () => {
|
|
310
|
+
const column: string = columns[0]!;
|
|
311
|
+
const lastSeenAt: Date = new Date();
|
|
312
|
+
|
|
313
|
+
await rawWrite(service, {
|
|
314
|
+
lastSeenAt: lastSeenAt,
|
|
315
|
+
otelCollectorStatus: "connected",
|
|
316
|
+
[column]: "x".repeat(5000),
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
const order: Array<string> = [
|
|
320
|
+
"lastSeenAt",
|
|
321
|
+
"otelCollectorStatus",
|
|
322
|
+
column,
|
|
323
|
+
];
|
|
324
|
+
expect(boundValue("lastSeenAt", order)).toBe(lastSeenAt);
|
|
325
|
+
expect(boundValue("otelCollectorStatus", order)).toBe("connected");
|
|
326
|
+
expect(captured).toHaveLength(1);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test("a value that already fits is bound byte-for-byte", async () => {
|
|
330
|
+
const column: string = columns[0]!;
|
|
331
|
+
|
|
332
|
+
await rawWrite(service, { [column]: "fits-easily" });
|
|
333
|
+
|
|
334
|
+
expect(boundValue(column, [column])).toBe("fits-easily");
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
test("the caller's own object is not mutated", async () => {
|
|
338
|
+
const column: string = columns[0]!;
|
|
339
|
+
const original: string = "x".repeat(5000);
|
|
340
|
+
const data: Record<string, unknown> = { [column]: original };
|
|
341
|
+
|
|
342
|
+
await rawWrite(service, data);
|
|
343
|
+
|
|
344
|
+
expect(data[column]).toBe(original);
|
|
345
|
+
});
|
|
346
|
+
},
|
|
347
|
+
);
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
describe("updateColumnsByIdWithoutHooks clamp boundaries", () => {
|
|
351
|
+
let captured: Array<CapturedQuery>;
|
|
352
|
+
const host: Host = new Host();
|
|
353
|
+
|
|
354
|
+
beforeEach(() => {
|
|
355
|
+
captured = [];
|
|
356
|
+
getJestSpyOn(HostService, "getRepository").mockReturnValue(
|
|
357
|
+
fakeRepository(host, captured),
|
|
358
|
+
);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
afterEach(() => {
|
|
362
|
+
jest.restoreAllMocks();
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test("binds a 55-address host.ip list whole — the column is text now", async () => {
|
|
366
|
+
const hostIpAddresses: string = Array.from(
|
|
367
|
+
{ length: 55 },
|
|
368
|
+
(_unused: unknown, i: number) => {
|
|
369
|
+
return `fe80::42:acff:fe11:${(0x1000 + i).toString(16)}`;
|
|
370
|
+
},
|
|
371
|
+
).join(", ");
|
|
372
|
+
|
|
373
|
+
await HostService.updateColumnsByIdWithoutHooks({
|
|
374
|
+
id: ROW_ID,
|
|
375
|
+
data: { hostIpAddresses: hostIpAddresses },
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
expect(hostIpAddresses.length).toBeGreaterThan(500);
|
|
379
|
+
expect(captured[0]!.params[0]).toBe(hostIpAddresses);
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
test("a value exactly at the limit is left alone", async () => {
|
|
383
|
+
const exact: string = "l".repeat(declaredMaxLength(host, "osType")!);
|
|
384
|
+
|
|
385
|
+
await HostService.updateColumnsByIdWithoutHooks({
|
|
386
|
+
id: ROW_ID,
|
|
387
|
+
data: { osType: exact },
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
expect(captured[0]!.params[0]).toBe(exact);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test("a clamped value stays a prefix of the original", async () => {
|
|
394
|
+
const original: string = "abcdefghij".repeat(40);
|
|
395
|
+
|
|
396
|
+
await HostService.updateColumnsByIdWithoutHooks({
|
|
397
|
+
id: ROW_ID,
|
|
398
|
+
data: { osType: original },
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
expect(original.startsWith(captured[0]!.params[0] as string)).toBe(true);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
test("non-string values pass through untouched", async () => {
|
|
405
|
+
const lastSeenAt: Date = new Date();
|
|
406
|
+
|
|
407
|
+
await HostService.updateColumnsByIdWithoutHooks({
|
|
408
|
+
id: ROW_ID,
|
|
409
|
+
data: {
|
|
410
|
+
lastSeenAt: lastSeenAt,
|
|
411
|
+
cpuCores: 64,
|
|
412
|
+
totalMemoryBytes: 274877906944,
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
expect(captured[0]!.params.slice(0, 3)).toEqual([
|
|
417
|
+
lastSeenAt,
|
|
418
|
+
64,
|
|
419
|
+
274877906944,
|
|
420
|
+
]);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
test("an unknown column is still rejected, not silently clamped away", async () => {
|
|
424
|
+
await expect(
|
|
425
|
+
HostService.updateColumnsByIdWithoutHooks({
|
|
426
|
+
id: ROW_ID,
|
|
427
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
428
|
+
data: { notAColumn: "z".repeat(5000) } as any,
|
|
429
|
+
}),
|
|
430
|
+
).rejects.toThrow("unknown column");
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test("the clamp does not disturb the primary-key predicate", async () => {
|
|
434
|
+
await HostService.updateColumnsByIdWithoutHooks({
|
|
435
|
+
id: ROW_ID,
|
|
436
|
+
data: { osType: "l".repeat(5000) },
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
expect(captured[0]!.sql).toMatch(/WHERE "_id" = \$\d+$/);
|
|
440
|
+
expect(captured[0]!.params[captured[0]!.params.length - 1]).toBe(
|
|
441
|
+
ROW_ID.toString(),
|
|
442
|
+
);
|
|
443
|
+
});
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
/*
|
|
447
|
+
* The counter-flush primitive is the other raw path, and it has exactly the
|
|
448
|
+
* same exposure: an over-long string in `set` would abort the increments
|
|
449
|
+
* bound into the same statement.
|
|
450
|
+
*/
|
|
451
|
+
describe("atomicAddToColumnsByIdWithoutHooks clamps its set values", () => {
|
|
452
|
+
let captured: Array<CapturedQuery>;
|
|
453
|
+
const model: LogDropFilter = new LogDropFilter();
|
|
454
|
+
|
|
455
|
+
beforeEach(() => {
|
|
456
|
+
captured = [];
|
|
457
|
+
getJestSpyOn(LogDropFilterService, "getRepository").mockReturnValue(
|
|
458
|
+
fakeRepository(model, captured),
|
|
459
|
+
);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
afterEach(() => {
|
|
463
|
+
jest.restoreAllMocks();
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test("clamps an oversized set value to its declared width", async () => {
|
|
467
|
+
const maxLength: number = declaredMaxLength(model, "name")!;
|
|
468
|
+
|
|
469
|
+
await LogDropFilterService.atomicAddToColumnsByIdWithoutHooks({
|
|
470
|
+
id: ROW_ID,
|
|
471
|
+
add: { droppedCount: 5 },
|
|
472
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
473
|
+
set: { name: "n".repeat(maxLength + 250) } as any,
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
// params: [delta, set value, ...]
|
|
477
|
+
expect(captured[0]!.params[1]).toHaveLength(maxLength);
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
test("leaves the numeric delta alone", async () => {
|
|
481
|
+
await LogDropFilterService.atomicAddToColumnsByIdWithoutHooks({
|
|
482
|
+
id: ROW_ID,
|
|
483
|
+
add: { droppedCount: 7 },
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
expect(captured[0]!.params[0]).toBe(7);
|
|
487
|
+
expect(captured[0]!.sql).toContain("COALESCE");
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
test("does not mutate the caller's set object", async () => {
|
|
491
|
+
const original: string = "n".repeat(5000);
|
|
492
|
+
const set: Record<string, unknown> = { name: original };
|
|
493
|
+
|
|
494
|
+
await LogDropFilterService.atomicAddToColumnsByIdWithoutHooks({
|
|
495
|
+
id: ROW_ID,
|
|
496
|
+
add: { droppedCount: 1 },
|
|
497
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
498
|
+
set: set as any,
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
expect(set["name"]).toBe(original);
|
|
502
|
+
});
|
|
503
|
+
});
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import Host from "../../../Models/DatabaseModels/Host";
|
|
2
|
+
import { AddHostIpAddresses1778013317872 } from "../../../Server/Infrastructure/Postgres/SchemaMigrations/1778013317872-AddHostIpAddresses";
|
|
3
|
+
import { IncreaseHostIpAddressesLength1785915638551 } from "../../../Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength";
|
|
4
|
+
import SchemaMigrations from "../../../Server/Infrastructure/Postgres/SchemaMigrations/Index";
|
|
5
|
+
import ColumnType from "../../../Types/Database/ColumnType";
|
|
6
|
+
import TableColumnType from "../../../Types/Database/TableColumnType";
|
|
7
|
+
import { getMaxLengthFromTableColumnType } from "../../../Types/Database/ColumnLength";
|
|
8
|
+
import { TableColumnMetadata } from "../../../Types/Database/TableColumn";
|
|
9
|
+
import { QueryRunner, getMetadataArgsStorage } from "typeorm";
|
|
10
|
+
import type { ColumnMetadataArgs } from "typeorm/metadata-args/ColumnMetadataArgs";
|
|
11
|
+
import { describe, expect, test } from "@jest/globals";
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* Issue #3006: Host.hostIpAddresses was character varying(500). The OTel
|
|
15
|
+
* `host.ip` resource attribute is an ARRAY of every address on every
|
|
16
|
+
* interface, and a Docker host with IPv6 enabled reported 55 of them
|
|
17
|
+
* (~1450 characters). The overflow aborted the whole Host metadata UPDATE
|
|
18
|
+
* — which also carries lastSeenAt and otelCollectorStatus — so a host with
|
|
19
|
+
* healthy, still-arriving telemetry was displayed as "Disconnected".
|
|
20
|
+
*
|
|
21
|
+
* Three things have to line up for the fix to hold, and a regression in any
|
|
22
|
+
* one of them silently reintroduces the bug:
|
|
23
|
+
* 1. the entity declares a `text` column (no max length),
|
|
24
|
+
* 2. the migration actually widens the existing column in Postgres, and
|
|
25
|
+
* its down() can narrow back without exploding on rows written since,
|
|
26
|
+
* 3. the migration is registered in SchemaMigrations/Index.ts — an
|
|
27
|
+
* unregistered migration is dead code that never runs on boot.
|
|
28
|
+
*
|
|
29
|
+
* Pure metadata/mocks — no Postgres connection anywhere.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
function hostIpColumnArgs(): ColumnMetadataArgs {
|
|
33
|
+
const args: ColumnMetadataArgs | undefined = getMetadataArgsStorage()
|
|
34
|
+
.columns.filter((column: ColumnMetadataArgs) => {
|
|
35
|
+
return column.target === Host;
|
|
36
|
+
})
|
|
37
|
+
.find((column: ColumnMetadataArgs) => {
|
|
38
|
+
return column.propertyName === "hostIpAddresses";
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (!args) {
|
|
42
|
+
throw new Error("Host.hostIpAddresses has no TypeORM @Column metadata");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return args;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function makeQueryRunner(): { runner: QueryRunner; query: jest.Mock } {
|
|
49
|
+
const query: jest.Mock = jest.fn().mockResolvedValue(undefined);
|
|
50
|
+
return { runner: { query } as unknown as QueryRunner, query };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function executedSql(query: jest.Mock): Array<string> {
|
|
54
|
+
return query.mock.calls.map((call: Array<unknown>) => {
|
|
55
|
+
return String(call[0]);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
describe("Host.hostIpAddresses entity declaration", () => {
|
|
60
|
+
test("is a text column, not a bounded varchar", () => {
|
|
61
|
+
expect(hostIpColumnArgs().options.type).toBe(ColumnType.VeryLongText);
|
|
62
|
+
expect(ColumnType.VeryLongText).toBe("text");
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("declares no length — a length on a text column is what regenerates drift", () => {
|
|
66
|
+
expect(hostIpColumnArgs().options.length).toBeUndefined();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("is still nullable, so hosts that report no host.ip are unaffected", () => {
|
|
70
|
+
expect(hostIpColumnArgs().options.nullable).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("its TableColumn type is VeryLongText", () => {
|
|
74
|
+
const metadata: TableColumnMetadata = new Host().getTableColumnMetadata(
|
|
75
|
+
"hostIpAddresses",
|
|
76
|
+
);
|
|
77
|
+
expect(metadata.type).toBe(TableColumnType.VeryLongText);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("has no max length, so DatabaseService length validation cannot reject a long IP list", () => {
|
|
81
|
+
const metadata: TableColumnMetadata = new Host().getTableColumnMetadata(
|
|
82
|
+
"hostIpAddresses",
|
|
83
|
+
);
|
|
84
|
+
expect(getMaxLengthFromTableColumnType(metadata.type)).toBeUndefined();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("would have been rejected under the old LongText declaration", () => {
|
|
88
|
+
/*
|
|
89
|
+
* Guards the premise of the fix: 500 really was the bound that the
|
|
90
|
+
* reported ~1450-character payload blew through.
|
|
91
|
+
*/
|
|
92
|
+
expect(getMaxLengthFromTableColumnType(TableColumnType.LongText)).toBe(500);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("is still readable on relation queries and optional", () => {
|
|
96
|
+
const metadata: TableColumnMetadata = new Host().getTableColumnMetadata(
|
|
97
|
+
"hostIpAddresses",
|
|
98
|
+
);
|
|
99
|
+
expect(metadata.canReadOnRelationQuery).toBe(true);
|
|
100
|
+
expect(metadata.required).toBeFalsy();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("IncreaseHostIpAddressesLength1785915638551 SQL contract", () => {
|
|
105
|
+
const migration: IncreaseHostIpAddressesLength1785915638551 =
|
|
106
|
+
new IncreaseHostIpAddressesLength1785915638551();
|
|
107
|
+
|
|
108
|
+
test("up() widens the column to text in a single statement", async () => {
|
|
109
|
+
const { runner, query } = makeQueryRunner();
|
|
110
|
+
await migration.up(runner);
|
|
111
|
+
|
|
112
|
+
expect(executedSql(query)).toEqual([
|
|
113
|
+
`ALTER TABLE "Host" ALTER COLUMN "hostIpAddresses" TYPE text`,
|
|
114
|
+
]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("up() touches no other table or column", async () => {
|
|
118
|
+
const { runner, query } = makeQueryRunner();
|
|
119
|
+
await migration.up(runner);
|
|
120
|
+
|
|
121
|
+
for (const sql of executedSql(query)) {
|
|
122
|
+
expect(sql).toContain(`"Host"`);
|
|
123
|
+
expect(sql).toContain(`"hostIpAddresses"`);
|
|
124
|
+
expect(sql).not.toContain("DROP");
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("down() clips oversized rows BEFORE narrowing, so the revert cannot fail", async () => {
|
|
129
|
+
const { runner, query } = makeQueryRunner();
|
|
130
|
+
await migration.down(runner);
|
|
131
|
+
|
|
132
|
+
const statements: Array<string> = executedSql(query);
|
|
133
|
+
expect(statements).toHaveLength(2);
|
|
134
|
+
|
|
135
|
+
const [clip, narrow] = statements as [string, string];
|
|
136
|
+
expect(clip).toContain("UPDATE");
|
|
137
|
+
expect(clip).toContain("LEFT");
|
|
138
|
+
expect(clip).toContain("500");
|
|
139
|
+
expect(narrow).toBe(
|
|
140
|
+
`ALTER TABLE "Host" ALTER COLUMN "hostIpAddresses" TYPE character varying(500)`,
|
|
141
|
+
);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("down() restores exactly the width the original migration created", async () => {
|
|
145
|
+
const addColumn: AddHostIpAddresses1778013317872 =
|
|
146
|
+
new AddHostIpAddresses1778013317872();
|
|
147
|
+
const { runner: addRunner, query: addQuery } = makeQueryRunner();
|
|
148
|
+
await addColumn.up(addRunner);
|
|
149
|
+
|
|
150
|
+
const { runner: downRunner, query: downQuery } = makeQueryRunner();
|
|
151
|
+
await migration.down(downRunner);
|
|
152
|
+
|
|
153
|
+
// The ADD used "character varying(500)"; the revert must land on the same.
|
|
154
|
+
expect(executedSql(addQuery).join("\n")).toContain(
|
|
155
|
+
"character varying(500)",
|
|
156
|
+
);
|
|
157
|
+
expect(executedSql(downQuery).join("\n")).toContain(
|
|
158
|
+
"character varying(500)",
|
|
159
|
+
);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("the class name carries its own timestamp, matching the file name", () => {
|
|
163
|
+
expect(migration.name).toBe("IncreaseHostIpAddressesLength1785915638551");
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe("IncreaseHostIpAddressesLength1785915638551 registration", () => {
|
|
168
|
+
test("is registered in SchemaMigrations/Index.ts", () => {
|
|
169
|
+
expect(SchemaMigrations).toContain(
|
|
170
|
+
IncreaseHostIpAddressesLength1785915638551,
|
|
171
|
+
);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("is registered exactly once", () => {
|
|
175
|
+
const occurrences: number = SchemaMigrations.filter(
|
|
176
|
+
(migration: unknown) => {
|
|
177
|
+
return migration === IncreaseHostIpAddressesLength1785915638551;
|
|
178
|
+
},
|
|
179
|
+
).length;
|
|
180
|
+
expect(occurrences).toBe(1);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("runs after the migration that created the column", () => {
|
|
184
|
+
/*
|
|
185
|
+
* TypeORM executes registered migrations in array order. Widening a
|
|
186
|
+
* column that does not exist yet fails, so the ADD must come first.
|
|
187
|
+
*/
|
|
188
|
+
const addIndex: number = SchemaMigrations.indexOf(
|
|
189
|
+
AddHostIpAddresses1778013317872,
|
|
190
|
+
);
|
|
191
|
+
const widenIndex: number = SchemaMigrations.indexOf(
|
|
192
|
+
IncreaseHostIpAddressesLength1785915638551,
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
expect(addIndex).toBeGreaterThanOrEqual(0);
|
|
196
|
+
expect(widenIndex).toBeGreaterThan(addIndex);
|
|
197
|
+
});
|
|
198
|
+
});
|