@oneuptime/common 12.0.0 → 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.
Files changed (237) hide show
  1. package/Models/DatabaseModels/Host.ts +12 -4
  2. package/Models/DatabaseModels/KubernetesContainer.ts +5 -4
  3. package/Models/DatabaseModels/KubernetesResource.ts +8 -7
  4. package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.ts +12 -2
  5. package/Models/DatabaseModels/PodmanResource.ts +6 -6
  6. package/Models/DatabaseModels/ServiceLevelObjective.ts +91 -0
  7. package/Models/DatabaseModels/StatusPage.ts +95 -0
  8. package/Server/API/TeamMemberAPI.ts +85 -0
  9. package/Server/Infrastructure/Postgres/LocalMigrationGenerationDataSource.ts +12 -2
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.ts +84 -0
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.ts +40 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.ts +131 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.ts +25 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  15. package/Server/Middleware/ProjectAuthorization.ts +66 -4
  16. package/Server/Services/BillingService.ts +600 -51
  17. package/Server/Services/CephClusterService.ts +47 -5
  18. package/Server/Services/CephResourceService.ts +43 -4
  19. package/Server/Services/CloudResourceService.ts +47 -5
  20. package/Server/Services/DatabaseService.ts +87 -6
  21. package/Server/Services/DockerHostService.ts +47 -5
  22. package/Server/Services/DockerResourceService.ts +62 -8
  23. package/Server/Services/DockerSwarmClusterService.ts +48 -5
  24. package/Server/Services/DockerSwarmResourceService.ts +53 -4
  25. package/Server/Services/HostService.ts +48 -5
  26. package/Server/Services/IoTDeviceService.ts +1 -17
  27. package/Server/Services/IoTFleetService.ts +47 -5
  28. package/Server/Services/KubernetesClusterService.ts +48 -5
  29. package/Server/Services/KubernetesContainerService.ts +50 -9
  30. package/Server/Services/KubernetesResourceService.ts +53 -7
  31. package/Server/Services/LabelService.ts +106 -1
  32. package/Server/Services/LlmProviderService.ts +69 -9
  33. package/Server/Services/MonitorService.ts +57 -0
  34. package/Server/Services/PodmanHostService.ts +47 -5
  35. package/Server/Services/PodmanResourceService.ts +62 -8
  36. package/Server/Services/ProjectService.ts +165 -35
  37. package/Server/Services/ProxmoxClusterService.ts +47 -5
  38. package/Server/Services/ProxmoxResourceService.ts +43 -4
  39. package/Server/Services/RumApplicationService.ts +48 -5
  40. package/Server/Services/RunnerService.ts +1 -7
  41. package/Server/Services/ServerlessFunctionService.ts +48 -5
  42. package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +426 -0
  43. package/Server/Services/ServiceLevelObjectiveService.ts +39 -0
  44. package/Server/Services/StatusPageService.ts +95 -36
  45. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +18 -1
  46. package/Server/Services/StatusPageSubscriberService.ts +3 -0
  47. package/Server/Types/Database/JSONColumnQuery.ts +621 -0
  48. package/Server/Types/Database/Permissions/PublicPermission.ts +9 -2
  49. package/Server/Types/Database/QueryHelper.ts +17 -32
  50. package/Server/Utils/Database/TruncateColumnValue.ts +84 -0
  51. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +12 -0
  52. package/Tests/App/Dashboard/RunnerInstallInstructions.test.tsx +119 -0
  53. package/Tests/App/Dashboard/RunnerStatus.test.tsx +274 -0
  54. package/Tests/App/Dashboard/UsersTableGroupsByPerson.test.tsx +448 -0
  55. package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +704 -0
  56. package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +303 -0
  57. package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +350 -0
  58. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyHeader.test.ts +362 -0
  59. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +687 -0
  60. package/Tests/Server/Services/BillingService.test.ts +78 -3
  61. package/Tests/Server/Services/BillingServiceChangePlanCancel.test.ts +645 -0
  62. package/Tests/Server/Services/BillingServiceTrialPlanChange.test.ts +1005 -0
  63. package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +503 -0
  64. package/Tests/Server/Services/HostIpAddressesColumnWidth.test.ts +198 -0
  65. package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +510 -0
  66. package/Tests/Server/Services/InventoryResourceNameColumnWidth.test.ts +500 -0
  67. package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +586 -0
  68. package/Tests/Server/Services/InventoryUpsertTruncationGuard.test.ts +1242 -0
  69. package/Tests/Server/Services/LlmProviderUsableByProject.test.ts +293 -0
  70. package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +650 -0
  71. package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +1 -0
  72. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +387 -0
  73. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +770 -0
  74. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +130 -0
  75. package/Tests/Server/Services/SloMonitorLabelRuleHooks.test.ts +352 -0
  76. package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +29 -13
  77. package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +69 -8
  78. package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +593 -0
  79. package/Tests/Server/Types/Database/Permissions/PublicPermission.test.ts +495 -0
  80. package/Tests/Server/Types/Database/QueryUtil.test.ts +113 -0
  81. package/Tests/Server/Utils/AI/ToolArgsExtractors.test.ts +141 -0
  82. package/Tests/Server/Utils/AI/Toolbox/WidgetBuilder.test.ts +205 -0
  83. package/Tests/Server/Utils/Database/TruncateColumnValue.test.ts +179 -0
  84. package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +897 -0
  85. package/Tests/Types/Events/Recurring.test.ts +157 -0
  86. package/Tests/Types/Runner/RunnerLiveStatus.test.ts +320 -0
  87. package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +285 -0
  88. package/Tests/UI/Components/Detail/EntityFields.test.tsx +166 -0
  89. package/Tests/UI/Components/ModelTable/ModelTableGroupsProjectUsers.test.tsx +375 -0
  90. package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +196 -0
  91. package/Tests/UI/Utils/ProjectUsersModelAPI.test.ts +739 -0
  92. package/Tests/UI/Utils/TeamMembersByUser.test.ts +633 -0
  93. package/Tests/Utils/StatusPage/ReportPeriod.test.ts +218 -0
  94. package/Tests/Utils/Telemetry/HostIpAddresses.test.ts +291 -0
  95. package/Types/CustomField/CustomFieldDefinition.ts +24 -0
  96. package/Types/Date.ts +112 -0
  97. package/Types/Events/Recurring.ts +99 -1
  98. package/Types/Runbook/RunbookStep.ts +15 -0
  99. package/Types/Runner/RunnerLiveStatus.ts +114 -0
  100. package/Types/StatusPage/StatusPageReport.ts +11 -0
  101. package/Types/StatusPage/StatusPageReportPeriodType.ts +21 -0
  102. package/UI/Components/Detail/Detail.tsx +155 -0
  103. package/UI/Components/ModelTable/CustomFieldColumns.tsx +2 -6
  104. package/UI/Components/ModelTable/useCustomFieldColumns.ts +21 -2
  105. package/UI/Utils/ModelAPI/ProjectUsersModelAPI.ts +305 -0
  106. package/UI/Utils/TeamMembersByUser.ts +302 -0
  107. package/Utils/StatusPage/ReportPeriod.ts +236 -0
  108. package/Utils/Telemetry/HostIpAddresses.ts +98 -0
  109. package/build/dist/Models/DatabaseModels/Host.js +12 -4
  110. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  111. package/build/dist/Models/DatabaseModels/KubernetesContainer.js +4 -4
  112. package/build/dist/Models/DatabaseModels/KubernetesContainer.js.map +1 -1
  113. package/build/dist/Models/DatabaseModels/KubernetesResource.js +7 -7
  114. package/build/dist/Models/DatabaseModels/KubernetesResource.js.map +1 -1
  115. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js +16 -4
  116. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js.map +1 -1
  117. package/build/dist/Models/DatabaseModels/PodmanResource.js +6 -6
  118. package/build/dist/Models/DatabaseModels/PodmanResource.js.map +1 -1
  119. package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js +87 -0
  120. package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js.map +1 -1
  121. package/build/dist/Models/DatabaseModels/StatusPage.js +98 -0
  122. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  123. package/build/dist/Server/API/TeamMemberAPI.js +57 -2
  124. package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
  125. package/build/dist/Server/Infrastructure/Postgres/LocalMigrationGenerationDataSource.js +11 -1
  126. package/build/dist/Server/Infrastructure/Postgres/LocalMigrationGenerationDataSource.js.map +1 -1
  127. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js +45 -0
  128. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js.map +1 -0
  129. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js +31 -0
  130. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js.map +1 -0
  131. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js +90 -0
  132. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js.map +1 -0
  133. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js +14 -0
  134. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js.map +1 -0
  135. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  136. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  137. package/build/dist/Server/Middleware/ProjectAuthorization.js +59 -4
  138. package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
  139. package/build/dist/Server/Services/BillingService.js +466 -36
  140. package/build/dist/Server/Services/BillingService.js.map +1 -1
  141. package/build/dist/Server/Services/CephClusterService.js +42 -5
  142. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  143. package/build/dist/Server/Services/CephResourceService.js +28 -3
  144. package/build/dist/Server/Services/CephResourceService.js.map +1 -1
  145. package/build/dist/Server/Services/CloudResourceService.js +42 -5
  146. package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
  147. package/build/dist/Server/Services/DatabaseService.js +69 -2
  148. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  149. package/build/dist/Server/Services/DockerHostService.js +42 -5
  150. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  151. package/build/dist/Server/Services/DockerResourceService.js +34 -4
  152. package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
  153. package/build/dist/Server/Services/DockerSwarmClusterService.js +43 -5
  154. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  155. package/build/dist/Server/Services/DockerSwarmResourceService.js +28 -3
  156. package/build/dist/Server/Services/DockerSwarmResourceService.js.map +1 -1
  157. package/build/dist/Server/Services/HostService.js +43 -5
  158. package/build/dist/Server/Services/HostService.js.map +1 -1
  159. package/build/dist/Server/Services/IoTDeviceService.js +1 -8
  160. package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
  161. package/build/dist/Server/Services/IoTFleetService.js +42 -5
  162. package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
  163. package/build/dist/Server/Services/KubernetesClusterService.js +43 -5
  164. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  165. package/build/dist/Server/Services/KubernetesContainerService.js +29 -3
  166. package/build/dist/Server/Services/KubernetesContainerService.js.map +1 -1
  167. package/build/dist/Server/Services/KubernetesResourceService.js +32 -4
  168. package/build/dist/Server/Services/KubernetesResourceService.js.map +1 -1
  169. package/build/dist/Server/Services/LabelService.js +92 -0
  170. package/build/dist/Server/Services/LabelService.js.map +1 -1
  171. package/build/dist/Server/Services/LlmProviderService.js +61 -8
  172. package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
  173. package/build/dist/Server/Services/MonitorService.js +47 -0
  174. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  175. package/build/dist/Server/Services/PodmanHostService.js +42 -5
  176. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  177. package/build/dist/Server/Services/PodmanResourceService.js +34 -4
  178. package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
  179. package/build/dist/Server/Services/ProjectService.js +135 -21
  180. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  181. package/build/dist/Server/Services/ProxmoxClusterService.js +42 -5
  182. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  183. package/build/dist/Server/Services/ProxmoxResourceService.js +28 -3
  184. package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
  185. package/build/dist/Server/Services/RumApplicationService.js +43 -5
  186. package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
  187. package/build/dist/Server/Services/RunnerService.js +1 -6
  188. package/build/dist/Server/Services/RunnerService.js.map +1 -1
  189. package/build/dist/Server/Services/ServerlessFunctionService.js +43 -5
  190. package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
  191. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +347 -0
  192. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -0
  193. package/build/dist/Server/Services/ServiceLevelObjectiveService.js +33 -1
  194. package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
  195. package/build/dist/Server/Services/StatusPageService.js +69 -46
  196. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  197. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +17 -1
  198. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
  199. package/build/dist/Server/Services/StatusPageSubscriberService.js +3 -0
  200. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  201. package/build/dist/Server/Types/Database/JSONColumnQuery.js +402 -0
  202. package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -0
  203. package/build/dist/Server/Types/Database/Permissions/PublicPermission.js +9 -2
  204. package/build/dist/Server/Types/Database/Permissions/PublicPermission.js.map +1 -1
  205. package/build/dist/Server/Types/Database/QueryHelper.js +17 -27
  206. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  207. package/build/dist/Server/Utils/Database/TruncateColumnValue.js +32 -0
  208. package/build/dist/Server/Utils/Database/TruncateColumnValue.js.map +1 -0
  209. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +11 -0
  210. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
  211. package/build/dist/Types/CustomField/CustomFieldDefinition.js +2 -0
  212. package/build/dist/Types/CustomField/CustomFieldDefinition.js.map +1 -0
  213. package/build/dist/Types/Date.js +68 -0
  214. package/build/dist/Types/Date.js.map +1 -1
  215. package/build/dist/Types/Events/Recurring.js +63 -0
  216. package/build/dist/Types/Events/Recurring.js.map +1 -1
  217. package/build/dist/Types/Runbook/RunbookStep.js.map +1 -1
  218. package/build/dist/Types/Runner/RunnerLiveStatus.js +69 -0
  219. package/build/dist/Types/Runner/RunnerLiveStatus.js.map +1 -0
  220. package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js +22 -0
  221. package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js.map +1 -0
  222. package/build/dist/UI/Components/Detail/Detail.js +97 -0
  223. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  224. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  225. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +18 -2
  226. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
  227. package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js +213 -0
  228. package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js.map +1 -0
  229. package/build/dist/UI/Utils/TeamMembersByUser.js +195 -0
  230. package/build/dist/UI/Utils/TeamMembersByUser.js.map +1 -0
  231. package/build/dist/Utils/StatusPage/ReportPeriod.js +131 -0
  232. package/build/dist/Utils/StatusPage/ReportPeriod.js.map +1 -0
  233. package/build/dist/Utils/Telemetry/HostIpAddresses.js +82 -0
  234. package/build/dist/Utils/Telemetry/HostIpAddresses.js.map +1 -0
  235. package/jest.config.json +2 -0
  236. package/package.json +1 -1
  237. 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
+ });