@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.
- package/Models/DatabaseModels/Host.ts +12 -4
- package/Models/DatabaseModels/KubernetesContainer.ts +5 -4
- package/Models/DatabaseModels/KubernetesResource.ts +8 -7
- package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.ts +12 -2
- 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/LocalMigrationGenerationDataSource.ts +12 -2
- 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/LlmProviderService.ts +69 -9
- 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/RunnerService.ts +1 -7
- 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/Server/Utils/Monitor/Criteria/CompareCriteria.ts +12 -0
- package/Tests/App/Dashboard/RunnerInstallInstructions.test.tsx +119 -0
- package/Tests/App/Dashboard/RunnerStatus.test.tsx +274 -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/LlmProviderUsableByProject.test.ts +293 -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/AI/ToolArgsExtractors.test.ts +141 -0
- package/Tests/Server/Utils/AI/Toolbox/WidgetBuilder.test.ts +205 -0
- package/Tests/Server/Utils/Database/TruncateColumnValue.test.ts +179 -0
- package/Tests/Server/Utils/Monitor/Criteria/CompareCriteria.test.ts +897 -0
- package/Tests/Types/Events/Recurring.test.ts +157 -0
- package/Tests/Types/Runner/RunnerLiveStatus.test.ts +320 -0
- package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +285 -0
- package/Tests/UI/Components/Detail/EntityFields.test.tsx +166 -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/Runbook/RunbookStep.ts +15 -0
- package/Types/Runner/RunnerLiveStatus.ts +114 -0
- package/Types/StatusPage/StatusPageReport.ts +11 -0
- package/Types/StatusPage/StatusPageReportPeriodType.ts +21 -0
- package/UI/Components/Detail/Detail.tsx +155 -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/OnCallDutyPolicyScheduleLayer.js +16 -4
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.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/LocalMigrationGenerationDataSource.js +11 -1
- package/build/dist/Server/Infrastructure/Postgres/LocalMigrationGenerationDataSource.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/LlmProviderService.js +61 -8
- package/build/dist/Server/Services/LlmProviderService.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/RunnerService.js +1 -6
- package/build/dist/Server/Services/RunnerService.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/Server/Utils/Monitor/Criteria/CompareCriteria.js +11 -0
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js.map +1 -1
- 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/Runbook/RunbookStep.js.map +1 -1
- package/build/dist/Types/Runner/RunnerLiveStatus.js +69 -0
- package/build/dist/Types/Runner/RunnerLiveStatus.js.map +1 -0
- package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js +22 -0
- package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js.map +1 -0
- package/build/dist/UI/Components/Detail/Detail.js +97 -0
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- 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,1242 @@
|
|
|
1
|
+
import CephResourceService, {
|
|
2
|
+
ParsedCephResource,
|
|
3
|
+
} from "../../../Server/Services/CephResourceService";
|
|
4
|
+
import DockerResourceService, {
|
|
5
|
+
ParsedDockerContainer,
|
|
6
|
+
ParsedDockerResource,
|
|
7
|
+
} from "../../../Server/Services/DockerResourceService";
|
|
8
|
+
import DockerSwarmResourceService, {
|
|
9
|
+
ParsedDockerSwarmResource,
|
|
10
|
+
} from "../../../Server/Services/DockerSwarmResourceService";
|
|
11
|
+
import KubernetesContainerService, {
|
|
12
|
+
ContainerLatestMetric,
|
|
13
|
+
ParsedKubernetesContainer,
|
|
14
|
+
} from "../../../Server/Services/KubernetesContainerService";
|
|
15
|
+
import KubernetesResourceService, {
|
|
16
|
+
ParsedKubernetesResource,
|
|
17
|
+
ResourceLatestMetric,
|
|
18
|
+
} from "../../../Server/Services/KubernetesResourceService";
|
|
19
|
+
import PodmanResourceService, {
|
|
20
|
+
ParsedPodmanContainer,
|
|
21
|
+
ParsedPodmanResource,
|
|
22
|
+
} from "../../../Server/Services/PodmanResourceService";
|
|
23
|
+
import ProxmoxResourceService, {
|
|
24
|
+
ParsedProxmoxResource,
|
|
25
|
+
} from "../../../Server/Services/ProxmoxResourceService";
|
|
26
|
+
import logger from "../../../Server/Utils/Logger";
|
|
27
|
+
import ColumnLength from "../../../Types/Database/ColumnLength";
|
|
28
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
29
|
+
import { describe, expect, test } from "@jest/globals";
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
* Follow-up to issue #3006. The inventory services hand-build
|
|
33
|
+
* `INSERT ... ON CONFLICT` strings and flush them through
|
|
34
|
+
* `getRepository().manager.query()` in chunks of 500 rows, which
|
|
35
|
+
* bypasses the length validation DatabaseService applies to ordinary
|
|
36
|
+
* CRUD writes. A single value past its column limit raises 22001
|
|
37
|
+
* ("value too long for type character varying(N)") and aborts the WHOLE
|
|
38
|
+
* multi-row INSERT — one malformed resource silently drops up to 499
|
|
39
|
+
* healthy inventory rows with it.
|
|
40
|
+
*
|
|
41
|
+
* Widening the columns (see InventoryResourceNameColumnWidth.test.ts)
|
|
42
|
+
* removes the overflow that actually happens in the field; these clamps
|
|
43
|
+
* are the backstop that keeps ANY future overflow from taking the batch
|
|
44
|
+
* down with it. Every test here asserts the same two things: the
|
|
45
|
+
* oversized value is clipped to its column bound, and the healthy row
|
|
46
|
+
* riding in the same chunk still makes it into the statement.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
const PROJECT_ID: ObjectID = ObjectID.generate();
|
|
50
|
+
const PARENT_ID: ObjectID = ObjectID.generate();
|
|
51
|
+
const OBSERVED_AT: Date = new Date("2026-08-05T00:00:00.000Z");
|
|
52
|
+
|
|
53
|
+
const OVERSIZED: string = "x".repeat(900);
|
|
54
|
+
const CLAMPED_TO_SHORT_TEXT: string = OVERSIZED.substring(
|
|
55
|
+
0,
|
|
56
|
+
ColumnLength.ShortText,
|
|
57
|
+
);
|
|
58
|
+
const CLAMPED_TO_LONG_TEXT: string = OVERSIZED.substring(
|
|
59
|
+
0,
|
|
60
|
+
ColumnLength.LongText,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
type QueryCall = [string, Array<unknown>];
|
|
64
|
+
|
|
65
|
+
interface RepositoryHolder {
|
|
66
|
+
getRepository: () => unknown;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function mockQueryRunner(service: RepositoryHolder): jest.Mock {
|
|
70
|
+
const query: jest.Mock = jest.fn().mockResolvedValue([]);
|
|
71
|
+
jest
|
|
72
|
+
.spyOn(service, "getRepository")
|
|
73
|
+
.mockReturnValue({ manager: { query } } as any);
|
|
74
|
+
return query;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function paramsOf(query: jest.Mock, callIndex: number = 0): Array<unknown> {
|
|
78
|
+
const [, params] = query.mock.calls[callIndex] as QueryCall;
|
|
79
|
+
return params;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Every string parameter in the statement fits inside the widest
|
|
84
|
+
* bounded varchar this codebase uses, so nothing in the tuple can
|
|
85
|
+
* trigger 22001.
|
|
86
|
+
*/
|
|
87
|
+
function expectNoParamExceedsLongText(params: Array<unknown>): void {
|
|
88
|
+
for (const value of params) {
|
|
89
|
+
if (typeof value === "string") {
|
|
90
|
+
expect(value.length).toBeLessThanOrEqual(ColumnLength.LongText);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
afterEach(() => {
|
|
96
|
+
jest.restoreAllMocks();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("KubernetesResourceService — oversized values can't drop the chunk", () => {
|
|
100
|
+
function resource(
|
|
101
|
+
overrides: Partial<ParsedKubernetesResource> = {},
|
|
102
|
+
): ParsedKubernetesResource {
|
|
103
|
+
return {
|
|
104
|
+
kind: "Pod",
|
|
105
|
+
namespaceKey: "default",
|
|
106
|
+
name: "api-7d9f8c6b5d-x2k9p",
|
|
107
|
+
uid: "9f1c2b3a-4d5e-6f70-8192-a3b4c5d6e7f8",
|
|
108
|
+
phase: "Running",
|
|
109
|
+
isReady: true,
|
|
110
|
+
hasMemoryPressure: null,
|
|
111
|
+
hasDiskPressure: null,
|
|
112
|
+
hasPidPressure: null,
|
|
113
|
+
labels: null,
|
|
114
|
+
annotations: null,
|
|
115
|
+
ownerReferences: null,
|
|
116
|
+
spec: null,
|
|
117
|
+
containerCount: 1,
|
|
118
|
+
status: null,
|
|
119
|
+
lastSeenAt: OBSERVED_AT,
|
|
120
|
+
resourceCreationTimestamp: null,
|
|
121
|
+
...overrides,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
test("bulkUpsert clamps an oversized name and keeps its sibling row", async () => {
|
|
126
|
+
const query: jest.Mock = mockQueryRunner(KubernetesResourceService);
|
|
127
|
+
|
|
128
|
+
await KubernetesResourceService.bulkUpsert({
|
|
129
|
+
projectId: PROJECT_ID,
|
|
130
|
+
kubernetesClusterId: PARENT_ID,
|
|
131
|
+
resources: [resource({ name: OVERSIZED }), resource()],
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
expect(query).toHaveBeenCalledTimes(1);
|
|
135
|
+
const params: Array<unknown> = paramsOf(query);
|
|
136
|
+
|
|
137
|
+
expect(params).toContain(CLAMPED_TO_LONG_TEXT);
|
|
138
|
+
expect(params).not.toContain(OVERSIZED);
|
|
139
|
+
expect(params).toContain("api-7d9f8c6b5d-x2k9p");
|
|
140
|
+
expectNoParamExceedsLongText(params);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
test("bulkUpsert clamps the ShortText identity columns at 100", async () => {
|
|
144
|
+
const query: jest.Mock = mockQueryRunner(KubernetesResourceService);
|
|
145
|
+
|
|
146
|
+
await KubernetesResourceService.bulkUpsert({
|
|
147
|
+
projectId: PROJECT_ID,
|
|
148
|
+
kubernetesClusterId: PARENT_ID,
|
|
149
|
+
resources: [
|
|
150
|
+
resource({
|
|
151
|
+
kind: OVERSIZED,
|
|
152
|
+
namespaceKey: OVERSIZED,
|
|
153
|
+
uid: OVERSIZED,
|
|
154
|
+
phase: OVERSIZED,
|
|
155
|
+
}),
|
|
156
|
+
],
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const params: Array<unknown> = paramsOf(query);
|
|
160
|
+
const clamped: Array<unknown> = params.filter((value: unknown) => {
|
|
161
|
+
return value === CLAMPED_TO_SHORT_TEXT;
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
expect(clamped).toHaveLength(4);
|
|
165
|
+
expect(params).not.toContain(OVERSIZED);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("bulkUpdateLatestMetrics clamps controller names, which are assigned columns", async () => {
|
|
169
|
+
const query: jest.Mock = mockQueryRunner(KubernetesResourceService);
|
|
170
|
+
|
|
171
|
+
const metric: ResourceLatestMetric = {
|
|
172
|
+
kind: "Pod",
|
|
173
|
+
namespaceKey: "default",
|
|
174
|
+
name: "api-7d9f8c6b5d-x2k9p",
|
|
175
|
+
cpuPercent: 12,
|
|
176
|
+
memoryBytes: 1024,
|
|
177
|
+
memoryPercent: 4,
|
|
178
|
+
observedAt: OBSERVED_AT,
|
|
179
|
+
controllerDeploymentName: OVERSIZED,
|
|
180
|
+
controllerCronJobName: OVERSIZED,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
await KubernetesResourceService.bulkUpdateLatestMetrics({
|
|
184
|
+
projectId: PROJECT_ID,
|
|
185
|
+
kubernetesClusterId: PARENT_ID,
|
|
186
|
+
metrics: [metric],
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const params: Array<unknown> = paramsOf(query);
|
|
190
|
+
|
|
191
|
+
// controllerDeploymentName is LongText, controllerCronJobName is ShortText.
|
|
192
|
+
expect(params).toContain(CLAMPED_TO_LONG_TEXT);
|
|
193
|
+
expect(params).toContain(CLAMPED_TO_SHORT_TEXT);
|
|
194
|
+
expect(params).not.toContain(OVERSIZED);
|
|
195
|
+
expectNoParamExceedsLongText(params);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test("bulkUpdateLatestMetrics clamps identity the same way bulkUpsert does", async () => {
|
|
199
|
+
/*
|
|
200
|
+
* If the two paths clamped differently, the mirror UPDATE would
|
|
201
|
+
* silently miss the row the upsert just wrote under the clipped name.
|
|
202
|
+
*/
|
|
203
|
+
const upsertQuery: jest.Mock = mockQueryRunner(KubernetesResourceService);
|
|
204
|
+
await KubernetesResourceService.bulkUpsert({
|
|
205
|
+
projectId: PROJECT_ID,
|
|
206
|
+
kubernetesClusterId: PARENT_ID,
|
|
207
|
+
resources: [resource({ name: OVERSIZED })],
|
|
208
|
+
});
|
|
209
|
+
const upsertParams: Array<unknown> = paramsOf(upsertQuery);
|
|
210
|
+
|
|
211
|
+
jest.restoreAllMocks();
|
|
212
|
+
|
|
213
|
+
const metricQuery: jest.Mock = mockQueryRunner(KubernetesResourceService);
|
|
214
|
+
await KubernetesResourceService.bulkUpdateLatestMetrics({
|
|
215
|
+
projectId: PROJECT_ID,
|
|
216
|
+
kubernetesClusterId: PARENT_ID,
|
|
217
|
+
metrics: [
|
|
218
|
+
{
|
|
219
|
+
kind: "Pod",
|
|
220
|
+
namespaceKey: "default",
|
|
221
|
+
name: OVERSIZED,
|
|
222
|
+
cpuPercent: null,
|
|
223
|
+
memoryBytes: null,
|
|
224
|
+
memoryPercent: null,
|
|
225
|
+
observedAt: OBSERVED_AT,
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
});
|
|
229
|
+
const metricParams: Array<unknown> = paramsOf(metricQuery);
|
|
230
|
+
|
|
231
|
+
/*
|
|
232
|
+
* name is written at index 4 of the INSERT tuple, index 4 of the
|
|
233
|
+
* metric tuple (which is offset by the two leading scope params).
|
|
234
|
+
*/
|
|
235
|
+
expect(upsertParams[4]).toBe(CLAMPED_TO_LONG_TEXT);
|
|
236
|
+
expect(metricParams[4]).toBe(CLAMPED_TO_LONG_TEXT);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe("KubernetesContainerService — oversized values can't drop the chunk", () => {
|
|
241
|
+
function container(
|
|
242
|
+
overrides: Partial<ParsedKubernetesContainer> = {},
|
|
243
|
+
): ParsedKubernetesContainer {
|
|
244
|
+
return {
|
|
245
|
+
podNamespaceKey: "default",
|
|
246
|
+
podName: "api-7d9f8c6b5d-x2k9p",
|
|
247
|
+
name: "app",
|
|
248
|
+
image: "ghcr.io/acme/api:1.2.3",
|
|
249
|
+
state: "running",
|
|
250
|
+
reason: null,
|
|
251
|
+
isReady: true,
|
|
252
|
+
restartCount: 0,
|
|
253
|
+
memoryLimitBytes: 1024,
|
|
254
|
+
lastSeenAt: OBSERVED_AT,
|
|
255
|
+
...overrides,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
test("bulkUpsert clamps an oversized podName and keeps its sibling row", async () => {
|
|
260
|
+
const query: jest.Mock = mockQueryRunner(KubernetesContainerService);
|
|
261
|
+
|
|
262
|
+
await KubernetesContainerService.bulkUpsert({
|
|
263
|
+
projectId: PROJECT_ID,
|
|
264
|
+
kubernetesClusterId: PARENT_ID,
|
|
265
|
+
containers: [container({ podName: OVERSIZED }), container()],
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
expect(query).toHaveBeenCalledTimes(1);
|
|
269
|
+
const params: Array<unknown> = paramsOf(query);
|
|
270
|
+
|
|
271
|
+
expect(params).toContain(CLAMPED_TO_LONG_TEXT);
|
|
272
|
+
expect(params).not.toContain(OVERSIZED);
|
|
273
|
+
expect(params).toContain("api-7d9f8c6b5d-x2k9p");
|
|
274
|
+
expectNoParamExceedsLongText(params);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("bulkUpsert clamps image at 500 and the ShortText columns at 100", async () => {
|
|
278
|
+
const query: jest.Mock = mockQueryRunner(KubernetesContainerService);
|
|
279
|
+
|
|
280
|
+
await KubernetesContainerService.bulkUpsert({
|
|
281
|
+
projectId: PROJECT_ID,
|
|
282
|
+
kubernetesClusterId: PARENT_ID,
|
|
283
|
+
containers: [
|
|
284
|
+
container({
|
|
285
|
+
image: OVERSIZED,
|
|
286
|
+
name: OVERSIZED,
|
|
287
|
+
state: OVERSIZED,
|
|
288
|
+
reason: OVERSIZED,
|
|
289
|
+
}),
|
|
290
|
+
],
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
const params: Array<unknown> = paramsOf(query);
|
|
294
|
+
|
|
295
|
+
expect(params).toContain(CLAMPED_TO_LONG_TEXT);
|
|
296
|
+
expect(
|
|
297
|
+
params.filter((value: unknown) => {
|
|
298
|
+
return value === CLAMPED_TO_SHORT_TEXT;
|
|
299
|
+
}),
|
|
300
|
+
).toHaveLength(3);
|
|
301
|
+
expectNoParamExceedsLongText(params);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("bulkUpdateLatestMetrics clamps identity the same way bulkUpsert does", async () => {
|
|
305
|
+
const query: jest.Mock = mockQueryRunner(KubernetesContainerService);
|
|
306
|
+
|
|
307
|
+
const metric: ContainerLatestMetric = {
|
|
308
|
+
podNamespaceKey: "default",
|
|
309
|
+
podName: OVERSIZED,
|
|
310
|
+
name: "app",
|
|
311
|
+
cpuPercent: 1,
|
|
312
|
+
memoryBytes: 2,
|
|
313
|
+
observedAt: OBSERVED_AT,
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
await KubernetesContainerService.bulkUpdateLatestMetrics({
|
|
317
|
+
projectId: PROJECT_ID,
|
|
318
|
+
kubernetesClusterId: PARENT_ID,
|
|
319
|
+
metrics: [metric],
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const params: Array<unknown> = paramsOf(query);
|
|
323
|
+
expect(params).toContain(CLAMPED_TO_LONG_TEXT);
|
|
324
|
+
expect(params).not.toContain(OVERSIZED);
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
describe("DockerResourceService — oversized values can't drop the chunk", () => {
|
|
329
|
+
function dockerContainer(
|
|
330
|
+
overrides: Partial<ParsedDockerContainer> = {},
|
|
331
|
+
): ParsedDockerContainer {
|
|
332
|
+
return {
|
|
333
|
+
containerName: "api",
|
|
334
|
+
containerId: "0123456789ab",
|
|
335
|
+
imageName: "ghcr.io/acme/api:1.2.3",
|
|
336
|
+
state: "running",
|
|
337
|
+
cpuPercent: 5,
|
|
338
|
+
memoryBytes: 1024,
|
|
339
|
+
observedAt: OBSERVED_AT,
|
|
340
|
+
...overrides,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function dockerResource(
|
|
345
|
+
overrides: Partial<ParsedDockerResource> = {},
|
|
346
|
+
): ParsedDockerResource {
|
|
347
|
+
return {
|
|
348
|
+
kind: "Image",
|
|
349
|
+
name: "ghcr.io/acme/api:1.2.3",
|
|
350
|
+
containerId: null,
|
|
351
|
+
imageName: null,
|
|
352
|
+
state: null,
|
|
353
|
+
labels: null,
|
|
354
|
+
resourceCreationTimestamp: null,
|
|
355
|
+
lastSeenAt: OBSERVED_AT,
|
|
356
|
+
...overrides,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
test("bulkUpsertContainers clamps an oversized image reference", async () => {
|
|
361
|
+
const query: jest.Mock = mockQueryRunner(DockerResourceService);
|
|
362
|
+
|
|
363
|
+
await DockerResourceService.bulkUpsertContainers({
|
|
364
|
+
projectId: PROJECT_ID,
|
|
365
|
+
dockerHostId: PARENT_ID,
|
|
366
|
+
containers: [
|
|
367
|
+
dockerContainer({ imageName: OVERSIZED }),
|
|
368
|
+
dockerContainer(),
|
|
369
|
+
],
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
expect(query).toHaveBeenCalledTimes(1);
|
|
373
|
+
const params: Array<unknown> = paramsOf(query);
|
|
374
|
+
|
|
375
|
+
expect(params).toContain(CLAMPED_TO_LONG_TEXT);
|
|
376
|
+
expect(params).not.toContain(OVERSIZED);
|
|
377
|
+
expect(params).toContain("ghcr.io/acme/api:1.2.3");
|
|
378
|
+
expectNoParamExceedsLongText(params);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test("bulkUpsert clamps an oversized name and keeps its sibling row", async () => {
|
|
382
|
+
const query: jest.Mock = mockQueryRunner(DockerResourceService);
|
|
383
|
+
|
|
384
|
+
await DockerResourceService.bulkUpsert({
|
|
385
|
+
projectId: PROJECT_ID,
|
|
386
|
+
dockerHostId: PARENT_ID,
|
|
387
|
+
resources: [dockerResource({ name: OVERSIZED }), dockerResource()],
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
const params: Array<unknown> = paramsOf(query);
|
|
391
|
+
expect(params).toContain(CLAMPED_TO_LONG_TEXT);
|
|
392
|
+
expect(params).toContain("ghcr.io/acme/api:1.2.3");
|
|
393
|
+
expectNoParamExceedsLongText(params);
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
describe("PodmanResourceService — oversized values can't drop the chunk", () => {
|
|
398
|
+
function podmanContainer(
|
|
399
|
+
overrides: Partial<ParsedPodmanContainer> = {},
|
|
400
|
+
): ParsedPodmanContainer {
|
|
401
|
+
return {
|
|
402
|
+
containerName: "api",
|
|
403
|
+
containerId: "0123456789ab",
|
|
404
|
+
imageName: "ghcr.io/acme/api:1.2.3",
|
|
405
|
+
state: "running",
|
|
406
|
+
cpuPercent: 5,
|
|
407
|
+
memoryBytes: 1024,
|
|
408
|
+
observedAt: OBSERVED_AT,
|
|
409
|
+
...overrides,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function podmanResource(
|
|
414
|
+
overrides: Partial<ParsedPodmanResource> = {},
|
|
415
|
+
): ParsedPodmanResource {
|
|
416
|
+
return {
|
|
417
|
+
kind: "Image",
|
|
418
|
+
name: "ghcr.io/acme/api:1.2.3",
|
|
419
|
+
containerId: null,
|
|
420
|
+
imageName: null,
|
|
421
|
+
state: null,
|
|
422
|
+
labels: null,
|
|
423
|
+
resourceCreationTimestamp: null,
|
|
424
|
+
lastSeenAt: OBSERVED_AT,
|
|
425
|
+
...overrides,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
test("bulkUpsertContainers clamps an oversized image reference", async () => {
|
|
430
|
+
const query: jest.Mock = mockQueryRunner(PodmanResourceService);
|
|
431
|
+
|
|
432
|
+
await PodmanResourceService.bulkUpsertContainers({
|
|
433
|
+
projectId: PROJECT_ID,
|
|
434
|
+
podmanHostId: PARENT_ID,
|
|
435
|
+
containers: [
|
|
436
|
+
podmanContainer({ imageName: OVERSIZED }),
|
|
437
|
+
podmanContainer(),
|
|
438
|
+
],
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
expect(query).toHaveBeenCalledTimes(1);
|
|
442
|
+
const params: Array<unknown> = paramsOf(query);
|
|
443
|
+
|
|
444
|
+
expect(params).toContain(CLAMPED_TO_LONG_TEXT);
|
|
445
|
+
expect(params).not.toContain(OVERSIZED);
|
|
446
|
+
expect(params).toContain("ghcr.io/acme/api:1.2.3");
|
|
447
|
+
expectNoParamExceedsLongText(params);
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
test("bulkUpsert clamps an oversized Repository:Tag name from `image ls`", async () => {
|
|
451
|
+
const query: jest.Mock = mockQueryRunner(PodmanResourceService);
|
|
452
|
+
|
|
453
|
+
await PodmanResourceService.bulkUpsert({
|
|
454
|
+
projectId: PROJECT_ID,
|
|
455
|
+
podmanHostId: PARENT_ID,
|
|
456
|
+
resources: [podmanResource({ name: OVERSIZED }), podmanResource()],
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
const params: Array<unknown> = paramsOf(query);
|
|
460
|
+
expect(params).toContain(CLAMPED_TO_LONG_TEXT);
|
|
461
|
+
expect(params).toContain("ghcr.io/acme/api:1.2.3");
|
|
462
|
+
expectNoParamExceedsLongText(params);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
test("bulkUpsert clamps the ShortText columns at 100, not 500", async () => {
|
|
466
|
+
const query: jest.Mock = mockQueryRunner(PodmanResourceService);
|
|
467
|
+
|
|
468
|
+
await PodmanResourceService.bulkUpsert({
|
|
469
|
+
projectId: PROJECT_ID,
|
|
470
|
+
podmanHostId: PARENT_ID,
|
|
471
|
+
resources: [
|
|
472
|
+
podmanResource({
|
|
473
|
+
kind: OVERSIZED,
|
|
474
|
+
containerId: OVERSIZED,
|
|
475
|
+
state: OVERSIZED,
|
|
476
|
+
}),
|
|
477
|
+
],
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
const params: Array<unknown> = paramsOf(query);
|
|
481
|
+
expect(
|
|
482
|
+
params.filter((value: unknown) => {
|
|
483
|
+
return value === CLAMPED_TO_SHORT_TEXT;
|
|
484
|
+
}),
|
|
485
|
+
).toHaveLength(3);
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
describe("DockerSwarmResourceService — oversized values can't drop the chunk", () => {
|
|
490
|
+
function swarmResource(
|
|
491
|
+
overrides: Partial<ParsedDockerSwarmResource> = {},
|
|
492
|
+
): ParsedDockerSwarmResource {
|
|
493
|
+
return {
|
|
494
|
+
kind: "Task",
|
|
495
|
+
externalId: "task/abc123",
|
|
496
|
+
name: "api.1",
|
|
497
|
+
state: "running",
|
|
498
|
+
role: null,
|
|
499
|
+
serviceMode: null,
|
|
500
|
+
desiredReplicas: null,
|
|
501
|
+
runningReplicas: null,
|
|
502
|
+
image: "ghcr.io/acme/api:1.2.3",
|
|
503
|
+
stackName: "acme",
|
|
504
|
+
serviceName: "api",
|
|
505
|
+
nodeHostname: "swarm-1",
|
|
506
|
+
driver: null,
|
|
507
|
+
isReady: true,
|
|
508
|
+
attributes: null,
|
|
509
|
+
lastSeenAt: OBSERVED_AT,
|
|
510
|
+
...overrides,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
test("bulkUpsert clamps an oversized externalId and keeps its sibling row", async () => {
|
|
515
|
+
const query: jest.Mock = mockQueryRunner(DockerSwarmResourceService);
|
|
516
|
+
|
|
517
|
+
await DockerSwarmResourceService.bulkUpsert({
|
|
518
|
+
projectId: PROJECT_ID,
|
|
519
|
+
dockerSwarmClusterId: PARENT_ID,
|
|
520
|
+
resources: [swarmResource({ externalId: OVERSIZED }), swarmResource()],
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
expect(query).toHaveBeenCalledTimes(1);
|
|
524
|
+
const params: Array<unknown> = paramsOf(query);
|
|
525
|
+
|
|
526
|
+
expect(params).toContain(CLAMPED_TO_SHORT_TEXT);
|
|
527
|
+
expect(params).not.toContain(OVERSIZED);
|
|
528
|
+
expect(params).toContain("task/abc123");
|
|
529
|
+
expectNoParamExceedsLongText(params);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
test("bulkUpsert clamps image at 500, since it is the one LongText column", async () => {
|
|
533
|
+
const query: jest.Mock = mockQueryRunner(DockerSwarmResourceService);
|
|
534
|
+
|
|
535
|
+
await DockerSwarmResourceService.bulkUpsert({
|
|
536
|
+
projectId: PROJECT_ID,
|
|
537
|
+
dockerSwarmClusterId: PARENT_ID,
|
|
538
|
+
resources: [swarmResource({ image: OVERSIZED })],
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
const params: Array<unknown> = paramsOf(query);
|
|
542
|
+
expect(params).toContain(CLAMPED_TO_LONG_TEXT);
|
|
543
|
+
expect(params).not.toContain(CLAMPED_TO_SHORT_TEXT);
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
test("bulkUpdateLatestMetrics clamps identity the same way bulkUpsert does", async () => {
|
|
547
|
+
const query: jest.Mock = mockQueryRunner(DockerSwarmResourceService);
|
|
548
|
+
|
|
549
|
+
await DockerSwarmResourceService.bulkUpdateLatestMetrics({
|
|
550
|
+
projectId: PROJECT_ID,
|
|
551
|
+
dockerSwarmClusterId: PARENT_ID,
|
|
552
|
+
metrics: [
|
|
553
|
+
{
|
|
554
|
+
kind: "Task",
|
|
555
|
+
externalId: OVERSIZED,
|
|
556
|
+
cpuPercent: null,
|
|
557
|
+
memoryBytes: null,
|
|
558
|
+
maxMemoryBytes: null,
|
|
559
|
+
memoryPercent: null,
|
|
560
|
+
observedAt: OBSERVED_AT,
|
|
561
|
+
},
|
|
562
|
+
],
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
const params: Array<unknown> = paramsOf(query);
|
|
566
|
+
expect(params).toContain(CLAMPED_TO_SHORT_TEXT);
|
|
567
|
+
expect(params).not.toContain(OVERSIZED);
|
|
568
|
+
});
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
describe("ProxmoxResourceService — oversized values can't drop the chunk", () => {
|
|
572
|
+
function proxmoxResource(
|
|
573
|
+
overrides: Partial<ParsedProxmoxResource> = {},
|
|
574
|
+
): ParsedProxmoxResource {
|
|
575
|
+
return {
|
|
576
|
+
kind: "Guest",
|
|
577
|
+
externalId: "qemu/100",
|
|
578
|
+
name: "web-01",
|
|
579
|
+
vmid: 100,
|
|
580
|
+
guestType: "qemu",
|
|
581
|
+
parentNodeName: "pve1",
|
|
582
|
+
isUp: true,
|
|
583
|
+
haState: null,
|
|
584
|
+
onboot: null,
|
|
585
|
+
isBackedUp: null,
|
|
586
|
+
uptimeSeconds: 3600,
|
|
587
|
+
lastSeenAt: OBSERVED_AT,
|
|
588
|
+
...overrides,
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
test("bulkUpsert clamps an oversized externalId and keeps its sibling row", async () => {
|
|
593
|
+
const query: jest.Mock = mockQueryRunner(ProxmoxResourceService);
|
|
594
|
+
|
|
595
|
+
await ProxmoxResourceService.bulkUpsert({
|
|
596
|
+
projectId: PROJECT_ID,
|
|
597
|
+
proxmoxClusterId: PARENT_ID,
|
|
598
|
+
resources: [
|
|
599
|
+
proxmoxResource({ externalId: OVERSIZED }),
|
|
600
|
+
proxmoxResource(),
|
|
601
|
+
],
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
expect(query).toHaveBeenCalledTimes(1);
|
|
605
|
+
const params: Array<unknown> = paramsOf(query);
|
|
606
|
+
|
|
607
|
+
expect(params).toContain(CLAMPED_TO_SHORT_TEXT);
|
|
608
|
+
expect(params).not.toContain(OVERSIZED);
|
|
609
|
+
expect(params).toContain("qemu/100");
|
|
610
|
+
expectNoParamExceedsLongText(params);
|
|
611
|
+
});
|
|
612
|
+
|
|
613
|
+
test("bulkUpdateLatestMetrics clamps identity the same way bulkUpsert does", async () => {
|
|
614
|
+
const query: jest.Mock = mockQueryRunner(ProxmoxResourceService);
|
|
615
|
+
|
|
616
|
+
await ProxmoxResourceService.bulkUpdateLatestMetrics({
|
|
617
|
+
projectId: PROJECT_ID,
|
|
618
|
+
proxmoxClusterId: PARENT_ID,
|
|
619
|
+
metrics: [
|
|
620
|
+
{
|
|
621
|
+
kind: "Guest",
|
|
622
|
+
externalId: OVERSIZED,
|
|
623
|
+
cpuPercent: null,
|
|
624
|
+
memoryBytes: null,
|
|
625
|
+
maxMemoryBytes: null,
|
|
626
|
+
memoryPercent: null,
|
|
627
|
+
diskBytes: null,
|
|
628
|
+
maxDiskBytes: null,
|
|
629
|
+
observedAt: OBSERVED_AT,
|
|
630
|
+
},
|
|
631
|
+
],
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
const params: Array<unknown> = paramsOf(query);
|
|
635
|
+
expect(params).toContain(CLAMPED_TO_SHORT_TEXT);
|
|
636
|
+
expect(params).not.toContain(OVERSIZED);
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
describe("CephResourceService — oversized values can't drop the chunk", () => {
|
|
641
|
+
function cephResource(
|
|
642
|
+
overrides: Partial<ParsedCephResource> = {},
|
|
643
|
+
): ParsedCephResource {
|
|
644
|
+
return {
|
|
645
|
+
kind: "Osd",
|
|
646
|
+
externalId: "osd.3",
|
|
647
|
+
name: null,
|
|
648
|
+
hostname: "ceph-node-1",
|
|
649
|
+
daemonVersion: "ceph version 18.2.2 reef",
|
|
650
|
+
deviceClass: "ssd",
|
|
651
|
+
isUp: true,
|
|
652
|
+
isIn: true,
|
|
653
|
+
inQuorum: null,
|
|
654
|
+
lastSeenAt: OBSERVED_AT,
|
|
655
|
+
...overrides,
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
test("bulkUpsert clamps an oversized externalId and keeps its sibling row", async () => {
|
|
660
|
+
const query: jest.Mock = mockQueryRunner(CephResourceService);
|
|
661
|
+
|
|
662
|
+
await CephResourceService.bulkUpsert({
|
|
663
|
+
projectId: PROJECT_ID,
|
|
664
|
+
cephClusterId: PARENT_ID,
|
|
665
|
+
resources: [cephResource({ externalId: OVERSIZED }), cephResource()],
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
expect(query).toHaveBeenCalledTimes(1);
|
|
669
|
+
const params: Array<unknown> = paramsOf(query);
|
|
670
|
+
|
|
671
|
+
expect(params).toContain(CLAMPED_TO_SHORT_TEXT);
|
|
672
|
+
expect(params).not.toContain(OVERSIZED);
|
|
673
|
+
expect(params).toContain("osd.3");
|
|
674
|
+
expectNoParamExceedsLongText(params);
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
test("bulkUpsert leaves NULL identity columns alone", async () => {
|
|
678
|
+
/*
|
|
679
|
+
* CephResource.name is pool-only — clamping must not turn a
|
|
680
|
+
* legitimate NULL into an empty string, or the COALESCE in the
|
|
681
|
+
* upsert would stop protecting the last-known value.
|
|
682
|
+
*/
|
|
683
|
+
const query: jest.Mock = mockQueryRunner(CephResourceService);
|
|
684
|
+
|
|
685
|
+
await CephResourceService.bulkUpsert({
|
|
686
|
+
projectId: PROJECT_ID,
|
|
687
|
+
cephClusterId: PARENT_ID,
|
|
688
|
+
resources: [cephResource()],
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
const params: Array<unknown> = paramsOf(query);
|
|
692
|
+
expect(params[4]).toBeNull();
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
test("bulkUpdateLatestMetrics clamps identity the same way bulkUpsert does", async () => {
|
|
696
|
+
const query: jest.Mock = mockQueryRunner(CephResourceService);
|
|
697
|
+
|
|
698
|
+
await CephResourceService.bulkUpdateLatestMetrics({
|
|
699
|
+
projectId: PROJECT_ID,
|
|
700
|
+
cephClusterId: PARENT_ID,
|
|
701
|
+
metrics: [
|
|
702
|
+
{
|
|
703
|
+
kind: "Osd",
|
|
704
|
+
externalId: OVERSIZED,
|
|
705
|
+
statBytes: null,
|
|
706
|
+
statBytesUsed: null,
|
|
707
|
+
applyLatencyMs: null,
|
|
708
|
+
commitLatencyMs: null,
|
|
709
|
+
pgCount: null,
|
|
710
|
+
storedBytes: null,
|
|
711
|
+
maxAvailBytes: null,
|
|
712
|
+
objects: null,
|
|
713
|
+
readOpsCounter: null,
|
|
714
|
+
writeOpsCounter: null,
|
|
715
|
+
observedAt: OBSERVED_AT,
|
|
716
|
+
},
|
|
717
|
+
],
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
const params: Array<unknown> = paramsOf(query);
|
|
721
|
+
expect(params).toContain(CLAMPED_TO_SHORT_TEXT);
|
|
722
|
+
expect(params).not.toContain(OVERSIZED);
|
|
723
|
+
});
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
describe("a full 500-row chunk survives one oversized row", () => {
|
|
727
|
+
test("every row still rides in the single statement", async () => {
|
|
728
|
+
const query: jest.Mock = mockQueryRunner(KubernetesContainerService);
|
|
729
|
+
|
|
730
|
+
const containers: Array<ParsedKubernetesContainer> = [];
|
|
731
|
+
for (let i: number = 0; i < 500; i++) {
|
|
732
|
+
containers.push({
|
|
733
|
+
podNamespaceKey: "default",
|
|
734
|
+
podName: i === 0 ? OVERSIZED : `api-7d9f8c6b5d-${i}`,
|
|
735
|
+
name: "app",
|
|
736
|
+
image: "ghcr.io/acme/api:1.2.3",
|
|
737
|
+
state: "running",
|
|
738
|
+
reason: null,
|
|
739
|
+
isReady: true,
|
|
740
|
+
restartCount: 0,
|
|
741
|
+
memoryLimitBytes: null,
|
|
742
|
+
lastSeenAt: OBSERVED_AT,
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
await KubernetesContainerService.bulkUpsert({
|
|
747
|
+
projectId: PROJECT_ID,
|
|
748
|
+
kubernetesClusterId: PARENT_ID,
|
|
749
|
+
containers,
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
// One chunk, 13 columns per row — nothing dropped by the clamp.
|
|
753
|
+
expect(query).toHaveBeenCalledTimes(1);
|
|
754
|
+
const params: Array<unknown> = paramsOf(query);
|
|
755
|
+
expect(params).toHaveLength(500 * 13);
|
|
756
|
+
expect(params).toContain(CLAMPED_TO_LONG_TEXT);
|
|
757
|
+
expect(params).toContain("api-7d9f8c6b5d-499");
|
|
758
|
+
expectNoParamExceedsLongText(params);
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
test("an oversized row in a later chunk doesn't disturb the earlier one", async () => {
|
|
762
|
+
const query: jest.Mock = mockQueryRunner(KubernetesContainerService);
|
|
763
|
+
|
|
764
|
+
const containers: Array<ParsedKubernetesContainer> = [];
|
|
765
|
+
for (let i: number = 0; i < 501; i++) {
|
|
766
|
+
containers.push({
|
|
767
|
+
podNamespaceKey: "default",
|
|
768
|
+
podName: i === 500 ? OVERSIZED : `api-7d9f8c6b5d-${i}`,
|
|
769
|
+
name: "app",
|
|
770
|
+
image: "ghcr.io/acme/api:1.2.3",
|
|
771
|
+
state: "running",
|
|
772
|
+
reason: null,
|
|
773
|
+
isReady: true,
|
|
774
|
+
restartCount: 0,
|
|
775
|
+
memoryLimitBytes: null,
|
|
776
|
+
lastSeenAt: OBSERVED_AT,
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
await KubernetesContainerService.bulkUpsert({
|
|
781
|
+
projectId: PROJECT_ID,
|
|
782
|
+
kubernetesClusterId: PARENT_ID,
|
|
783
|
+
containers,
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
expect(query).toHaveBeenCalledTimes(2);
|
|
787
|
+
expect(paramsOf(query, 0)).toHaveLength(500 * 13);
|
|
788
|
+
expect(paramsOf(query, 1)).toHaveLength(13);
|
|
789
|
+
expect(paramsOf(query, 0)).not.toContain(CLAMPED_TO_LONG_TEXT);
|
|
790
|
+
expect(paramsOf(query, 1)).toContain(CLAMPED_TO_LONG_TEXT);
|
|
791
|
+
});
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
describe("values that already fit are never touched", () => {
|
|
795
|
+
/*
|
|
796
|
+
* The other half of the contract. A clamp that fires one character
|
|
797
|
+
* early would silently corrupt legitimate inventory names — and
|
|
798
|
+
* because the clamped columns are conflict targets, a corrupted name
|
|
799
|
+
* mints a duplicate row rather than updating the real one.
|
|
800
|
+
*/
|
|
801
|
+
const AT_SHORT_TEXT_BOUND: string = "s".repeat(ColumnLength.ShortText);
|
|
802
|
+
const AT_LONG_TEXT_BOUND: string = "l".repeat(ColumnLength.LongText);
|
|
803
|
+
const KUBERNETES_LONGEST_NAME: string = "k".repeat(253);
|
|
804
|
+
|
|
805
|
+
test("KubernetesResource keeps a name sitting exactly on the 500 bound", async () => {
|
|
806
|
+
const query: jest.Mock = mockQueryRunner(KubernetesResourceService);
|
|
807
|
+
|
|
808
|
+
await KubernetesResourceService.bulkUpsert({
|
|
809
|
+
projectId: PROJECT_ID,
|
|
810
|
+
kubernetesClusterId: PARENT_ID,
|
|
811
|
+
resources: [
|
|
812
|
+
{
|
|
813
|
+
kind: AT_SHORT_TEXT_BOUND,
|
|
814
|
+
namespaceKey: "default",
|
|
815
|
+
name: AT_LONG_TEXT_BOUND,
|
|
816
|
+
uid: null,
|
|
817
|
+
phase: "Running",
|
|
818
|
+
isReady: true,
|
|
819
|
+
hasMemoryPressure: null,
|
|
820
|
+
hasDiskPressure: null,
|
|
821
|
+
hasPidPressure: null,
|
|
822
|
+
labels: null,
|
|
823
|
+
annotations: null,
|
|
824
|
+
ownerReferences: null,
|
|
825
|
+
spec: null,
|
|
826
|
+
containerCount: null,
|
|
827
|
+
status: null,
|
|
828
|
+
lastSeenAt: OBSERVED_AT,
|
|
829
|
+
resourceCreationTimestamp: null,
|
|
830
|
+
},
|
|
831
|
+
],
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
const params: Array<unknown> = paramsOf(query);
|
|
835
|
+
expect(params[2]).toBe(AT_SHORT_TEXT_BOUND);
|
|
836
|
+
expect(params[4]).toBe(AT_LONG_TEXT_BOUND);
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
test("KubernetesContainer keeps the longest name Kubernetes can produce", async () => {
|
|
840
|
+
const query: jest.Mock = mockQueryRunner(KubernetesContainerService);
|
|
841
|
+
|
|
842
|
+
await KubernetesContainerService.bulkUpsert({
|
|
843
|
+
projectId: PROJECT_ID,
|
|
844
|
+
kubernetesClusterId: PARENT_ID,
|
|
845
|
+
containers: [
|
|
846
|
+
{
|
|
847
|
+
podNamespaceKey: "default",
|
|
848
|
+
podName: KUBERNETES_LONGEST_NAME,
|
|
849
|
+
name: "app",
|
|
850
|
+
image: "ghcr.io/acme/api:1.2.3",
|
|
851
|
+
state: "running",
|
|
852
|
+
reason: null,
|
|
853
|
+
isReady: true,
|
|
854
|
+
restartCount: 0,
|
|
855
|
+
memoryLimitBytes: null,
|
|
856
|
+
lastSeenAt: OBSERVED_AT,
|
|
857
|
+
},
|
|
858
|
+
],
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
// The whole point of the widening: 253 chars must survive intact.
|
|
862
|
+
expect(paramsOf(query)[3]).toBe(KUBERNETES_LONGEST_NAME);
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
test("PodmanResource keeps a long-but-legal image reference intact", async () => {
|
|
866
|
+
const query: jest.Mock = mockQueryRunner(PodmanResourceService);
|
|
867
|
+
const imageReference: string = `ghcr.io/${"o".repeat(60)}/${"r".repeat(
|
|
868
|
+
60,
|
|
869
|
+
)}:${"t".repeat(60)}`;
|
|
870
|
+
|
|
871
|
+
await PodmanResourceService.bulkUpsert({
|
|
872
|
+
projectId: PROJECT_ID,
|
|
873
|
+
podmanHostId: PARENT_ID,
|
|
874
|
+
resources: [
|
|
875
|
+
{
|
|
876
|
+
kind: "Image",
|
|
877
|
+
name: imageReference,
|
|
878
|
+
containerId: null,
|
|
879
|
+
imageName: null,
|
|
880
|
+
state: null,
|
|
881
|
+
labels: null,
|
|
882
|
+
resourceCreationTimestamp: null,
|
|
883
|
+
lastSeenAt: OBSERVED_AT,
|
|
884
|
+
},
|
|
885
|
+
],
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
expect(imageReference.length).toBeGreaterThan(ColumnLength.ShortText);
|
|
889
|
+
expect(paramsOf(query)[3]).toBe(imageReference);
|
|
890
|
+
});
|
|
891
|
+
|
|
892
|
+
test("CephResource keeps every identity value that already fits", async () => {
|
|
893
|
+
const query: jest.Mock = mockQueryRunner(CephResourceService);
|
|
894
|
+
|
|
895
|
+
await CephResourceService.bulkUpsert({
|
|
896
|
+
projectId: PROJECT_ID,
|
|
897
|
+
cephClusterId: PARENT_ID,
|
|
898
|
+
resources: [
|
|
899
|
+
{
|
|
900
|
+
kind: "Osd",
|
|
901
|
+
externalId: "osd.3",
|
|
902
|
+
name: null,
|
|
903
|
+
hostname: "ceph-node-1",
|
|
904
|
+
daemonVersion: "ceph version 18.2.2 reef",
|
|
905
|
+
deviceClass: "ssd",
|
|
906
|
+
isUp: true,
|
|
907
|
+
isIn: true,
|
|
908
|
+
inQuorum: null,
|
|
909
|
+
lastSeenAt: OBSERVED_AT,
|
|
910
|
+
},
|
|
911
|
+
],
|
|
912
|
+
});
|
|
913
|
+
|
|
914
|
+
expect(paramsOf(query)).toEqual([
|
|
915
|
+
PROJECT_ID.toString(),
|
|
916
|
+
PARENT_ID.toString(),
|
|
917
|
+
"Osd",
|
|
918
|
+
"osd.3",
|
|
919
|
+
null,
|
|
920
|
+
"ceph-node-1",
|
|
921
|
+
"ceph version 18.2.2 reef",
|
|
922
|
+
"ssd",
|
|
923
|
+
true,
|
|
924
|
+
true,
|
|
925
|
+
null,
|
|
926
|
+
OBSERVED_AT,
|
|
927
|
+
0,
|
|
928
|
+
]);
|
|
929
|
+
});
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
describe("NULL stays NULL through the clamp", () => {
|
|
933
|
+
/*
|
|
934
|
+
* Every one of these upserts COALESCEs against the existing row, so a
|
|
935
|
+
* NULL means "this batch didn't carry the field, keep what's stored."
|
|
936
|
+
* A clamp that coerced NULL to "" would blank real data on every
|
|
937
|
+
* partial scrape.
|
|
938
|
+
*/
|
|
939
|
+
test("DockerSwarmResource passes its nullable identity columns through as null", async () => {
|
|
940
|
+
const query: jest.Mock = mockQueryRunner(DockerSwarmResourceService);
|
|
941
|
+
|
|
942
|
+
await DockerSwarmResourceService.bulkUpsert({
|
|
943
|
+
projectId: PROJECT_ID,
|
|
944
|
+
dockerSwarmClusterId: PARENT_ID,
|
|
945
|
+
resources: [
|
|
946
|
+
{
|
|
947
|
+
kind: "Network",
|
|
948
|
+
externalId: "network/abc",
|
|
949
|
+
name: null,
|
|
950
|
+
state: null,
|
|
951
|
+
role: null,
|
|
952
|
+
serviceMode: null,
|
|
953
|
+
desiredReplicas: null,
|
|
954
|
+
runningReplicas: null,
|
|
955
|
+
image: null,
|
|
956
|
+
stackName: null,
|
|
957
|
+
serviceName: null,
|
|
958
|
+
nodeHostname: null,
|
|
959
|
+
driver: null,
|
|
960
|
+
isReady: null,
|
|
961
|
+
attributes: null,
|
|
962
|
+
lastSeenAt: OBSERVED_AT,
|
|
963
|
+
},
|
|
964
|
+
],
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
const params: Array<unknown> = paramsOf(query);
|
|
968
|
+
for (const index of [4, 5, 6, 7, 10, 11, 12, 13, 14]) {
|
|
969
|
+
expect(params[index]).toBeNull();
|
|
970
|
+
}
|
|
971
|
+
expect(params).not.toContain("");
|
|
972
|
+
});
|
|
973
|
+
|
|
974
|
+
test("ProxmoxResource passes its nullable identity columns through as null", async () => {
|
|
975
|
+
const query: jest.Mock = mockQueryRunner(ProxmoxResourceService);
|
|
976
|
+
|
|
977
|
+
await ProxmoxResourceService.bulkUpsert({
|
|
978
|
+
projectId: PROJECT_ID,
|
|
979
|
+
proxmoxClusterId: PARENT_ID,
|
|
980
|
+
resources: [
|
|
981
|
+
{
|
|
982
|
+
kind: "Node",
|
|
983
|
+
externalId: "node/pve1",
|
|
984
|
+
name: null,
|
|
985
|
+
vmid: null,
|
|
986
|
+
guestType: null,
|
|
987
|
+
parentNodeName: null,
|
|
988
|
+
isUp: true,
|
|
989
|
+
haState: null,
|
|
990
|
+
onboot: null,
|
|
991
|
+
isBackedUp: null,
|
|
992
|
+
uptimeSeconds: null,
|
|
993
|
+
lastSeenAt: OBSERVED_AT,
|
|
994
|
+
},
|
|
995
|
+
],
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
const params: Array<unknown> = paramsOf(query);
|
|
999
|
+
for (const index of [4, 6, 7, 9]) {
|
|
1000
|
+
expect(params[index]).toBeNull();
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
test("PodmanResource keeps a null imageName null rather than empty", async () => {
|
|
1005
|
+
const query: jest.Mock = mockQueryRunner(PodmanResourceService);
|
|
1006
|
+
|
|
1007
|
+
await PodmanResourceService.bulkUpsertContainers({
|
|
1008
|
+
projectId: PROJECT_ID,
|
|
1009
|
+
podmanHostId: PARENT_ID,
|
|
1010
|
+
containers: [
|
|
1011
|
+
{
|
|
1012
|
+
containerName: "api",
|
|
1013
|
+
containerId: null,
|
|
1014
|
+
imageName: null,
|
|
1015
|
+
state: "running",
|
|
1016
|
+
cpuPercent: null,
|
|
1017
|
+
memoryBytes: null,
|
|
1018
|
+
observedAt: OBSERVED_AT,
|
|
1019
|
+
},
|
|
1020
|
+
],
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
const params: Array<unknown> = paramsOf(query);
|
|
1024
|
+
expect(params[4]).toBeNull();
|
|
1025
|
+
expect(params[5]).toBeNull();
|
|
1026
|
+
});
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
describe("the clamp announces itself", () => {
|
|
1030
|
+
/*
|
|
1031
|
+
* Truncation is data loss, however small. It has to be visible in the
|
|
1032
|
+
* logs or an operator debugging a "missing container" has nothing to
|
|
1033
|
+
* go on.
|
|
1034
|
+
*/
|
|
1035
|
+
test("warns once per offending row, naming the parent resource", async () => {
|
|
1036
|
+
const warn: jest.SpyInstance = jest
|
|
1037
|
+
.spyOn(logger, "warn")
|
|
1038
|
+
.mockImplementation(() => {});
|
|
1039
|
+
mockQueryRunner(PodmanResourceService);
|
|
1040
|
+
|
|
1041
|
+
await PodmanResourceService.bulkUpsert({
|
|
1042
|
+
projectId: PROJECT_ID,
|
|
1043
|
+
podmanHostId: PARENT_ID,
|
|
1044
|
+
resources: [
|
|
1045
|
+
{
|
|
1046
|
+
kind: "Image",
|
|
1047
|
+
name: OVERSIZED,
|
|
1048
|
+
containerId: null,
|
|
1049
|
+
imageName: null,
|
|
1050
|
+
state: null,
|
|
1051
|
+
labels: null,
|
|
1052
|
+
resourceCreationTimestamp: null,
|
|
1053
|
+
lastSeenAt: OBSERVED_AT,
|
|
1054
|
+
},
|
|
1055
|
+
],
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
1059
|
+
const message: string = String(warn.mock.calls[0]?.[0]);
|
|
1060
|
+
expect(message).toContain("PodmanResource name exceeds");
|
|
1061
|
+
expect(message).toContain(String(ColumnLength.LongText));
|
|
1062
|
+
expect(message).toContain(PARENT_ID.toString());
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
test("stays quiet for a batch that needs no clamping", async () => {
|
|
1066
|
+
const warn: jest.SpyInstance = jest
|
|
1067
|
+
.spyOn(logger, "warn")
|
|
1068
|
+
.mockImplementation(() => {});
|
|
1069
|
+
mockQueryRunner(KubernetesResourceService);
|
|
1070
|
+
|
|
1071
|
+
await KubernetesResourceService.bulkUpsert({
|
|
1072
|
+
projectId: PROJECT_ID,
|
|
1073
|
+
kubernetesClusterId: PARENT_ID,
|
|
1074
|
+
resources: [
|
|
1075
|
+
{
|
|
1076
|
+
kind: "Pod",
|
|
1077
|
+
namespaceKey: "default",
|
|
1078
|
+
name: "api-7d9f8c6b5d-x2k9p",
|
|
1079
|
+
uid: null,
|
|
1080
|
+
phase: "Running",
|
|
1081
|
+
isReady: true,
|
|
1082
|
+
hasMemoryPressure: null,
|
|
1083
|
+
hasDiskPressure: null,
|
|
1084
|
+
hasPidPressure: null,
|
|
1085
|
+
labels: null,
|
|
1086
|
+
annotations: null,
|
|
1087
|
+
ownerReferences: null,
|
|
1088
|
+
spec: null,
|
|
1089
|
+
containerCount: null,
|
|
1090
|
+
status: null,
|
|
1091
|
+
lastSeenAt: OBSERVED_AT,
|
|
1092
|
+
resourceCreationTimestamp: null,
|
|
1093
|
+
},
|
|
1094
|
+
],
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
expect(warn).not.toHaveBeenCalled();
|
|
1098
|
+
});
|
|
1099
|
+
|
|
1100
|
+
test("warns per offending row, not once for the whole batch", async () => {
|
|
1101
|
+
const warn: jest.SpyInstance = jest
|
|
1102
|
+
.spyOn(logger, "warn")
|
|
1103
|
+
.mockImplementation(() => {});
|
|
1104
|
+
mockQueryRunner(CephResourceService);
|
|
1105
|
+
|
|
1106
|
+
await CephResourceService.bulkUpsert({
|
|
1107
|
+
projectId: PROJECT_ID,
|
|
1108
|
+
cephClusterId: PARENT_ID,
|
|
1109
|
+
/*
|
|
1110
|
+
* The two ids must differ WITHIN the clamp bound, or they collapse
|
|
1111
|
+
* onto the same ON CONFLICT target and the statement Postgres gets
|
|
1112
|
+
* is one the clamp itself made unusable — not what this test is
|
|
1113
|
+
* about, and a misleading fixture to leave lying around.
|
|
1114
|
+
*/
|
|
1115
|
+
resources: [
|
|
1116
|
+
{
|
|
1117
|
+
kind: "Osd",
|
|
1118
|
+
externalId: `a${OVERSIZED}`,
|
|
1119
|
+
name: null,
|
|
1120
|
+
hostname: null,
|
|
1121
|
+
daemonVersion: null,
|
|
1122
|
+
deviceClass: null,
|
|
1123
|
+
isUp: true,
|
|
1124
|
+
isIn: true,
|
|
1125
|
+
inQuorum: null,
|
|
1126
|
+
lastSeenAt: OBSERVED_AT,
|
|
1127
|
+
},
|
|
1128
|
+
{
|
|
1129
|
+
kind: "Osd",
|
|
1130
|
+
externalId: `b${OVERSIZED}`,
|
|
1131
|
+
name: null,
|
|
1132
|
+
hostname: null,
|
|
1133
|
+
daemonVersion: null,
|
|
1134
|
+
deviceClass: null,
|
|
1135
|
+
isUp: true,
|
|
1136
|
+
isIn: true,
|
|
1137
|
+
inQuorum: null,
|
|
1138
|
+
lastSeenAt: OBSERVED_AT,
|
|
1139
|
+
},
|
|
1140
|
+
],
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
expect(warn).toHaveBeenCalledTimes(2);
|
|
1144
|
+
});
|
|
1145
|
+
});
|
|
1146
|
+
|
|
1147
|
+
describe("an empty batch still short-circuits", () => {
|
|
1148
|
+
/*
|
|
1149
|
+
* The sanitizing map runs before the chunk loop, so the early return
|
|
1150
|
+
* has to stay ahead of it — an empty batch must not reach Postgres.
|
|
1151
|
+
*/
|
|
1152
|
+
test.each([
|
|
1153
|
+
[
|
|
1154
|
+
"KubernetesResourceService.bulkUpsert",
|
|
1155
|
+
KubernetesResourceService,
|
|
1156
|
+
async (): Promise<void> => {
|
|
1157
|
+
await KubernetesResourceService.bulkUpsert({
|
|
1158
|
+
projectId: PROJECT_ID,
|
|
1159
|
+
kubernetesClusterId: PARENT_ID,
|
|
1160
|
+
resources: [],
|
|
1161
|
+
});
|
|
1162
|
+
},
|
|
1163
|
+
],
|
|
1164
|
+
[
|
|
1165
|
+
"KubernetesContainerService.bulkUpsert",
|
|
1166
|
+
KubernetesContainerService,
|
|
1167
|
+
async (): Promise<void> => {
|
|
1168
|
+
await KubernetesContainerService.bulkUpsert({
|
|
1169
|
+
projectId: PROJECT_ID,
|
|
1170
|
+
kubernetesClusterId: PARENT_ID,
|
|
1171
|
+
containers: [],
|
|
1172
|
+
});
|
|
1173
|
+
},
|
|
1174
|
+
],
|
|
1175
|
+
[
|
|
1176
|
+
"DockerResourceService.bulkUpsertContainers",
|
|
1177
|
+
DockerResourceService,
|
|
1178
|
+
async (): Promise<void> => {
|
|
1179
|
+
await DockerResourceService.bulkUpsertContainers({
|
|
1180
|
+
projectId: PROJECT_ID,
|
|
1181
|
+
dockerHostId: PARENT_ID,
|
|
1182
|
+
containers: [],
|
|
1183
|
+
});
|
|
1184
|
+
},
|
|
1185
|
+
],
|
|
1186
|
+
[
|
|
1187
|
+
"PodmanResourceService.bulkUpsert",
|
|
1188
|
+
PodmanResourceService,
|
|
1189
|
+
async (): Promise<void> => {
|
|
1190
|
+
await PodmanResourceService.bulkUpsert({
|
|
1191
|
+
projectId: PROJECT_ID,
|
|
1192
|
+
podmanHostId: PARENT_ID,
|
|
1193
|
+
resources: [],
|
|
1194
|
+
});
|
|
1195
|
+
},
|
|
1196
|
+
],
|
|
1197
|
+
[
|
|
1198
|
+
"DockerSwarmResourceService.bulkUpsert",
|
|
1199
|
+
DockerSwarmResourceService,
|
|
1200
|
+
async (): Promise<void> => {
|
|
1201
|
+
await DockerSwarmResourceService.bulkUpsert({
|
|
1202
|
+
projectId: PROJECT_ID,
|
|
1203
|
+
dockerSwarmClusterId: PARENT_ID,
|
|
1204
|
+
resources: [],
|
|
1205
|
+
});
|
|
1206
|
+
},
|
|
1207
|
+
],
|
|
1208
|
+
[
|
|
1209
|
+
"ProxmoxResourceService.bulkUpsert",
|
|
1210
|
+
ProxmoxResourceService,
|
|
1211
|
+
async (): Promise<void> => {
|
|
1212
|
+
await ProxmoxResourceService.bulkUpsert({
|
|
1213
|
+
projectId: PROJECT_ID,
|
|
1214
|
+
proxmoxClusterId: PARENT_ID,
|
|
1215
|
+
resources: [],
|
|
1216
|
+
});
|
|
1217
|
+
},
|
|
1218
|
+
],
|
|
1219
|
+
[
|
|
1220
|
+
"CephResourceService.bulkUpsert",
|
|
1221
|
+
CephResourceService,
|
|
1222
|
+
async (): Promise<void> => {
|
|
1223
|
+
await CephResourceService.bulkUpsert({
|
|
1224
|
+
projectId: PROJECT_ID,
|
|
1225
|
+
cephClusterId: PARENT_ID,
|
|
1226
|
+
resources: [],
|
|
1227
|
+
});
|
|
1228
|
+
},
|
|
1229
|
+
],
|
|
1230
|
+
])(
|
|
1231
|
+
"%s issues no statement",
|
|
1232
|
+
async (
|
|
1233
|
+
_label: string,
|
|
1234
|
+
service: RepositoryHolder,
|
|
1235
|
+
invoke: () => Promise<void>,
|
|
1236
|
+
) => {
|
|
1237
|
+
const query: jest.Mock = mockQueryRunner(service);
|
|
1238
|
+
await invoke();
|
|
1239
|
+
expect(query).not.toHaveBeenCalled();
|
|
1240
|
+
},
|
|
1241
|
+
);
|
|
1242
|
+
});
|