@oneuptime/common 12.0.1 → 12.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Models/DatabaseModels/Host.ts +12 -4
- package/Models/DatabaseModels/KubernetesContainer.ts +5 -4
- package/Models/DatabaseModels/KubernetesResource.ts +8 -7
- package/Models/DatabaseModels/PodmanResource.ts +6 -6
- package/Models/DatabaseModels/ServiceLevelObjective.ts +91 -0
- package/Models/DatabaseModels/StatusPage.ts +95 -0
- package/Server/API/TeamMemberAPI.ts +85 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.ts +84 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.ts +131 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Middleware/ProjectAuthorization.ts +66 -4
- package/Server/Services/BillingService.ts +600 -51
- package/Server/Services/CephClusterService.ts +47 -5
- package/Server/Services/CephResourceService.ts +43 -4
- package/Server/Services/CloudResourceService.ts +47 -5
- package/Server/Services/DatabaseService.ts +87 -6
- package/Server/Services/DockerHostService.ts +47 -5
- package/Server/Services/DockerResourceService.ts +62 -8
- package/Server/Services/DockerSwarmClusterService.ts +48 -5
- package/Server/Services/DockerSwarmResourceService.ts +53 -4
- package/Server/Services/HostService.ts +48 -5
- package/Server/Services/IoTDeviceService.ts +1 -17
- package/Server/Services/IoTFleetService.ts +47 -5
- package/Server/Services/KubernetesClusterService.ts +48 -5
- package/Server/Services/KubernetesContainerService.ts +50 -9
- package/Server/Services/KubernetesResourceService.ts +53 -7
- package/Server/Services/LabelService.ts +106 -1
- package/Server/Services/MonitorService.ts +57 -0
- package/Server/Services/PodmanHostService.ts +47 -5
- package/Server/Services/PodmanResourceService.ts +62 -8
- package/Server/Services/ProjectService.ts +165 -35
- package/Server/Services/ProxmoxClusterService.ts +47 -5
- package/Server/Services/ProxmoxResourceService.ts +43 -4
- package/Server/Services/RumApplicationService.ts +48 -5
- package/Server/Services/ServerlessFunctionService.ts +48 -5
- package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +426 -0
- package/Server/Services/ServiceLevelObjectiveService.ts +39 -0
- package/Server/Services/StatusPageService.ts +95 -36
- package/Server/Services/StatusPageSubscriberNotificationTemplateService.ts +18 -1
- package/Server/Services/StatusPageSubscriberService.ts +3 -0
- package/Server/Types/Database/JSONColumnQuery.ts +621 -0
- package/Server/Types/Database/Permissions/PublicPermission.ts +9 -2
- package/Server/Types/Database/QueryHelper.ts +17 -32
- package/Server/Utils/Database/TruncateColumnValue.ts +84 -0
- package/Tests/App/Dashboard/UsersTableGroupsByPerson.test.tsx +448 -0
- package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +704 -0
- package/Tests/Server/API/TeamMemberRemoveUserFromProjectAPI.test.ts +303 -0
- package/Tests/Server/Middleware/MasterAdminAuthorization.test.ts +350 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyHeader.test.ts +362 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +687 -0
- package/Tests/Server/Services/BillingService.test.ts +78 -3
- package/Tests/Server/Services/BillingServiceChangePlanCancel.test.ts +645 -0
- package/Tests/Server/Services/BillingServiceTrialPlanChange.test.ts +1005 -0
- package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +503 -0
- package/Tests/Server/Services/HostIpAddressesColumnWidth.test.ts +198 -0
- package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +510 -0
- package/Tests/Server/Services/InventoryResourceNameColumnWidth.test.ts +500 -0
- package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +586 -0
- package/Tests/Server/Services/InventoryUpsertTruncationGuard.test.ts +1242 -0
- package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +650 -0
- package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +1 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +387 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +770 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +130 -0
- package/Tests/Server/Services/SloMonitorLabelRuleHooks.test.ts +352 -0
- package/Tests/Server/Services/StatusPageSubscriberReport.test.ts +29 -13
- package/Tests/Server/TestingUtils/Services/BillingServiceHelper.ts +69 -8
- package/Tests/Server/Types/Database/JSONColumnQuery.test.ts +593 -0
- package/Tests/Server/Types/Database/Permissions/PublicPermission.test.ts +495 -0
- package/Tests/Server/Types/Database/QueryUtil.test.ts +113 -0
- package/Tests/Server/Utils/Database/TruncateColumnValue.test.ts +179 -0
- package/Tests/Types/Events/Recurring.test.ts +157 -0
- package/Tests/UI/Components/ModelTable/ModelTableGroupsProjectUsers.test.tsx +375 -0
- package/Tests/UI/Components/ModelTable/useCustomFieldColumns.test.tsx +196 -0
- package/Tests/UI/Utils/ProjectUsersModelAPI.test.ts +739 -0
- package/Tests/UI/Utils/TeamMembersByUser.test.ts +633 -0
- package/Tests/Utils/StatusPage/ReportPeriod.test.ts +218 -0
- package/Tests/Utils/Telemetry/HostIpAddresses.test.ts +291 -0
- package/Types/CustomField/CustomFieldDefinition.ts +24 -0
- package/Types/Date.ts +112 -0
- package/Types/Events/Recurring.ts +99 -1
- package/Types/StatusPage/StatusPageReport.ts +11 -0
- package/Types/StatusPage/StatusPageReportPeriodType.ts +21 -0
- package/UI/Components/ModelTable/CustomFieldColumns.tsx +2 -6
- package/UI/Components/ModelTable/useCustomFieldColumns.ts +21 -2
- package/UI/Utils/ModelAPI/ProjectUsersModelAPI.ts +305 -0
- package/UI/Utils/TeamMembersByUser.ts +302 -0
- package/Utils/StatusPage/ReportPeriod.ts +236 -0
- package/Utils/Telemetry/HostIpAddresses.ts +98 -0
- package/build/dist/Models/DatabaseModels/Host.js +12 -4
- package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesContainer.js +4 -4
- package/build/dist/Models/DatabaseModels/KubernetesContainer.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesResource.js +7 -7
- package/build/dist/Models/DatabaseModels/KubernetesResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/PodmanResource.js +6 -6
- package/build/dist/Models/DatabaseModels/PodmanResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js +87 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjective.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +98 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/TeamMemberAPI.js +57 -2
- package/build/dist/Server/API/TeamMemberAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js +45 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785900000000-AddSloMonitorLabelRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js +31 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785915638551-IncreaseHostIpAddressesLength.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js +90 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930000000-WidenInventoryResourceNameColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785930709405-AddStatusPageReportPeriod.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +59 -4
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Services/BillingService.js +466 -36
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +42 -5
- package/build/dist/Server/Services/CephClusterService.js.map +1 -1
- package/build/dist/Server/Services/CephResourceService.js +28 -3
- package/build/dist/Server/Services/CephResourceService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceService.js +42 -5
- package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +69 -2
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +42 -5
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/DockerResourceService.js +34 -4
- package/build/dist/Server/Services/DockerResourceService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js +43 -5
- package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmResourceService.js +28 -3
- package/build/dist/Server/Services/DockerSwarmResourceService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +43 -5
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/IoTDeviceService.js +1 -8
- package/build/dist/Server/Services/IoTDeviceService.js.map +1 -1
- package/build/dist/Server/Services/IoTFleetService.js +42 -5
- package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +43 -5
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesContainerService.js +29 -3
- package/build/dist/Server/Services/KubernetesContainerService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesResourceService.js +32 -4
- package/build/dist/Server/Services/KubernetesResourceService.js.map +1 -1
- package/build/dist/Server/Services/LabelService.js +92 -0
- package/build/dist/Server/Services/LabelService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +47 -0
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostService.js +42 -5
- package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
- package/build/dist/Server/Services/PodmanResourceService.js +34 -4
- package/build/dist/Server/Services/PodmanResourceService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +135 -21
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js +42 -5
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxResourceService.js +28 -3
- package/build/dist/Server/Services/ProxmoxResourceService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationService.js +43 -5
- package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionService.js +43 -5
- package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +347 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js +33 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +69 -46
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js +17 -1
- package/build/dist/Server/Services/StatusPageSubscriberNotificationTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +3 -0
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Types/Database/JSONColumnQuery.js +402 -0
- package/build/dist/Server/Types/Database/JSONColumnQuery.js.map +1 -0
- package/build/dist/Server/Types/Database/Permissions/PublicPermission.js +9 -2
- package/build/dist/Server/Types/Database/Permissions/PublicPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/QueryHelper.js +17 -27
- package/build/dist/Server/Types/Database/QueryHelper.js.map +1 -1
- package/build/dist/Server/Utils/Database/TruncateColumnValue.js +32 -0
- package/build/dist/Server/Utils/Database/TruncateColumnValue.js.map +1 -0
- package/build/dist/Types/CustomField/CustomFieldDefinition.js +2 -0
- package/build/dist/Types/CustomField/CustomFieldDefinition.js.map +1 -0
- package/build/dist/Types/Date.js +68 -0
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Events/Recurring.js +63 -0
- package/build/dist/Types/Events/Recurring.js.map +1 -1
- package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js +22 -0
- package/build/dist/Types/StatusPage/StatusPageReportPeriodType.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +18 -2
- package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js +213 -0
- package/build/dist/UI/Utils/ModelAPI/ProjectUsersModelAPI.js.map +1 -0
- package/build/dist/UI/Utils/TeamMembersByUser.js +195 -0
- package/build/dist/UI/Utils/TeamMembersByUser.js.map +1 -0
- package/build/dist/Utils/StatusPage/ReportPeriod.js +131 -0
- package/build/dist/Utils/StatusPage/ReportPeriod.js.map +1 -0
- package/build/dist/Utils/Telemetry/HostIpAddresses.js +82 -0
- package/build/dist/Utils/Telemetry/HostIpAddresses.js.map +1 -0
- package/jest.config.json +2 -0
- package/package.json +1 -1
- package/tsconfig.json +12 -1
|
@@ -0,0 +1,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
|
+
});
|
|
@@ -326,6 +326,163 @@ describe("Recurring", () => {
|
|
|
326
326
|
});
|
|
327
327
|
});
|
|
328
328
|
|
|
329
|
+
/*
|
|
330
|
+
* getNextDate approximates a month as 30.4375 days, which walks a monthly
|
|
331
|
+
* schedule backwards through the calendar (1 Jul -> 31 Jul -> 30 Aug) and
|
|
332
|
+
* eventually skips a month outright. getNextDateAfter steps in whole calendar
|
|
333
|
+
* units instead, which is what a status page report scheduled for "the 1st of
|
|
334
|
+
* every month" needs.
|
|
335
|
+
*/
|
|
336
|
+
describe("getNextDateAfter", () => {
|
|
337
|
+
test("keeps a monthly schedule on the same day of the month", () => {
|
|
338
|
+
const start: Date = OneUptimeDate.fromString("2026-07-01T09:00:00.000Z");
|
|
339
|
+
const recurring: Recurring = makeRecurring(EventInterval.Month, 1);
|
|
340
|
+
|
|
341
|
+
let next: Date = Recurring.getNextDateAfter({
|
|
342
|
+
startDate: start,
|
|
343
|
+
recurring: recurring,
|
|
344
|
+
afterDate: OneUptimeDate.fromString("2026-07-01T09:00:01.000Z"),
|
|
345
|
+
timezone: "UTC",
|
|
346
|
+
});
|
|
347
|
+
expect(next.toISOString()).toBe("2026-08-01T09:00:00.000Z");
|
|
348
|
+
|
|
349
|
+
next = Recurring.getNextDateAfter({
|
|
350
|
+
startDate: next,
|
|
351
|
+
recurring: recurring,
|
|
352
|
+
afterDate: OneUptimeDate.fromString("2026-08-01T09:00:01.000Z"),
|
|
353
|
+
timezone: "UTC",
|
|
354
|
+
});
|
|
355
|
+
expect(next.toISOString()).toBe("2026-09-01T09:00:00.000Z");
|
|
356
|
+
|
|
357
|
+
next = Recurring.getNextDateAfter({
|
|
358
|
+
startDate: next,
|
|
359
|
+
recurring: recurring,
|
|
360
|
+
afterDate: OneUptimeDate.fromString("2026-09-01T09:00:01.000Z"),
|
|
361
|
+
timezone: "UTC",
|
|
362
|
+
});
|
|
363
|
+
expect(next.toISOString()).toBe("2026-10-01T09:00:00.000Z");
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test("never skips a month, which the millisecond approximation does", () => {
|
|
367
|
+
const start: Date = OneUptimeDate.fromString("2026-01-01T09:00:00.000Z");
|
|
368
|
+
|
|
369
|
+
const next: Date = Recurring.getNextDateAfter({
|
|
370
|
+
startDate: start,
|
|
371
|
+
recurring: makeRecurring(EventInterval.Month, 1),
|
|
372
|
+
// A status page left alone for most of a year.
|
|
373
|
+
afterDate: OneUptimeDate.fromString("2026-11-15T00:00:00.000Z"),
|
|
374
|
+
timezone: "UTC",
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
expect(next.toISOString()).toBe("2026-12-01T09:00:00.000Z");
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test("holds the local time of day across a DST transition", () => {
|
|
381
|
+
/*
|
|
382
|
+
* 1 Mar 2026 09:00 in New York is EST (UTC-5); 1 Apr is EDT (UTC-4).
|
|
383
|
+
* Adding a fixed number of milliseconds would land at 08:00 local.
|
|
384
|
+
*/
|
|
385
|
+
const next: Date = Recurring.getNextDateAfter({
|
|
386
|
+
startDate: OneUptimeDate.fromString("2026-03-01T14:00:00.000Z"),
|
|
387
|
+
recurring: makeRecurring(EventInterval.Month, 1),
|
|
388
|
+
afterDate: OneUptimeDate.fromString("2026-03-15T00:00:00.000Z"),
|
|
389
|
+
timezone: "America/New_York",
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
expect(next.toISOString()).toBe("2026-04-01T13:00:00.000Z");
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
test("clamps a day-of-month that the next month does not have", () => {
|
|
396
|
+
const next: Date = Recurring.getNextDateAfter({
|
|
397
|
+
startDate: OneUptimeDate.fromString("2026-01-31T09:00:00.000Z"),
|
|
398
|
+
recurring: makeRecurring(EventInterval.Month, 1),
|
|
399
|
+
afterDate: OneUptimeDate.fromString("2026-02-01T00:00:00.000Z"),
|
|
400
|
+
timezone: "UTC",
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
expect(next.toISOString()).toBe("2026-02-28T09:00:00.000Z");
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
test("returns a start date that has not happened yet unchanged", () => {
|
|
407
|
+
const start: Date = OneUptimeDate.fromString("2026-08-01T09:00:00.000Z");
|
|
408
|
+
|
|
409
|
+
const next: Date = Recurring.getNextDateAfter({
|
|
410
|
+
startDate: start,
|
|
411
|
+
recurring: makeRecurring(EventInterval.Month, 1),
|
|
412
|
+
afterDate: OneUptimeDate.fromString("2026-07-15T00:00:00.000Z"),
|
|
413
|
+
timezone: "UTC",
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
expect(next.getTime()).toBe(start.getTime());
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
test("advances by whole intervals for a multi-unit recurrence", () => {
|
|
420
|
+
const next: Date = Recurring.getNextDateAfter({
|
|
421
|
+
startDate: OneUptimeDate.fromString("2026-01-01T00:00:00.000Z"),
|
|
422
|
+
recurring: makeRecurring(EventInterval.Month, 3),
|
|
423
|
+
afterDate: OneUptimeDate.fromString("2026-05-02T00:00:00.000Z"),
|
|
424
|
+
timezone: "UTC",
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// Occurrences are 1 Jan, 1 Apr, 1 Jul - the first after 2 May is 1 Jul.
|
|
428
|
+
expect(next.toISOString()).toBe("2026-07-01T00:00:00.000Z");
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test("handles hourly, daily, weekly and yearly recurrences", () => {
|
|
432
|
+
const start: Date = OneUptimeDate.fromString("2026-01-01T00:00:00.000Z");
|
|
433
|
+
const after: Date = OneUptimeDate.fromString("2026-01-01T05:30:00.000Z");
|
|
434
|
+
|
|
435
|
+
expect(
|
|
436
|
+
Recurring.getNextDateAfter({
|
|
437
|
+
startDate: start,
|
|
438
|
+
recurring: makeRecurring(EventInterval.Hour, 2),
|
|
439
|
+
afterDate: after,
|
|
440
|
+
timezone: "UTC",
|
|
441
|
+
}).toISOString(),
|
|
442
|
+
).toBe("2026-01-01T06:00:00.000Z");
|
|
443
|
+
|
|
444
|
+
expect(
|
|
445
|
+
Recurring.getNextDateAfter({
|
|
446
|
+
startDate: start,
|
|
447
|
+
recurring: makeRecurring(EventInterval.Day, 1),
|
|
448
|
+
afterDate: after,
|
|
449
|
+
timezone: "UTC",
|
|
450
|
+
}).toISOString(),
|
|
451
|
+
).toBe("2026-01-02T00:00:00.000Z");
|
|
452
|
+
|
|
453
|
+
expect(
|
|
454
|
+
Recurring.getNextDateAfter({
|
|
455
|
+
startDate: start,
|
|
456
|
+
recurring: makeRecurring(EventInterval.Week, 1),
|
|
457
|
+
afterDate: after,
|
|
458
|
+
timezone: "UTC",
|
|
459
|
+
}).toISOString(),
|
|
460
|
+
).toBe("2026-01-08T00:00:00.000Z");
|
|
461
|
+
|
|
462
|
+
expect(
|
|
463
|
+
Recurring.getNextDateAfter({
|
|
464
|
+
startDate: start,
|
|
465
|
+
recurring: makeRecurring(EventInterval.Year, 1),
|
|
466
|
+
afterDate: after,
|
|
467
|
+
timezone: "UTC",
|
|
468
|
+
}).toISOString(),
|
|
469
|
+
).toBe("2027-01-01T00:00:00.000Z");
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
test("throws BadDataException for an unknown interval type", () => {
|
|
473
|
+
const recurring: Recurring = makeRecurring(EventInterval.Day, 1);
|
|
474
|
+
recurring.intervalType = "Fortnight" as unknown as EventInterval;
|
|
475
|
+
|
|
476
|
+
expect(() => {
|
|
477
|
+
Recurring.getNextDateAfter({
|
|
478
|
+
startDate: baseDate,
|
|
479
|
+
recurring: recurring,
|
|
480
|
+
afterDate: OneUptimeDate.getCurrentDate(),
|
|
481
|
+
});
|
|
482
|
+
}).toThrowError(BadDataException);
|
|
483
|
+
});
|
|
484
|
+
});
|
|
485
|
+
|
|
329
486
|
describe("toString", () => {
|
|
330
487
|
test("formats as count and interval type", () => {
|
|
331
488
|
expect(makeRecurring(EventInterval.Day, 1).toString()).toBe("1 Day");
|