@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,141 @@
|
|
|
1
|
+
import { ToolArgs } from "../../../../Server/Utils/AI/Toolbox/ToolTypes";
|
|
2
|
+
import { JSONObject } from "../../../../Types/JSON";
|
|
3
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* ToolArgs.scopeServiceIds and getTimeRange already have dedicated suites.
|
|
7
|
+
* This covers the remaining argument extractors — the coercion, trimming,
|
|
8
|
+
* clamping and defaulting rules the AI toolbox relies on to turn loosely-typed
|
|
9
|
+
* model-supplied JSON arguments into safe, typed values.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
describe("ToolArgs extractors", () => {
|
|
13
|
+
describe("getString", () => {
|
|
14
|
+
test("trims and returns a non-empty string", () => {
|
|
15
|
+
expect(ToolArgs.getString({ q: " hello " }, "q")).toBe("hello");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("returns undefined for empty or whitespace-only strings", () => {
|
|
19
|
+
expect(ToolArgs.getString({ q: "" }, "q")).toBeUndefined();
|
|
20
|
+
expect(ToolArgs.getString({ q: " " }, "q")).toBeUndefined();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("returns undefined for missing keys and non-string values", () => {
|
|
24
|
+
expect(ToolArgs.getString({}, "q")).toBeUndefined();
|
|
25
|
+
expect(ToolArgs.getString({ q: 5 }, "q")).toBeUndefined();
|
|
26
|
+
expect(ToolArgs.getString({ q: null }, "q")).toBeUndefined();
|
|
27
|
+
expect(ToolArgs.getString({ q: ["a"] }, "q")).toBeUndefined();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("getStringArray", () => {
|
|
32
|
+
test("keeps only non-empty strings, trimming them", () => {
|
|
33
|
+
expect(
|
|
34
|
+
ToolArgs.getStringArray({ ids: ["a", " b ", "", " ", "c"] }, "ids"),
|
|
35
|
+
).toEqual(["a", " b ", "c"]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("filters out non-string members", () => {
|
|
39
|
+
expect(
|
|
40
|
+
ToolArgs.getStringArray(
|
|
41
|
+
{ ids: ["a", 1, true, null, "b"] as unknown as Array<string> },
|
|
42
|
+
"ids",
|
|
43
|
+
),
|
|
44
|
+
).toEqual(["a", "b"]);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("returns undefined when nothing survives filtering", () => {
|
|
48
|
+
expect(
|
|
49
|
+
ToolArgs.getStringArray({ ids: ["", " "] }, "ids"),
|
|
50
|
+
).toBeUndefined();
|
|
51
|
+
expect(ToolArgs.getStringArray({ ids: [] }, "ids")).toBeUndefined();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("returns undefined for a non-array value", () => {
|
|
55
|
+
expect(ToolArgs.getStringArray({ ids: "a,b" }, "ids")).toBeUndefined();
|
|
56
|
+
expect(ToolArgs.getStringArray({}, "ids")).toBeUndefined();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("getNumber", () => {
|
|
61
|
+
const options: { defaultValue: number; min: number; max: number } = {
|
|
62
|
+
defaultValue: 10,
|
|
63
|
+
min: 1,
|
|
64
|
+
max: 100,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
test("passes finite numbers through, flooring fractionals", () => {
|
|
68
|
+
expect(ToolArgs.getNumber({ n: 5 }, "n", options)).toBe(5);
|
|
69
|
+
expect(ToolArgs.getNumber({ n: 5.9 }, "n", options)).toBe(5);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("parses numeric strings", () => {
|
|
73
|
+
expect(ToolArgs.getNumber({ n: "7" }, "n", options)).toBe(7);
|
|
74
|
+
expect(ToolArgs.getNumber({ n: "7.8" }, "n", options)).toBe(7);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("clamps to the configured min and max", () => {
|
|
78
|
+
expect(ToolArgs.getNumber({ n: 1000 }, "n", options)).toBe(100);
|
|
79
|
+
expect(ToolArgs.getNumber({ n: -5 }, "n", options)).toBe(1);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("falls back to the default for missing or invalid values", () => {
|
|
83
|
+
expect(ToolArgs.getNumber({}, "n", options)).toBe(10);
|
|
84
|
+
expect(ToolArgs.getNumber({ n: "abc" }, "n", options)).toBe(10);
|
|
85
|
+
expect(ToolArgs.getNumber({ n: NaN }, "n", options)).toBe(10);
|
|
86
|
+
expect(ToolArgs.getNumber({ n: Infinity }, "n", options)).toBe(10);
|
|
87
|
+
expect(ToolArgs.getNumber({ n: "" }, "n", options)).toBe(10);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("still clamps the default value itself", () => {
|
|
91
|
+
// Default below min is raised to min; default above max lowered to max.
|
|
92
|
+
expect(
|
|
93
|
+
ToolArgs.getNumber({}, "n", { defaultValue: 0, min: 5, max: 100 }),
|
|
94
|
+
).toBe(5);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("getBoolean", () => {
|
|
99
|
+
test("returns real booleans as-is", () => {
|
|
100
|
+
expect(ToolArgs.getBoolean({ b: true }, "b")).toBe(true);
|
|
101
|
+
expect(ToolArgs.getBoolean({ b: false }, "b")).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("coerces the strings 'true' and 'false'", () => {
|
|
105
|
+
expect(ToolArgs.getBoolean({ b: "true" }, "b")).toBe(true);
|
|
106
|
+
expect(ToolArgs.getBoolean({ b: "false" }, "b")).toBe(false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("returns undefined for anything else", () => {
|
|
110
|
+
expect(ToolArgs.getBoolean({ b: "TRUE" }, "b")).toBeUndefined();
|
|
111
|
+
expect(ToolArgs.getBoolean({ b: 1 }, "b")).toBeUndefined();
|
|
112
|
+
expect(ToolArgs.getBoolean({ b: "yes" }, "b")).toBeUndefined();
|
|
113
|
+
expect(ToolArgs.getBoolean({}, "b")).toBeUndefined();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("getObjectID", () => {
|
|
118
|
+
test("wraps a non-empty string as an ObjectID", () => {
|
|
119
|
+
const id: ObjectID | undefined = ToolArgs.getObjectID(
|
|
120
|
+
{ serviceId: " abc123 " },
|
|
121
|
+
"serviceId",
|
|
122
|
+
);
|
|
123
|
+
expect(id).toBeInstanceOf(ObjectID);
|
|
124
|
+
// getString trims first, so the ObjectID carries the trimmed value.
|
|
125
|
+
expect(id?.toString()).toBe("abc123");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("returns undefined for missing or empty values", () => {
|
|
129
|
+
expect(ToolArgs.getObjectID({}, "serviceId")).toBeUndefined();
|
|
130
|
+
expect(
|
|
131
|
+
ToolArgs.getObjectID({ serviceId: " " }, "serviceId"),
|
|
132
|
+
).toBeUndefined();
|
|
133
|
+
expect(
|
|
134
|
+
ToolArgs.getObjectID(
|
|
135
|
+
{ serviceId: 5 as unknown as string } as JSONObject,
|
|
136
|
+
"serviceId",
|
|
137
|
+
),
|
|
138
|
+
).toBeUndefined();
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
});
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import WidgetBuilder from "../../../../../Server/Utils/AI/Toolbox/WidgetBuilder";
|
|
2
|
+
import { JSONObject } from "../../../../../Types/JSON";
|
|
3
|
+
import {
|
|
4
|
+
AIChatCitationTarget,
|
|
5
|
+
AIChatWidget,
|
|
6
|
+
AIChatWidgetColumn,
|
|
7
|
+
AIChatWidgetSeries,
|
|
8
|
+
AIChatWidgetSpan,
|
|
9
|
+
AIChatWidgetStat,
|
|
10
|
+
AIChatWidgetType,
|
|
11
|
+
} from "../../../../../Types/AI/AIChatTypes";
|
|
12
|
+
|
|
13
|
+
/*
|
|
14
|
+
* WidgetBuilder is the factory the AI toolbox uses to attach inline widgets to
|
|
15
|
+
* a tool result. The shapes it produces are a contract with the frontend
|
|
16
|
+
* renderer, so this pins down the widget type, the data envelope, the defaults
|
|
17
|
+
* (bars.stacked / bars.xIsTime), and the invariant that id/citationId are left
|
|
18
|
+
* blank for ChatAgentRunner to mint.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const link: AIChatCitationTarget = {
|
|
22
|
+
// A minimal citation target; only its presence is asserted below.
|
|
23
|
+
} as AIChatCitationTarget;
|
|
24
|
+
|
|
25
|
+
describe("WidgetBuilder", () => {
|
|
26
|
+
test("table() builds a Table widget carrying columns and rows", () => {
|
|
27
|
+
const columns: Array<AIChatWidgetColumn> = [
|
|
28
|
+
{ key: "name", title: "Name" } as AIChatWidgetColumn,
|
|
29
|
+
];
|
|
30
|
+
const rows: Array<JSONObject> = [{ name: "api" }, { name: "worker" }];
|
|
31
|
+
|
|
32
|
+
const widget: AIChatWidget = WidgetBuilder.table({
|
|
33
|
+
title: "Services",
|
|
34
|
+
description: "All services",
|
|
35
|
+
columns,
|
|
36
|
+
rows,
|
|
37
|
+
link,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
expect(widget.type).toBe(AIChatWidgetType.Table);
|
|
41
|
+
expect(widget.title).toBe("Services");
|
|
42
|
+
expect(widget.description).toBe("All services");
|
|
43
|
+
expect(widget.id).toBe("");
|
|
44
|
+
expect((widget.data as JSONObject)["columns"]).toBe(columns);
|
|
45
|
+
expect((widget.data as JSONObject)["rows"]).toBe(rows);
|
|
46
|
+
expect((widget.data as JSONObject)["link"]).toBe(link);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("timeSeries() hardcodes xIsTime to true", () => {
|
|
50
|
+
const series: Array<AIChatWidgetSeries> = [
|
|
51
|
+
{ name: "cpu", points: [] } as unknown as AIChatWidgetSeries,
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const widget: AIChatWidget = WidgetBuilder.timeSeries({
|
|
55
|
+
title: "CPU over time",
|
|
56
|
+
series,
|
|
57
|
+
unit: "%",
|
|
58
|
+
valueLabel: "CPU",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
expect(widget.type).toBe(AIChatWidgetType.TimeSeriesChart);
|
|
62
|
+
const data: JSONObject = widget.data as JSONObject;
|
|
63
|
+
expect(data["xIsTime"]).toBe(true);
|
|
64
|
+
expect(data["unit"]).toBe("%");
|
|
65
|
+
expect(data["valueLabel"]).toBe("CPU");
|
|
66
|
+
expect(data["series"]).toBe(series);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("bars() defaults stacked and xIsTime to false", () => {
|
|
70
|
+
const series: Array<AIChatWidgetSeries> = [
|
|
71
|
+
{ name: "logs", points: [] } as unknown as AIChatWidgetSeries,
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
const widget: AIChatWidget = WidgetBuilder.bars({
|
|
75
|
+
title: "Log volume",
|
|
76
|
+
series,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(widget.type).toBe(AIChatWidgetType.BarChart);
|
|
80
|
+
const data: JSONObject = widget.data as JSONObject;
|
|
81
|
+
expect(data["stacked"]).toBe(false);
|
|
82
|
+
expect(data["xIsTime"]).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("bars() honors explicit stacked and xIsTime flags", () => {
|
|
86
|
+
const widget: AIChatWidget = WidgetBuilder.bars({
|
|
87
|
+
title: "Log volume by severity",
|
|
88
|
+
series: [],
|
|
89
|
+
stacked: true,
|
|
90
|
+
xIsTime: true,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const data: JSONObject = widget.data as JSONObject;
|
|
94
|
+
expect(data["stacked"]).toBe(true);
|
|
95
|
+
expect(data["xIsTime"]).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("bars() keeps a `false` flag rather than replacing it with the default", () => {
|
|
99
|
+
/*
|
|
100
|
+
* Regression guard for the `?? false` nullish coalescing: an explicit
|
|
101
|
+
* false must survive, and is indistinguishable from the default here —
|
|
102
|
+
* the point is that passing false does not throw or flip to true.
|
|
103
|
+
*/
|
|
104
|
+
const widget: AIChatWidget = WidgetBuilder.bars({
|
|
105
|
+
title: "x",
|
|
106
|
+
series: [],
|
|
107
|
+
stacked: false,
|
|
108
|
+
xIsTime: false,
|
|
109
|
+
});
|
|
110
|
+
const data: JSONObject = widget.data as JSONObject;
|
|
111
|
+
expect(data["stacked"]).toBe(false);
|
|
112
|
+
expect(data["xIsTime"]).toBe(false);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("traceWaterfall() carries spans and total duration", () => {
|
|
116
|
+
const spans: Array<AIChatWidgetSpan> = [
|
|
117
|
+
{ name: "GET /", durationMs: 12 } as unknown as AIChatWidgetSpan,
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
const widget: AIChatWidget = WidgetBuilder.traceWaterfall({
|
|
121
|
+
title: "Trace",
|
|
122
|
+
spans,
|
|
123
|
+
totalDurationMs: 120,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
expect(widget.type).toBe(AIChatWidgetType.TraceWaterfall);
|
|
127
|
+
const data: JSONObject = widget.data as JSONObject;
|
|
128
|
+
expect(data["spans"]).toBe(spans);
|
|
129
|
+
expect(data["totalDurationMs"]).toBe(120);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("incidentList / alertList / exceptionList wrap items under the right type", () => {
|
|
133
|
+
const items: Array<JSONObject> = [{ id: "1" }];
|
|
134
|
+
|
|
135
|
+
const incident: AIChatWidget = WidgetBuilder.incidentList({
|
|
136
|
+
title: "Open incidents",
|
|
137
|
+
items,
|
|
138
|
+
});
|
|
139
|
+
expect(incident.type).toBe(AIChatWidgetType.IncidentList);
|
|
140
|
+
expect((incident.data as JSONObject)["items"]).toBe(items);
|
|
141
|
+
|
|
142
|
+
const alert: AIChatWidget = WidgetBuilder.alertList({
|
|
143
|
+
title: "Open alerts",
|
|
144
|
+
items,
|
|
145
|
+
});
|
|
146
|
+
expect(alert.type).toBe(AIChatWidgetType.AlertList);
|
|
147
|
+
expect((alert.data as JSONObject)["items"]).toBe(items);
|
|
148
|
+
|
|
149
|
+
const exception: AIChatWidget = WidgetBuilder.exceptionList({
|
|
150
|
+
title: "Exceptions",
|
|
151
|
+
items,
|
|
152
|
+
});
|
|
153
|
+
expect(exception.type).toBe(AIChatWidgetType.ExceptionList);
|
|
154
|
+
expect((exception.data as JSONObject)["items"]).toBe(items);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("resourceCard() maps its heading/subheading/fields", () => {
|
|
158
|
+
const fields: Array<{ label: string; value: string }> = [
|
|
159
|
+
{ label: "State", value: "Investigating" },
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
const widget: AIChatWidget = WidgetBuilder.resourceCard({
|
|
163
|
+
title: "Incident",
|
|
164
|
+
resourceType: "Incident",
|
|
165
|
+
heading: "API is down",
|
|
166
|
+
subheading: "SEV1",
|
|
167
|
+
fields,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
expect(widget.type).toBe(AIChatWidgetType.ResourceCard);
|
|
171
|
+
const data: JSONObject = widget.data as JSONObject;
|
|
172
|
+
expect(data["resourceType"]).toBe("Incident");
|
|
173
|
+
expect(data["heading"]).toBe("API is down");
|
|
174
|
+
expect(data["subheading"]).toBe("SEV1");
|
|
175
|
+
expect(data["fields"]).toBe(fields);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("stats() builds StatCards", () => {
|
|
179
|
+
const stats: Array<AIChatWidgetStat> = [
|
|
180
|
+
{ label: "Errors", value: "42" } as unknown as AIChatWidgetStat,
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
const widget: AIChatWidget = WidgetBuilder.stats({
|
|
184
|
+
title: "KPIs",
|
|
185
|
+
stats,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
expect(widget.type).toBe(AIChatWidgetType.StatCards);
|
|
189
|
+
expect((widget.data as JSONObject)["stats"]).toBe(stats);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("every builder leaves id blank for the runner to mint", () => {
|
|
193
|
+
const widgets: Array<AIChatWidget> = [
|
|
194
|
+
WidgetBuilder.table({ title: "t", columns: [], rows: [] }),
|
|
195
|
+
WidgetBuilder.timeSeries({ title: "t", series: [] }),
|
|
196
|
+
WidgetBuilder.bars({ title: "t", series: [] }),
|
|
197
|
+
WidgetBuilder.incidentList({ title: "t", items: [] }),
|
|
198
|
+
WidgetBuilder.stats({ title: "t", stats: [] }),
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
for (const widget of widgets) {
|
|
202
|
+
expect(widget.id).toBe("");
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import {
|
|
2
|
+
truncateLongText,
|
|
3
|
+
truncateShortText,
|
|
4
|
+
truncateToLength,
|
|
5
|
+
} from "../../../../Server/Utils/Database/TruncateColumnValue";
|
|
6
|
+
import ColumnLength from "../../../../Types/Database/ColumnLength";
|
|
7
|
+
import { describe, expect, test } from "@jest/globals";
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* The clamp that keeps a single oversized value from aborting a 500-row
|
|
11
|
+
* bulk INSERT (issue #3006 follow-up). Everything here is about the
|
|
12
|
+
* boundary: clip one character too eagerly and legitimate values get
|
|
13
|
+
* silently corrupted; clip one too late and Postgres still raises 22001
|
|
14
|
+
* and drops the whole chunk.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
describe("truncateToLength", () => {
|
|
18
|
+
test("returns a short value untouched, by identity", () => {
|
|
19
|
+
const value: string = "nginx:1.27";
|
|
20
|
+
expect(truncateToLength(value, 100)).toBe(value);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("returns a value of exactly maxLength untouched", () => {
|
|
24
|
+
/*
|
|
25
|
+
* The off-by-one that matters most: varchar(100) accepts exactly
|
|
26
|
+
* 100 characters, so clipping at 100 would corrupt a legal value.
|
|
27
|
+
*/
|
|
28
|
+
const value: string = "a".repeat(100);
|
|
29
|
+
const result: string = truncateToLength(value, 100);
|
|
30
|
+
expect(result).toBe(value);
|
|
31
|
+
expect(result).toHaveLength(100);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("clips a value one character too long down to exactly maxLength", () => {
|
|
35
|
+
const value: string = "a".repeat(101);
|
|
36
|
+
const result: string = truncateToLength(value, 100);
|
|
37
|
+
expect(result).toHaveLength(100);
|
|
38
|
+
expect(value.startsWith(result)).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("clips a wildly oversized value down to exactly maxLength", () => {
|
|
42
|
+
const result: string = truncateToLength("b".repeat(5000), 100);
|
|
43
|
+
expect(result).toHaveLength(100);
|
|
44
|
+
expect(result).toBe("b".repeat(100));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("the clipped value is always a prefix of the original", () => {
|
|
48
|
+
const value: string =
|
|
49
|
+
"ghcr.io/some-really-long-organization-name/some-really-long-repository-name:v1.2.3-rc.4-with-a-long-build-suffix";
|
|
50
|
+
const result: string = truncateToLength(value, 40);
|
|
51
|
+
expect(value.startsWith(result)).toBe(true);
|
|
52
|
+
expect(result).toHaveLength(40);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("passes null through as null", () => {
|
|
56
|
+
expect(truncateToLength(null, 100)).toBeNull();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("normalizes undefined to null, so it binds as SQL NULL", () => {
|
|
60
|
+
expect(truncateToLength(undefined, 100)).toBeNull();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("keeps an empty string as a string, never as null", () => {
|
|
64
|
+
/*
|
|
65
|
+
* KubernetesContainer.podNamespaceKey is NOT NULL with a "" default
|
|
66
|
+
* for cluster-scoped pods — turning "" into NULL would break the
|
|
67
|
+
* insert and the unique index that covers it.
|
|
68
|
+
*/
|
|
69
|
+
const result: string = truncateToLength("", 100);
|
|
70
|
+
expect(result).toBe("");
|
|
71
|
+
expect(result).not.toBeNull();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("handles a maxLength of zero without throwing", () => {
|
|
75
|
+
expect(truncateToLength("anything", 0)).toBe("");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("multi-byte characters", () => {
|
|
79
|
+
/*
|
|
80
|
+
* Postgres counts varchar(n) in characters; JS counts string length
|
|
81
|
+
* in UTF-16 code units. Astral-plane characters (emoji, rare CJK)
|
|
82
|
+
* are two code units each, so clipping by code units is always
|
|
83
|
+
* conservative — but the cut must not land inside a surrogate pair.
|
|
84
|
+
*/
|
|
85
|
+
test("never emits more characters than the column allows", () => {
|
|
86
|
+
const value: string = "😀".repeat(200); // 400 code units, 200 chars
|
|
87
|
+
const result: string = truncateToLength(value, 100);
|
|
88
|
+
|
|
89
|
+
expect(result.length).toBeLessThanOrEqual(100);
|
|
90
|
+
expect(Array.from(result).length).toBeLessThanOrEqual(100);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("does not split a surrogate pair when the cut lands inside one", () => {
|
|
94
|
+
/*
|
|
95
|
+
* 99 ASCII + one 2-code-unit emoji = 101 code units; cut at 100
|
|
96
|
+
* would otherwise land between the emoji's two halves.
|
|
97
|
+
*/
|
|
98
|
+
const value: string = `${"a".repeat(99)}😀`;
|
|
99
|
+
const result: string = truncateToLength(value, 100);
|
|
100
|
+
|
|
101
|
+
expect(result).toBe("a".repeat(99));
|
|
102
|
+
expect(result).not.toContain("\ud83d");
|
|
103
|
+
// A lone surrogate survives a round trip through UTF-8 as U+FFFD.
|
|
104
|
+
expect(Buffer.from(result, "utf8").toString("utf8")).toBe(result);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("keeps a whole surrogate pair when it fits exactly", () => {
|
|
108
|
+
const value: string = `${"a".repeat(98)}😀b`; // 101 code units
|
|
109
|
+
const result: string = truncateToLength(value, 100);
|
|
110
|
+
|
|
111
|
+
expect(result).toBe(`${"a".repeat(98)}😀`);
|
|
112
|
+
expect(result).toHaveLength(100);
|
|
113
|
+
expect(Buffer.from(result, "utf8").toString("utf8")).toBe(result);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("leaves a multi-byte value that fits completely alone", () => {
|
|
117
|
+
const value: string = "café-über-日本語";
|
|
118
|
+
expect(truncateToLength(value, 100)).toBe(value);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe("truncateShortText", () => {
|
|
124
|
+
test("clamps at ColumnLength.ShortText", () => {
|
|
125
|
+
expect(truncateShortText("a".repeat(500))).toHaveLength(
|
|
126
|
+
ColumnLength.ShortText,
|
|
127
|
+
);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("ColumnLength.ShortText is still 100, the varchar width it mirrors", () => {
|
|
131
|
+
expect(ColumnLength.ShortText).toBe(100);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("agrees with truncateToLength at the same bound", () => {
|
|
135
|
+
const value: string = "z".repeat(250);
|
|
136
|
+
expect(truncateShortText(value)).toBe(
|
|
137
|
+
truncateToLength(value, ColumnLength.ShortText),
|
|
138
|
+
);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("passes null and short values straight through", () => {
|
|
142
|
+
expect(truncateShortText(null)).toBeNull();
|
|
143
|
+
expect(truncateShortText("osd.3")).toBe("osd.3");
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe("truncateLongText", () => {
|
|
148
|
+
test("clamps at ColumnLength.LongText", () => {
|
|
149
|
+
expect(truncateLongText("a".repeat(5000))).toHaveLength(
|
|
150
|
+
ColumnLength.LongText,
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test("ColumnLength.LongText is still 500, the varchar width it mirrors", () => {
|
|
155
|
+
expect(ColumnLength.LongText).toBe(500);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("accepts a 253-character Kubernetes DNS-subdomain name untouched", () => {
|
|
159
|
+
/*
|
|
160
|
+
* The whole point of widening the name columns: the longest name
|
|
161
|
+
* Kubernetes itself will ever produce has to pass through unclipped.
|
|
162
|
+
*/
|
|
163
|
+
const podName: string = "p".repeat(253);
|
|
164
|
+
expect(truncateLongText(podName)).toBe(podName);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("is strictly more permissive than truncateShortText", () => {
|
|
168
|
+
const value: string = "c".repeat(400);
|
|
169
|
+
expect(truncateLongText(value)).toBe(value);
|
|
170
|
+
expect(truncateShortText(value)).toHaveLength(ColumnLength.ShortText);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("passes null and short values straight through", () => {
|
|
174
|
+
expect(truncateLongText(null)).toBeNull();
|
|
175
|
+
expect(truncateLongText("ghcr.io/acme/api:1.2.3")).toBe(
|
|
176
|
+
"ghcr.io/acme/api:1.2.3",
|
|
177
|
+
);
|
|
178
|
+
});
|
|
179
|
+
});
|