@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.
Files changed (205) 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/PodmanResource.ts +6 -6
  5. package/Models/DatabaseModels/ServiceLevelObjective.ts +91 -0
  6. package/Models/DatabaseModels/StatusPage.ts +95 -0
  7. package/Server/API/TeamMemberAPI.ts +85 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.ts +84 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.ts +40 -0
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.ts +131 -0
  11. package/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.ts +25 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  13. package/Server/Middleware/ProjectAuthorization.ts +66 -4
  14. package/Server/Services/BillingService.ts +600 -51
  15. package/Server/Services/CephClusterService.ts +47 -5
  16. package/Server/Services/CephResourceService.ts +43 -4
  17. package/Server/Services/CloudResourceService.ts +47 -5
  18. package/Server/Services/DatabaseService.ts +87 -6
  19. package/Server/Services/DockerHostService.ts +47 -5
  20. package/Server/Services/DockerResourceService.ts +62 -8
  21. package/Server/Services/DockerSwarmClusterService.ts +48 -5
  22. package/Server/Services/DockerSwarmResourceService.ts +53 -4
  23. package/Server/Services/HostService.ts +48 -5
  24. package/Server/Services/IoTDeviceService.ts +1 -17
  25. package/Server/Services/IoTFleetService.ts +47 -5
  26. package/Server/Services/KubernetesClusterService.ts +48 -5
  27. package/Server/Services/KubernetesContainerService.ts +50 -9
  28. package/Server/Services/KubernetesResourceService.ts +53 -7
  29. package/Server/Services/LabelService.ts +106 -1
  30. package/Server/Services/MonitorService.ts +57 -0
  31. package/Server/Services/PodmanHostService.ts +47 -5
  32. package/Server/Services/PodmanResourceService.ts +62 -8
  33. package/Server/Services/ProjectService.ts +165 -35
  34. package/Server/Services/ProxmoxClusterService.ts +47 -5
  35. package/Server/Services/ProxmoxResourceService.ts +43 -4
  36. package/Server/Services/RumApplicationService.ts +48 -5
  37. package/Server/Services/ServerlessFunctionService.ts +48 -5
  38. package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +426 -0
  39. package/Server/Services/ServiceLevelObjectiveService.ts +39 -0
  40. package/Server/Services/StatusPageService.ts +95 -36
  41. package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +18 -1
  42. package/Server/Services/StatusPageSubscriberService.ts +3 -0
  43. package/Server/Types/Database/JSONColumnQuery.ts +621 -0
  44. package/Server/Types/Database/Permissions/PublicPermission.ts +9 -2
  45. package/Server/Types/Database/QueryHelper.ts +17 -32
  46. package/Server/Utils/Database/TruncateColumnValue.ts +84 -0
  47. package/Tests/App/Dashboard/UsersTableGroupsByPerson.test.tsx +448 -0
  48. package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +704 -0
  49. package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +303 -0
  50. package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +350 -0
  51. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyHeader.test.ts +362 -0
  52. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +687 -0
  53. package/Tests/Server/Services/BillingService.test.ts +78 -3
  54. package/Tests/Server/Services/BillingServiceChangePlanCancel.test.ts +645 -0
  55. package/Tests/Server/Services/BillingServiceTrialPlanChange.test.ts +1005 -0
  56. package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +503 -0
  57. package/Tests/Server/Services/HostIpAddressesColumnWidth.test.ts +198 -0
  58. package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +510 -0
  59. package/Tests/Server/Services/InventoryResourceNameColumnWidth.test.ts +500 -0
  60. package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +586 -0
  61. package/Tests/Server/Services/InventoryUpsertTruncationGuard.test.ts +1242 -0
  62. package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +650 -0
  63. package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +1 -0
  64. package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +387 -0
  65. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +770 -0
  66. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +130 -0
  67. package/Tests/Server/Services/SloMonitorLabelRuleHooks.test.ts +352 -0
  68. package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +29 -13
  69. package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +69 -8
  70. package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +593 -0
  71. package/Tests/Server/Types/Database/Permissions/PublicPermission.test.ts +495 -0
  72. package/Tests/Server/Types/Database/QueryUtil.test.ts +113 -0
  73. package/Tests/Server/Utils/Database/TruncateColumnValue.test.ts +179 -0
  74. package/Tests/Types/Events/Recurring.test.ts +157 -0
  75. package/Tests/UI/Components/ModelTable/ModelTableGroupsProjectUsers.test.tsx +375 -0
  76. package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +196 -0
  77. package/Tests/UI/Utils/ProjectUsersModelAPI.test.ts +739 -0
  78. package/Tests/UI/Utils/TeamMembersByUser.test.ts +633 -0
  79. package/Tests/Utils/StatusPage/ReportPeriod.test.ts +218 -0
  80. package/Tests/Utils/Telemetry/HostIpAddresses.test.ts +291 -0
  81. package/Types/CustomField/CustomFieldDefinition.ts +24 -0
  82. package/Types/Date.ts +112 -0
  83. package/Types/Events/Recurring.ts +99 -1
  84. package/Types/StatusPage/StatusPageReport.ts +11 -0
  85. package/Types/StatusPage/StatusPageReportPeriodType.ts +21 -0
  86. package/UI/Components/ModelTable/CustomFieldColumns.tsx +2 -6
  87. package/UI/Components/ModelTable/useCustomFieldColumns.ts +21 -2
  88. package/UI/Utils/ModelAPI/ProjectUsersModelAPI.ts +305 -0
  89. package/UI/Utils/TeamMembersByUser.ts +302 -0
  90. package/Utils/StatusPage/ReportPeriod.ts +236 -0
  91. package/Utils/Telemetry/HostIpAddresses.ts +98 -0
  92. package/build/dist/Models/DatabaseModels/Host.js +12 -4
  93. package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
  94. package/build/dist/Models/DatabaseModels/KubernetesContainer.js +4 -4
  95. package/build/dist/Models/DatabaseModels/KubernetesContainer.js.map +1 -1
  96. package/build/dist/Models/DatabaseModels/KubernetesResource.js +7 -7
  97. package/build/dist/Models/DatabaseModels/KubernetesResource.js.map +1 -1
  98. package/build/dist/Models/DatabaseModels/PodmanResource.js +6 -6
  99. package/build/dist/Models/DatabaseModels/PodmanResource.js.map +1 -1
  100. package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js +87 -0
  101. package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js.map +1 -1
  102. package/build/dist/Models/DatabaseModels/StatusPage.js +98 -0
  103. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  104. package/build/dist/Server/API/TeamMemberAPI.js +57 -2
  105. package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
  106. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js +45 -0
  107. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js.map +1 -0
  108. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js +31 -0
  109. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js.map +1 -0
  110. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js +90 -0
  111. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js.map +1 -0
  112. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js +14 -0
  113. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js.map +1 -0
  114. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  115. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  116. package/build/dist/Server/Middleware/ProjectAuthorization.js +59 -4
  117. package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
  118. package/build/dist/Server/Services/BillingService.js +466 -36
  119. package/build/dist/Server/Services/BillingService.js.map +1 -1
  120. package/build/dist/Server/Services/CephClusterService.js +42 -5
  121. package/build/dist/Server/Services/CephClusterService.js.map +1 -1
  122. package/build/dist/Server/Services/CephResourceService.js +28 -3
  123. package/build/dist/Server/Services/CephResourceService.js.map +1 -1
  124. package/build/dist/Server/Services/CloudResourceService.js +42 -5
  125. package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
  126. package/build/dist/Server/Services/DatabaseService.js +69 -2
  127. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  128. package/build/dist/Server/Services/DockerHostService.js +42 -5
  129. package/build/dist/Server/Services/DockerHostService.js.map +1 -1
  130. package/build/dist/Server/Services/DockerResourceService.js +34 -4
  131. package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
  132. package/build/dist/Server/Services/DockerSwarmClusterService.js +43 -5
  133. package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
  134. package/build/dist/Server/Services/DockerSwarmResourceService.js +28 -3
  135. package/build/dist/Server/Services/DockerSwarmResourceService.js.map +1 -1
  136. package/build/dist/Server/Services/HostService.js +43 -5
  137. package/build/dist/Server/Services/HostService.js.map +1 -1
  138. package/build/dist/Server/Services/IoTDeviceService.js +1 -8
  139. package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
  140. package/build/dist/Server/Services/IoTFleetService.js +42 -5
  141. package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
  142. package/build/dist/Server/Services/KubernetesClusterService.js +43 -5
  143. package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
  144. package/build/dist/Server/Services/KubernetesContainerService.js +29 -3
  145. package/build/dist/Server/Services/KubernetesContainerService.js.map +1 -1
  146. package/build/dist/Server/Services/KubernetesResourceService.js +32 -4
  147. package/build/dist/Server/Services/KubernetesResourceService.js.map +1 -1
  148. package/build/dist/Server/Services/LabelService.js +92 -0
  149. package/build/dist/Server/Services/LabelService.js.map +1 -1
  150. package/build/dist/Server/Services/MonitorService.js +47 -0
  151. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  152. package/build/dist/Server/Services/PodmanHostService.js +42 -5
  153. package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
  154. package/build/dist/Server/Services/PodmanResourceService.js +34 -4
  155. package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
  156. package/build/dist/Server/Services/ProjectService.js +135 -21
  157. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  158. package/build/dist/Server/Services/ProxmoxClusterService.js +42 -5
  159. package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
  160. package/build/dist/Server/Services/ProxmoxResourceService.js +28 -3
  161. package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
  162. package/build/dist/Server/Services/RumApplicationService.js +43 -5
  163. package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
  164. package/build/dist/Server/Services/ServerlessFunctionService.js +43 -5
  165. package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
  166. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +347 -0
  167. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -0
  168. package/build/dist/Server/Services/ServiceLevelObjectiveService.js +33 -1
  169. package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
  170. package/build/dist/Server/Services/StatusPageService.js +69 -46
  171. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  172. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +17 -1
  173. package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
  174. package/build/dist/Server/Services/StatusPageSubscriberService.js +3 -0
  175. package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
  176. package/build/dist/Server/Types/Database/JSONColumnQuery.js +402 -0
  177. package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -0
  178. package/build/dist/Server/Types/Database/Permissions/PublicPermission.js +9 -2
  179. package/build/dist/Server/Types/Database/Permissions/PublicPermission.js.map +1 -1
  180. package/build/dist/Server/Types/Database/QueryHelper.js +17 -27
  181. package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
  182. package/build/dist/Server/Utils/Database/TruncateColumnValue.js +32 -0
  183. package/build/dist/Server/Utils/Database/TruncateColumnValue.js.map +1 -0
  184. package/build/dist/Types/CustomField/CustomFieldDefinition.js +2 -0
  185. package/build/dist/Types/CustomField/CustomFieldDefinition.js.map +1 -0
  186. package/build/dist/Types/Date.js +68 -0
  187. package/build/dist/Types/Date.js.map +1 -1
  188. package/build/dist/Types/Events/Recurring.js +63 -0
  189. package/build/dist/Types/Events/Recurring.js.map +1 -1
  190. package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js +22 -0
  191. package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js.map +1 -0
  192. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
  193. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +18 -2
  194. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
  195. package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js +213 -0
  196. package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js.map +1 -0
  197. package/build/dist/UI/Utils/TeamMembersByUser.js +195 -0
  198. package/build/dist/UI/Utils/TeamMembersByUser.js.map +1 -0
  199. package/build/dist/Utils/StatusPage/ReportPeriod.js +131 -0
  200. package/build/dist/Utils/StatusPage/ReportPeriod.js.map +1 -0
  201. package/build/dist/Utils/Telemetry/HostIpAddresses.js +82 -0
  202. package/build/dist/Utils/Telemetry/HostIpAddresses.js.map +1 -0
  203. package/jest.config.json +2 -0
  204. package/package.json +1 -1
  205. package/tsconfig.json +12 -1
@@ -0,0 +1,500 @@
1
+ import BaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
2
+ import KubernetesContainer from "../../../Models/DatabaseModels/KubernetesContainer";
3
+ import KubernetesResource from "../../../Models/DatabaseModels/KubernetesResource";
4
+ import PodmanResource from "../../../Models/DatabaseModels/PodmanResource";
5
+ import { MigrationName1776504277320 } from "../../../Server/Infrastructure/Postgres/SchemaMigrations/1776504277320-MigrationName";
6
+ import { MigrationName1777571961028 } from "../../../Server/Infrastructure/Postgres/SchemaMigrations/1777571961028-MigrationName";
7
+ import { MigrationName1781750000000 } from "../../../Server/Infrastructure/Postgres/SchemaMigrations/1781750000000-MigrationName";
8
+ import { WidenInventoryResourceNameColumns1785930000000 } from "../../../Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns";
9
+ import SchemaMigrations from "../../../Server/Infrastructure/Postgres/SchemaMigrations/Index";
10
+ import ColumnType from "../../../Types/Database/ColumnType";
11
+ import ColumnLength, {
12
+ getMaxLengthFromTableColumnType,
13
+ } from "../../../Types/Database/ColumnLength";
14
+ import TableColumnType from "../../../Types/Database/TableColumnType";
15
+ import { TableColumnMetadata } from "../../../Types/Database/TableColumn";
16
+ import {
17
+ MigrationInterface,
18
+ QueryRunner,
19
+ getMetadataArgsStorage,
20
+ } from "typeorm";
21
+ import type { ColumnMetadataArgs } from "typeorm/metadata-args/ColumnMetadataArgs";
22
+ import { describe, expect, test } from "@jest/globals";
23
+
24
+ /*
25
+ * Follow-up to issue #3006, same overflow class but on the bulk-upsert
26
+ * inventory paths, where the blast radius is much worse. Those services
27
+ * hand-build `INSERT ... ON CONFLICT` strings and flush them through
28
+ * `manager.query()` in chunks of 500 rows, so ONE value past
29
+ * varchar(100) raises 22001 and aborts the ENTIRE multi-row INSERT —
30
+ * up to 500 inventory rows dropped per flush, silently.
31
+ *
32
+ * The columns that actually overflow in the field:
33
+ * - PodmanResource.name / .imageName — full image references
34
+ * (registry host + org + repo + tag) run well past 100 chars.
35
+ * - KubernetesResource.name, KubernetesContainer.podName — Kubernetes
36
+ * DNS-subdomain names go to 253 chars, and generated pod names
37
+ * (deployment + replicaset hash + pod hash) routinely clear 100.
38
+ * - KubernetesResource.controllerDeploymentName — written from the
39
+ * `k8s.deployment.name` resource attribute, same bound.
40
+ *
41
+ * Three things have to line up for the fix to hold, and a regression in
42
+ * any one of them silently reintroduces the bug:
43
+ * 1. the entities declare varchar(500) (LongText) columns,
44
+ * 2. the migration actually widens the existing columns in Postgres,
45
+ * and its down() can narrow back without exploding on rows written
46
+ * since, and
47
+ * 3. the migration is registered in SchemaMigrations/Index.ts — an
48
+ * unregistered migration is dead code that never runs on boot.
49
+ *
50
+ * Pure metadata/mocks — no Postgres connection anywhere.
51
+ */
52
+
53
+ // Kubernetes caps DNS-subdomain names (pods, deployments) at 253 chars.
54
+ const KUBERNETES_NAME_CEILING: number = 253;
55
+
56
+ interface WidenedColumn {
57
+ label: string;
58
+ table: string;
59
+ target: unknown;
60
+ newInstance: () => BaseModel;
61
+ /** The migration whose DDL first declared this column at varchar(100). */
62
+ createdBy: () => MigrationInterface;
63
+ propertyName: string;
64
+ nullable: boolean;
65
+ }
66
+
67
+ const WIDENED_COLUMNS: Array<WidenedColumn> = [
68
+ {
69
+ label: "PodmanResource.name",
70
+ table: "PodmanResource",
71
+ target: PodmanResource,
72
+ newInstance: (): BaseModel => {
73
+ return new PodmanResource();
74
+ },
75
+ createdBy: (): MigrationInterface => {
76
+ return new MigrationName1781750000000();
77
+ },
78
+ propertyName: "name",
79
+ nullable: false,
80
+ },
81
+ {
82
+ label: "PodmanResource.imageName",
83
+ table: "PodmanResource",
84
+ target: PodmanResource,
85
+ newInstance: (): BaseModel => {
86
+ return new PodmanResource();
87
+ },
88
+ createdBy: (): MigrationInterface => {
89
+ return new MigrationName1781750000000();
90
+ },
91
+ propertyName: "imageName",
92
+ nullable: true,
93
+ },
94
+ {
95
+ label: "KubernetesResource.name",
96
+ table: "KubernetesResource",
97
+ target: KubernetesResource,
98
+ newInstance: (): BaseModel => {
99
+ return new KubernetesResource();
100
+ },
101
+ createdBy: (): MigrationInterface => {
102
+ return new MigrationName1776504277320();
103
+ },
104
+ propertyName: "name",
105
+ nullable: false,
106
+ },
107
+ {
108
+ label: "KubernetesResource.controllerDeploymentName",
109
+ table: "KubernetesResource",
110
+ target: KubernetesResource,
111
+ newInstance: (): BaseModel => {
112
+ return new KubernetesResource();
113
+ },
114
+ createdBy: (): MigrationInterface => {
115
+ return new MigrationName1777571961028();
116
+ },
117
+ propertyName: "controllerDeploymentName",
118
+ nullable: true,
119
+ },
120
+ {
121
+ label: "KubernetesContainer.podName",
122
+ table: "KubernetesContainer",
123
+ target: KubernetesContainer,
124
+ newInstance: (): BaseModel => {
125
+ return new KubernetesContainer();
126
+ },
127
+ createdBy: (): MigrationInterface => {
128
+ return new MigrationName1777571961028();
129
+ },
130
+ propertyName: "podName",
131
+ nullable: false,
132
+ },
133
+ ];
134
+
135
+ function columnArgs(column: WidenedColumn): ColumnMetadataArgs {
136
+ const args: ColumnMetadataArgs | undefined = getMetadataArgsStorage()
137
+ .columns.filter((candidate: ColumnMetadataArgs) => {
138
+ return candidate.target === column.target;
139
+ })
140
+ .find((candidate: ColumnMetadataArgs) => {
141
+ return candidate.propertyName === column.propertyName;
142
+ });
143
+
144
+ if (!args) {
145
+ throw new Error(`${column.label} has no TypeORM @Column metadata`);
146
+ }
147
+
148
+ return args;
149
+ }
150
+
151
+ function makeQueryRunner(): { runner: QueryRunner; query: jest.Mock } {
152
+ const query: jest.Mock = jest.fn().mockResolvedValue(undefined);
153
+ return { runner: { query } as unknown as QueryRunner, query };
154
+ }
155
+
156
+ function executedSql(query: jest.Mock): Array<string> {
157
+ return query.mock.calls.map((call: Array<unknown>) => {
158
+ return String(call[0]);
159
+ });
160
+ }
161
+
162
+ describe("widened inventory entity declarations", () => {
163
+ test.each(WIDENED_COLUMNS)(
164
+ "$label is a LongText varchar(500) column",
165
+ (column: WidenedColumn) => {
166
+ const args: ColumnMetadataArgs = columnArgs(column);
167
+ expect(args.options.type).toBe(ColumnType.LongText);
168
+ expect(args.options.length).toBe(ColumnLength.LongText);
169
+ expect(ColumnLength.LongText).toBe(500);
170
+ },
171
+ );
172
+
173
+ test.each(WIDENED_COLUMNS)(
174
+ "$label keeps its original nullability, so the widening is type-only",
175
+ (column: WidenedColumn) => {
176
+ expect(columnArgs(column).options.nullable).toBe(column.nullable);
177
+ },
178
+ );
179
+
180
+ test.each(WIDENED_COLUMNS)(
181
+ "$label reports TableColumnType.LongText",
182
+ (column: WidenedColumn) => {
183
+ const metadata: TableColumnMetadata = column
184
+ .newInstance()
185
+ .getTableColumnMetadata(column.propertyName);
186
+ expect(metadata.type).toBe(TableColumnType.LongText);
187
+ },
188
+ );
189
+
190
+ test.each(WIDENED_COLUMNS)(
191
+ "$label clears the 253-char Kubernetes name ceiling",
192
+ (column: WidenedColumn) => {
193
+ const metadata: TableColumnMetadata = column
194
+ .newInstance()
195
+ .getTableColumnMetadata(column.propertyName);
196
+ const maxLength: number | undefined = getMaxLengthFromTableColumnType(
197
+ metadata.type,
198
+ );
199
+
200
+ expect(maxLength).toBe(500);
201
+ expect(maxLength as number).toBeGreaterThanOrEqual(
202
+ KUBERNETES_NAME_CEILING,
203
+ );
204
+ },
205
+ );
206
+
207
+ test.each(WIDENED_COLUMNS)(
208
+ "$label stays readable on relation queries",
209
+ (column: WidenedColumn) => {
210
+ const metadata: TableColumnMetadata = column
211
+ .newInstance()
212
+ .getTableColumnMetadata(column.propertyName);
213
+ expect(metadata.canReadOnRelationQuery).toBe(true);
214
+ },
215
+ );
216
+
217
+ test("these columns would all have been rejected under the old ShortText declaration", () => {
218
+ /*
219
+ * Guards the premise of the fix: 100 really was the bound that a
220
+ * 253-char pod name and a long GHCR image reference blew through.
221
+ */
222
+ expect(getMaxLengthFromTableColumnType(TableColumnType.ShortText)).toBe(
223
+ 100,
224
+ );
225
+ expect(ColumnLength.ShortText).toBeLessThan(KUBERNETES_NAME_CEILING);
226
+ });
227
+ });
228
+
229
+ describe("WidenInventoryResourceNameColumns1785930000000 SQL contract", () => {
230
+ const migration: WidenInventoryResourceNameColumns1785930000000 =
231
+ new WidenInventoryResourceNameColumns1785930000000();
232
+
233
+ test("up() widens exactly the five offending columns to varchar(500)", async () => {
234
+ const { runner, query } = makeQueryRunner();
235
+ await migration.up(runner);
236
+
237
+ expect(executedSql(query)).toEqual([
238
+ `ALTER TABLE "PodmanResource" ALTER COLUMN "name" TYPE character varying(500)`,
239
+ `ALTER TABLE "PodmanResource" ALTER COLUMN "imageName" TYPE character varying(500)`,
240
+ `ALTER TABLE "KubernetesResource" ALTER COLUMN "name" TYPE character varying(500)`,
241
+ `ALTER TABLE "KubernetesResource" ALTER COLUMN "controllerDeploymentName" TYPE character varying(500)`,
242
+ `ALTER TABLE "KubernetesContainer" ALTER COLUMN "podName" TYPE character varying(500)`,
243
+ ]);
244
+ });
245
+
246
+ test("up() only ever widens — it never drops or deletes", async () => {
247
+ const { runner, query } = makeQueryRunner();
248
+ await migration.up(runner);
249
+
250
+ for (const sql of executedSql(query)) {
251
+ expect(sql).toContain("ALTER COLUMN");
252
+ expect(sql).not.toContain("DROP");
253
+ expect(sql).not.toContain("DELETE");
254
+ }
255
+ });
256
+
257
+ test("up() touches no table outside the three inventory tables", async () => {
258
+ const { runner, query } = makeQueryRunner();
259
+ await migration.up(runner);
260
+
261
+ const tables: Set<string> = new Set(
262
+ WIDENED_COLUMNS.map((column: WidenedColumn) => {
263
+ return column.table;
264
+ }),
265
+ );
266
+
267
+ for (const sql of executedSql(query)) {
268
+ const matched: Array<string> = Array.from(tables).filter(
269
+ (table: string) => {
270
+ return sql.includes(`"${table}"`);
271
+ },
272
+ );
273
+ expect(matched).toHaveLength(1);
274
+ }
275
+ });
276
+
277
+ test("down() clips oversized rows BEFORE narrowing, so the revert cannot fail", async () => {
278
+ const { runner, query } = makeQueryRunner();
279
+ await migration.down(runner);
280
+
281
+ const statements: Array<string> = executedSql(query);
282
+ const firstNarrowIndex: number = statements.findIndex((sql: string) => {
283
+ return sql.includes("character varying(100)");
284
+ });
285
+ expect(firstNarrowIndex).toBeGreaterThan(0);
286
+
287
+ for (const column of WIDENED_COLUMNS) {
288
+ const clipIndex: number = statements.findIndex((sql: string) => {
289
+ return (
290
+ sql.startsWith(`UPDATE "${column.table}"`) &&
291
+ sql.includes(`SET "${column.propertyName}" = LEFT(`) &&
292
+ sql.includes("100")
293
+ );
294
+ });
295
+
296
+ expect(clipIndex).toBeGreaterThanOrEqual(0);
297
+ expect(clipIndex).toBeLessThan(firstNarrowIndex);
298
+ }
299
+ });
300
+
301
+ test("down() resolves unique-index collisions before it clips", async () => {
302
+ /*
303
+ * "name" / "podName" are part of UNIQUE indexes. Two rows whose
304
+ * names differ only past character 100 would collide once clipped,
305
+ * so the dedupe DELETEs have to land before the clipping UPDATEs.
306
+ */
307
+ const { runner, query } = makeQueryRunner();
308
+ await migration.down(runner);
309
+
310
+ const statements: Array<string> = executedSql(query);
311
+ const deleteIndexes: Array<number> = [];
312
+ statements.forEach((sql: string, index: number) => {
313
+ if (sql.startsWith("DELETE FROM")) {
314
+ deleteIndexes.push(index);
315
+ }
316
+ });
317
+ const firstClipIndex: number = statements.findIndex((sql: string) => {
318
+ return sql.startsWith("UPDATE");
319
+ });
320
+
321
+ // One dedupe per uniquely-indexed table.
322
+ expect(deleteIndexes).toHaveLength(3);
323
+ expect(firstClipIndex).toBeGreaterThanOrEqual(0);
324
+ for (const deleteIndex of deleteIndexes) {
325
+ expect(deleteIndex).toBeLessThan(firstClipIndex);
326
+ expect(statements[deleteIndex]).toContain("LEFT(");
327
+ }
328
+ });
329
+
330
+ test("down() narrows every widened column back to varchar(100)", async () => {
331
+ const { runner, query } = makeQueryRunner();
332
+ await migration.down(runner);
333
+
334
+ const statements: Array<string> = executedSql(query);
335
+ for (const column of WIDENED_COLUMNS) {
336
+ expect(statements).toContain(
337
+ `ALTER TABLE "${column.table}" ALTER COLUMN "${column.propertyName}" TYPE character varying(100)`,
338
+ );
339
+ }
340
+ });
341
+
342
+ test.each(WIDENED_COLUMNS)(
343
+ "down() restores $label to exactly the width its creating migration used",
344
+ async (column: WidenedColumn) => {
345
+ const { runner: createRunner, query: createQuery } = makeQueryRunner();
346
+ await column.createdBy().up(createRunner);
347
+
348
+ /*
349
+ * Scope the search to the statement that declares THIS table. A
350
+ * bare substring search for `"name" character varying(100)` is
351
+ * satisfied by any of the dozens of unrelated tables in the same
352
+ * migration, which makes the precondition unfalsifiable.
353
+ *
354
+ * The original DDL declared varchar(100) — the revert must match,
355
+ * or a rollback leaves the schema drifted from the entities.
356
+ */
357
+ const declaringStatement: string | undefined = executedSql(
358
+ createQuery,
359
+ ).find((sql: string) => {
360
+ return (
361
+ (sql.startsWith(`CREATE TABLE "${column.table}" (`) ||
362
+ sql.startsWith(`ALTER TABLE "${column.table}" ADD `)) &&
363
+ sql.includes(`"${column.propertyName}" character varying(100)`)
364
+ );
365
+ });
366
+
367
+ expect(declaringStatement).toBeDefined();
368
+
369
+ const { runner: downRunner, query: downQuery } = makeQueryRunner();
370
+ await migration.down(downRunner);
371
+
372
+ expect(executedSql(downQuery)).toContain(
373
+ `ALTER TABLE "${column.table}" ALTER COLUMN "${column.propertyName}" TYPE character varying(100)`,
374
+ );
375
+ },
376
+ );
377
+
378
+ test("up() and down() touch exactly the same set of columns", async () => {
379
+ const { runner: upRunner, query: upQuery } = makeQueryRunner();
380
+ await migration.up(upRunner);
381
+ const { runner: downRunner, query: downQuery } = makeQueryRunner();
382
+ await migration.down(downRunner);
383
+
384
+ const alteredColumns: (statements: Array<string>) => Array<string> = (
385
+ statements: Array<string>,
386
+ ): Array<string> => {
387
+ return statements
388
+ .filter((sql: string) => {
389
+ return sql.startsWith("ALTER TABLE");
390
+ })
391
+ .map((sql: string) => {
392
+ const match: RegExpMatchArray | null = sql.match(
393
+ /ALTER TABLE "(\w+)" ALTER COLUMN "(\w+)"/,
394
+ );
395
+ return match ? `${match[1]}.${match[2]}` : sql;
396
+ })
397
+ .sort();
398
+ };
399
+
400
+ expect(alteredColumns(executedSql(downQuery))).toEqual(
401
+ alteredColumns(executedSql(upQuery)),
402
+ );
403
+ });
404
+
405
+ test("the class name carries its own timestamp, matching the file name", () => {
406
+ expect(migration.name).toBe(
407
+ "WidenInventoryResourceNameColumns1785930000000",
408
+ );
409
+ });
410
+ });
411
+
412
+ describe("migration and entity declarations agree", () => {
413
+ /*
414
+ * The failure this catches: widening the entity but not the migration
415
+ * (or vice versa). Postgres would keep rejecting long values while the
416
+ * model claims they fit, and CI's schema-drift check is the only other
417
+ * thing standing between that and production.
418
+ */
419
+ const migration: WidenInventoryResourceNameColumns1785930000000 =
420
+ new WidenInventoryResourceNameColumns1785930000000();
421
+
422
+ test("the migration widens exactly the columns the entities declare as LongText", async () => {
423
+ const { runner, query } = makeQueryRunner();
424
+ await migration.up(runner);
425
+
426
+ const widenedByMigration: Array<string> = executedSql(query)
427
+ .map((sql: string) => {
428
+ const match: RegExpMatchArray | null = sql.match(
429
+ /ALTER TABLE "(\w+)" ALTER COLUMN "(\w+)" TYPE character varying\(500\)/,
430
+ );
431
+ return match ? `${match[1]}.${match[2]}` : "";
432
+ })
433
+ .filter((entry: string) => {
434
+ return entry.length > 0;
435
+ })
436
+ .sort();
437
+
438
+ const declaredByEntities: Array<string> = WIDENED_COLUMNS.map(
439
+ (column: WidenedColumn) => {
440
+ return `${column.table}.${column.propertyName}`;
441
+ },
442
+ ).sort();
443
+
444
+ expect(widenedByMigration).toEqual(declaredByEntities);
445
+ });
446
+
447
+ test("every column the migration widens really is varchar(500) on the entity", async () => {
448
+ const { runner, query } = makeQueryRunner();
449
+ await migration.up(runner);
450
+
451
+ for (const column of WIDENED_COLUMNS) {
452
+ const args: ColumnMetadataArgs = columnArgs(column);
453
+ expect(args.options.length).toBe(500);
454
+ expect(executedSql(query)).toContain(
455
+ `ALTER TABLE "${column.table}" ALTER COLUMN "${column.propertyName}" TYPE character varying(${args.options.length})`,
456
+ );
457
+ }
458
+ });
459
+ });
460
+
461
+ describe("WidenInventoryResourceNameColumns1785930000000 registration", () => {
462
+ test("is registered in SchemaMigrations/Index.ts", () => {
463
+ expect(SchemaMigrations).toContain(
464
+ WidenInventoryResourceNameColumns1785930000000,
465
+ );
466
+ });
467
+
468
+ test("is registered exactly once", () => {
469
+ const occurrences: number = SchemaMigrations.filter(
470
+ (migration: unknown) => {
471
+ return migration === WidenInventoryResourceNameColumns1785930000000;
472
+ },
473
+ ).length;
474
+ expect(occurrences).toBe(1);
475
+ });
476
+
477
+ test.each([
478
+ ["KubernetesResource", MigrationName1776504277320],
479
+ ["KubernetesContainer", MigrationName1777571961028],
480
+ ["PodmanResource", MigrationName1781750000000],
481
+ ])(
482
+ "runs after the migration that created %s",
483
+ (_table: string, createMigration: unknown) => {
484
+ /*
485
+ * TypeORM executes registered migrations in array order. Widening
486
+ * a column on a table that does not exist yet fails, so every
487
+ * CREATE TABLE must come first.
488
+ */
489
+ const createIndex: number = SchemaMigrations.indexOf(
490
+ createMigration as never,
491
+ );
492
+ const widenIndex: number = SchemaMigrations.indexOf(
493
+ WidenInventoryResourceNameColumns1785930000000,
494
+ );
495
+
496
+ expect(createIndex).toBeGreaterThanOrEqual(0);
497
+ expect(widenIndex).toBeGreaterThan(createIndex);
498
+ },
499
+ );
500
+ });