@oneuptime/common 12.0.28 → 12.0.29
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/DashboardDomain.ts +43 -0
- package/Models/DatabaseModels/MonitorTemplate.ts +15 -3
- package/Models/DatabaseModels/StatusPageDomain.ts +43 -0
- package/Server/API/DashboardDomainAPI.ts +74 -0
- package/Server/API/MicrosoftTeamsAPI.ts +81 -180
- package/Server/API/StatusPageDomainAPI.ts +74 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.ts +43 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/ProjectAuthorization.ts +6 -1
- package/Server/Services/DashboardDomainService.ts +131 -0
- package/Server/Services/LogPipelineProcessorService.ts +88 -0
- package/Server/Services/MonitorTemplateService.ts +70 -11
- package/Server/Services/StatusPageDomainService.ts +163 -3
- package/Server/Services/StatusPagePrivateUserSessionService.ts +197 -56
- package/Server/Types/Domain.ts +17 -0
- package/Server/Utils/APIKey/AccessPermission.ts +24 -0
- package/Server/Utils/Greenlock/Greenlock.ts +156 -58
- package/Server/Utils/LogPipelineProcessorValidation.ts +112 -0
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +1094 -45
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +153 -17
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.ts +23 -11
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.ts +0 -170
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.ts +158 -0
- package/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.ts +23 -11
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +35 -13
- package/Tests/App/Dashboard/MonitorTemplateColumnAndFacet.test.tsx +505 -0
- package/Tests/App/StatusPage/StatusPageLoginCodeBootstrap.test.ts +106 -0
- package/Tests/App/StatusPage/StatusPageLoginCodeUtil.test.ts +84 -0
- package/Tests/App/StatusPage/StatusPageRedirectSafety.test.ts +119 -0
- package/Tests/Models/MonitorTemplateMonitorNameColumn.test.ts +258 -0
- package/Tests/Server/API/CustomDomainReissueSslAPI.test.ts +469 -0
- package/Tests/Server/API/MicrosoftTeamsBotMessagesEndpoint.test.ts +729 -0
- package/Tests/Server/API/MicrosoftTeamsBotTestEndpoint.test.ts +26 -0
- package/Tests/Server/API/MicrosoftTeamsLegacyWebhookRemoval.test.ts +402 -0
- package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +4 -0
- package/Tests/Server/Services/AddCertificateReissueRequestedAtToDomainsMigration.test.ts +287 -0
- package/Tests/Server/Services/CustomDomainCertificateReissue.test.ts +477 -0
- package/Tests/Server/Services/LogPipelineProcessorSaveValidation.test.ts +249 -0
- package/Tests/Server/Services/MonitorTemplateServiceBulkSync.test.ts +549 -0
- package/Tests/Server/Services/MonitorTemplateServiceNetworkDeviceSync.test.ts +16 -3
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleEngineService.test.ts +57 -0
- package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +151 -15
- package/Tests/Server/Services/StatusPagePrivateUserSessionService.test.ts +587 -0
- package/Tests/Server/Types/Database/Permissions/ApiKeyFileAccess.test.ts +286 -0
- package/Tests/Server/Types/Database/Permissions/MonitorTemplateColumnAccess.test.ts +328 -0
- package/Tests/Server/Types/Database/QueryHelperFindWithSameTextAndIsNull.test.ts +83 -0
- package/Tests/Server/Utils/AI/SRE/ConfidenceSignal.test.ts +377 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +16 -1
- package/Tests/Server/Utils/Greenlock/CertificateRenewalSchedule.test.ts +250 -0
- package/Tests/Server/Utils/LogPipelineProcessorValidation.test.ts +198 -0
- package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +48 -9
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsAuth.test.ts +726 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +74 -11
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsErrorHandling.test.ts +1107 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +20 -3
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +51 -11
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +1246 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsRequestContract.test.ts +961 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsResponseParsing.test.ts +653 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsActionAuthorization.test.ts +880 -0
- package/Tests/Types/API/HTTPResponse.test.ts +175 -0
- package/Tests/Types/API/Route.test.ts +36 -4
- package/Tests/Types/Database/DatabaseProperty.test.ts +171 -0
- package/Tests/Types/DateLocalShortDateTimeString.test.ts +518 -0
- package/Tests/Types/DateLocalTimeString.test.ts +259 -0
- package/Tests/Types/DateUserPrefers12HourFormat.test.ts +316 -0
- package/Tests/UI/Components/LogsHistogram.test.tsx +41 -2
- package/Tests/UI/Components/LogsViewerEmptyStateTimeRange.test.tsx +212 -0
- package/Tests/UI/Components/TelemetryChartClockFormat.test.tsx +359 -0
- package/Tests/UI/Components/TimeRangePickerClockFormat.test.tsx +422 -0
- package/Tests/UI/Components/TimeRangePickerDropdown.test.tsx +146 -8
- package/Tests/UI/MicrosoftTeams/MicrosoftTeamsMessagingEndpointGuidance.test.tsx +520 -0
- package/Tests/UI/Utils/Navigation.test.ts +82 -0
- package/Tests/Utils/Array.test.ts +100 -0
- package/Tests/Utils/CertificateReissue.test.ts +225 -0
- package/Tests/Utils/Grok/Grok.test.ts +492 -0
- package/Tests/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.test.ts +181 -13
- package/Types/API/Route.ts +20 -5
- package/Types/Date.ts +143 -13
- package/Types/Rum/SessionReplay.ts +62 -1
- package/UI/Components/Date/TimeRangePickerDropdown.tsx +9 -5
- package/UI/Components/LogsViewer/LogsViewer.tsx +12 -7
- package/UI/Components/LogsViewer/components/HistogramTooltip.tsx +18 -17
- package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +21 -28
- package/UI/Components/LogsViewer/components/LogsHistogram.tsx +2 -4
- package/UI/Components/TelemetryViewer/components/TelemetryHistogram.tsx +2 -4
- package/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.tsx +17 -16
- package/UI/Utils/Navigation.ts +42 -0
- package/Utils/Array.ts +37 -0
- package/Utils/CertificateReissue.ts +128 -0
- package/Utils/Grok/Grok.ts +461 -0
- package/Utils/Grok/GrokPatterns.ts +118 -0
- package/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.ts +58 -6
- package/build/dist/Models/DatabaseModels/DashboardDomain.js +44 -0
- package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js +17 -4
- package/build/dist/Models/DatabaseModels/MonitorTemplate.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js +44 -0
- package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
- package/build/dist/Server/API/DashboardDomainAPI.js +43 -1
- package/build/dist/Server/API/DashboardDomainAPI.js.map +1 -1
- package/build/dist/Server/API/MicrosoftTeamsAPI.js +74 -118
- package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
- package/build/dist/Server/API/StatusPageDomainAPI.js +43 -1
- package/build/dist/Server/API/StatusPageDomainAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790200000000-AllowNullMonitorNameOnMonitorTemplate.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js +28 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1790300000000-AddCertificateReissueRequestedAtToDomains.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +6 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Services/DashboardDomainService.js +104 -0
- package/build/dist/Server/Services/DashboardDomainService.js.map +1 -1
- package/build/dist/Server/Services/LogPipelineProcessorService.js +65 -0
- package/build/dist/Server/Services/LogPipelineProcessorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +57 -11
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageDomainService.js +132 -3
- package/build/dist/Server/Services/StatusPageDomainService.js.map +1 -1
- package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js +112 -32
- package/build/dist/Server/Services/StatusPagePrivateUserSessionService.js.map +1 -1
- package/build/dist/Server/Types/Domain.js +16 -0
- package/build/dist/Server/Types/Domain.js.map +1 -1
- package/build/dist/Server/Utils/APIKey/AccessPermission.js +24 -0
- package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
- package/build/dist/Server/Utils/Greenlock/Greenlock.js +110 -35
- package/build/dist/Server/Utils/Greenlock/Greenlock.js.map +1 -1
- package/build/dist/Server/Utils/LogPipelineProcessorValidation.js +77 -0
- package/build/dist/Server/Utils/LogPipelineProcessorValidation.js.map +1 -0
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +777 -36
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +108 -17
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js +17 -6
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Alert.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js +0 -159
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Auth.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js +128 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Authorization.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js +17 -6
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/Actions/Incident.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -13
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/API/Route.js +16 -5
- package/build/dist/Types/API/Route.js.map +1 -1
- package/build/dist/Types/Date.js +115 -13
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Rum/SessionReplay.js +38 -0
- package/build/dist/Types/Rum/SessionReplay.js.map +1 -1
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js +9 -5
- package/build/dist/UI/Components/Date/TimeRangePickerDropdown.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js +12 -7
- package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js +15 -14
- package/build/dist/UI/Components/LogsViewer/components/HistogramTooltip.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +17 -22
- package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js +2 -4
- package/build/dist/UI/Components/LogsViewer/components/LogsHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js +2 -4
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogram.js.map +1 -1
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js +15 -14
- package/build/dist/UI/Components/TelemetryViewer/components/TelemetryHistogramTooltip.js.map +1 -1
- package/build/dist/UI/Utils/Navigation.js +30 -0
- package/build/dist/UI/Utils/Navigation.js.map +1 -1
- package/build/dist/Utils/Array.js +25 -0
- package/build/dist/Utils/Array.js.map +1 -1
- package/build/dist/Utils/CertificateReissue.js +97 -0
- package/build/dist/Utils/CertificateReissue.js.map +1 -0
- package/build/dist/Utils/Grok/Grok.js +324 -0
- package/build/dist/Utils/Grok/Grok.js.map +1 -0
- package/build/dist/Utils/Grok/GrokPatterns.js +112 -0
- package/build/dist/Utils/Grok/GrokPatterns.js.map +1 -0
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js +63 -3
- package/build/dist/Utils/Monitor/NetworkDeviceMonitorTemplateUtil.js.map +1 -1
- package/package.json +1 -1
- package/UI/Utils/JsonWebToken.ts +0 -14
- package/build/dist/UI/Utils/JsonWebToken.js +0 -10
- package/build/dist/UI/Utils/JsonWebToken.js.map +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import ArrayUtil from "../../Utils/Array";
|
|
2
2
|
import ObjectID from "../../Types/ObjectID";
|
|
3
|
+
import Sleep from "../../Types/Sleep";
|
|
3
4
|
|
|
4
5
|
describe("ArrayUtil", () => {
|
|
5
6
|
describe("removeDuplicates", () => {
|
|
@@ -153,4 +154,103 @@ describe("ArrayUtil", () => {
|
|
|
153
154
|
expect(ArrayUtil.distinctByFieldName([], "group")).toEqual([]);
|
|
154
155
|
});
|
|
155
156
|
});
|
|
157
|
+
describe("forEachWithConcurrency", () => {
|
|
158
|
+
it("runs every item exactly once", async () => {
|
|
159
|
+
const items: Array<number> = [1, 2, 3, 4, 5, 6, 7];
|
|
160
|
+
const seen: Array<number> = [];
|
|
161
|
+
|
|
162
|
+
await ArrayUtil.forEachWithConcurrency(
|
|
163
|
+
items,
|
|
164
|
+
3,
|
|
165
|
+
async (item: number): Promise<void> => {
|
|
166
|
+
seen.push(item);
|
|
167
|
+
},
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
expect(seen.sort()).toEqual(items);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("never exceeds the requested concurrency", async () => {
|
|
174
|
+
let inFlight: number = 0;
|
|
175
|
+
let peak: number = 0;
|
|
176
|
+
|
|
177
|
+
await ArrayUtil.forEachWithConcurrency(
|
|
178
|
+
Array.from({ length: 30 }, (_v: unknown, i: number) => {
|
|
179
|
+
return i;
|
|
180
|
+
}),
|
|
181
|
+
4,
|
|
182
|
+
async (): Promise<void> => {
|
|
183
|
+
inFlight++;
|
|
184
|
+
peak = Math.max(peak, inFlight);
|
|
185
|
+
await Sleep.sleep(1);
|
|
186
|
+
inFlight--;
|
|
187
|
+
},
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
expect(peak).toBeLessThanOrEqual(4);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it("actually overlaps work rather than awaiting one at a time", async () => {
|
|
194
|
+
let inFlight: number = 0;
|
|
195
|
+
let peak: number = 0;
|
|
196
|
+
|
|
197
|
+
await ArrayUtil.forEachWithConcurrency(
|
|
198
|
+
Array.from({ length: 10 }, (_v: unknown, i: number) => {
|
|
199
|
+
return i;
|
|
200
|
+
}),
|
|
201
|
+
5,
|
|
202
|
+
async (): Promise<void> => {
|
|
203
|
+
inFlight++;
|
|
204
|
+
peak = Math.max(peak, inFlight);
|
|
205
|
+
await Sleep.sleep(5);
|
|
206
|
+
inFlight--;
|
|
207
|
+
},
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
expect(peak).toBeGreaterThan(1);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("starts items in order, so a caller that sorted by urgency is respected", async () => {
|
|
214
|
+
const started: Array<number> = [];
|
|
215
|
+
|
|
216
|
+
await ArrayUtil.forEachWithConcurrency(
|
|
217
|
+
[0, 1, 2, 3, 4, 5],
|
|
218
|
+
2,
|
|
219
|
+
async (item: number): Promise<void> => {
|
|
220
|
+
started.push(item);
|
|
221
|
+
await Sleep.sleep(1);
|
|
222
|
+
},
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
expect(started.slice(0, 2)).toEqual([0, 1]);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("does no work and does not hang on an empty array", async () => {
|
|
229
|
+
let calls: number = 0;
|
|
230
|
+
|
|
231
|
+
await ArrayUtil.forEachWithConcurrency(
|
|
232
|
+
[] as Array<number>,
|
|
233
|
+
5,
|
|
234
|
+
async (): Promise<void> => {
|
|
235
|
+
calls++;
|
|
236
|
+
},
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
expect(calls).toBe(0);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("treats a concurrency below one as one rather than doing nothing", async () => {
|
|
243
|
+
const seen: Array<number> = [];
|
|
244
|
+
|
|
245
|
+
await ArrayUtil.forEachWithConcurrency(
|
|
246
|
+
[1, 2, 3],
|
|
247
|
+
0,
|
|
248
|
+
async (item: number): Promise<void> => {
|
|
249
|
+
seen.push(item);
|
|
250
|
+
},
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
expect(seen).toEqual([1, 2, 3]);
|
|
254
|
+
});
|
|
255
|
+
});
|
|
156
256
|
});
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import CertificateReissueUtil from "../../Utils/CertificateReissue";
|
|
2
|
+
import OneUptimeDate from "../../Types/Date";
|
|
3
|
+
import { describe, expect, test } from "@jest/globals";
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* The cooldown behind the dashboard's "Reissue SSL" button.
|
|
7
|
+
*
|
|
8
|
+
* Everything here is pure, and deliberately so: the API refuses a request
|
|
9
|
+
* using these functions and the dashboard greys the button out using the very
|
|
10
|
+
* same ones, so the two can never disagree about whether a domain is allowed
|
|
11
|
+
* to order right now. What the tests pin is the property the whole feature
|
|
12
|
+
* rests on - that the boundary the SERVER claims a row with
|
|
13
|
+
* (getCooldownCutoff, which becomes a SQL comparison) and the boundary the
|
|
14
|
+
* CLIENT renders (isInCooldown) are the same boundary.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const NOW: Date = OneUptimeDate.fromString("2026-08-31T12:00:00.000Z");
|
|
18
|
+
|
|
19
|
+
type HoursAgoFunction = (hours: number) => Date;
|
|
20
|
+
|
|
21
|
+
const hoursAgo: HoursAgoFunction = (hours: number): Date => {
|
|
22
|
+
return OneUptimeDate.addRemoveHours(NOW, -hours);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
describe("CertificateReissueUtil cooldown window", () => {
|
|
26
|
+
test("a domain that has never been reissued is not cooling down", () => {
|
|
27
|
+
expect(CertificateReissueUtil.isInCooldown(null, NOW)).toBe(false);
|
|
28
|
+
expect(CertificateReissueUtil.isInCooldown(undefined, NOW)).toBe(false);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("a reissue requested a moment ago is cooling down", () => {
|
|
32
|
+
expect(CertificateReissueUtil.isInCooldown(hoursAgo(0), NOW)).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("a reissue requested just inside the window is still cooling down", () => {
|
|
36
|
+
expect(
|
|
37
|
+
CertificateReissueUtil.isInCooldown(
|
|
38
|
+
hoursAgo(CertificateReissueUtil.COOLDOWN_IN_HOURS - 1),
|
|
39
|
+
NOW,
|
|
40
|
+
),
|
|
41
|
+
).toBe(true);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("a reissue requested exactly a cooldown ago is no longer cooling down", () => {
|
|
45
|
+
expect(
|
|
46
|
+
CertificateReissueUtil.isInCooldown(
|
|
47
|
+
hoursAgo(CertificateReissueUtil.COOLDOWN_IN_HOURS),
|
|
48
|
+
NOW,
|
|
49
|
+
),
|
|
50
|
+
).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("a reissue requested long ago is not cooling down", () => {
|
|
54
|
+
expect(
|
|
55
|
+
CertificateReissueUtil.isInCooldown(
|
|
56
|
+
hoursAgo(CertificateReissueUtil.COOLDOWN_IN_HOURS * 10),
|
|
57
|
+
NOW,
|
|
58
|
+
),
|
|
59
|
+
).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/*
|
|
63
|
+
* A stamp in the future means a clock moved backwards, not that the caller
|
|
64
|
+
* has earned a free order. Treating it as "not cooling down" would turn a
|
|
65
|
+
* clock skew into an unthrottled button.
|
|
66
|
+
*/
|
|
67
|
+
test("a stamp in the future is treated as cooling down", () => {
|
|
68
|
+
expect(
|
|
69
|
+
CertificateReissueUtil.isInCooldown(
|
|
70
|
+
OneUptimeDate.addRemoveHours(NOW, 6),
|
|
71
|
+
NOW,
|
|
72
|
+
),
|
|
73
|
+
).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("the cooldown is at least a day, so the button cannot be a spam vector", () => {
|
|
77
|
+
expect(CertificateReissueUtil.COOLDOWN_IN_HOURS).toBeGreaterThanOrEqual(24);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe("CertificateReissueUtil.getCooldownCutoff", () => {
|
|
82
|
+
/*
|
|
83
|
+
* This is the regression that matters most. getCooldownCutoff becomes the
|
|
84
|
+
* `certificateReissueRequestedAt <= :cutoff OR IS NULL` clause the server
|
|
85
|
+
* claims a row with; isInCooldown is what the dashboard renders. If the two
|
|
86
|
+
* ever drift apart, one of them is lying to the customer - either a button
|
|
87
|
+
* that looks live and is refused, or one that looks dead and would work.
|
|
88
|
+
*/
|
|
89
|
+
test("the cutoff agrees with isInCooldown at every hour across the window", () => {
|
|
90
|
+
for (
|
|
91
|
+
let hours: number = 0;
|
|
92
|
+
hours <= CertificateReissueUtil.COOLDOWN_IN_HOURS * 2;
|
|
93
|
+
hours++
|
|
94
|
+
) {
|
|
95
|
+
const requestedAt: Date = hoursAgo(hours);
|
|
96
|
+
|
|
97
|
+
const claimableByServer: boolean = !OneUptimeDate.isAfter(
|
|
98
|
+
requestedAt,
|
|
99
|
+
CertificateReissueUtil.getCooldownCutoff(NOW),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const coolingDownForClient: boolean = CertificateReissueUtil.isInCooldown(
|
|
103
|
+
requestedAt,
|
|
104
|
+
NOW,
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
expect(claimableByServer).toBe(!coolingDownForClient);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("the cutoff is exactly one cooldown behind now", () => {
|
|
112
|
+
expect(
|
|
113
|
+
OneUptimeDate.getHoursBetweenTwoDates(
|
|
114
|
+
CertificateReissueUtil.getCooldownCutoff(NOW),
|
|
115
|
+
NOW,
|
|
116
|
+
),
|
|
117
|
+
).toBe(CertificateReissueUtil.COOLDOWN_IN_HOURS);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe("CertificateReissueUtil.getNextReissueAllowedAt", () => {
|
|
122
|
+
test("is one cooldown after the request", () => {
|
|
123
|
+
const requestedAt: Date = hoursAgo(3);
|
|
124
|
+
|
|
125
|
+
expect(
|
|
126
|
+
OneUptimeDate.getHoursBetweenTwoDates(
|
|
127
|
+
requestedAt,
|
|
128
|
+
CertificateReissueUtil.getNextReissueAllowedAt(requestedAt),
|
|
129
|
+
),
|
|
130
|
+
).toBe(CertificateReissueUtil.COOLDOWN_IN_HOURS);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe("CertificateReissueUtil.getTimeRemainingText", () => {
|
|
135
|
+
test("reports whole hours and minutes", () => {
|
|
136
|
+
/*
|
|
137
|
+
* Requested 21h30m ago, so 2h30m of a 24 hour cooldown is left.
|
|
138
|
+
*/
|
|
139
|
+
const requestedAt: Date = OneUptimeDate.addRemoveHours(
|
|
140
|
+
OneUptimeDate.fromString("2026-08-30T14:30:00.000Z"),
|
|
141
|
+
0,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
expect(CertificateReissueUtil.getTimeRemainingText(requestedAt, NOW)).toBe(
|
|
145
|
+
"2 hours 30 minutes",
|
|
146
|
+
);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("singularizes a lone hour and a lone minute", () => {
|
|
150
|
+
expect(
|
|
151
|
+
CertificateReissueUtil.getTimeRemainingText(
|
|
152
|
+
OneUptimeDate.addRemoveHours(
|
|
153
|
+
NOW,
|
|
154
|
+
-(CertificateReissueUtil.COOLDOWN_IN_HOURS - 1),
|
|
155
|
+
),
|
|
156
|
+
NOW,
|
|
157
|
+
),
|
|
158
|
+
).toBe("1 hour");
|
|
159
|
+
|
|
160
|
+
expect(
|
|
161
|
+
CertificateReissueUtil.getTimeRemainingText(
|
|
162
|
+
OneUptimeDate.addRemoveMinutes(
|
|
163
|
+
NOW,
|
|
164
|
+
-(CertificateReissueUtil.COOLDOWN_IN_HOURS * 60 - 1),
|
|
165
|
+
),
|
|
166
|
+
NOW,
|
|
167
|
+
),
|
|
168
|
+
).toBe("1 minute");
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("omits an hours part that is zero", () => {
|
|
172
|
+
const text: string = CertificateReissueUtil.getTimeRemainingText(
|
|
173
|
+
OneUptimeDate.addRemoveMinutes(
|
|
174
|
+
NOW,
|
|
175
|
+
-(CertificateReissueUtil.COOLDOWN_IN_HOURS * 60 - 20),
|
|
176
|
+
),
|
|
177
|
+
NOW,
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
expect(text).toBe("20 minutes");
|
|
181
|
+
expect(text).not.toContain("hour");
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
/*
|
|
185
|
+
* Never a negative or a bare "0 minutes": once the cooldown is over the
|
|
186
|
+
* message is not rendered at all, and the edge of the window must read as
|
|
187
|
+
* an edge rather than as a countdown that overshot.
|
|
188
|
+
*/
|
|
189
|
+
test("never counts below zero once the window has passed", () => {
|
|
190
|
+
expect(
|
|
191
|
+
CertificateReissueUtil.getTimeRemainingText(
|
|
192
|
+
hoursAgo(CertificateReissueUtil.COOLDOWN_IN_HOURS * 5),
|
|
193
|
+
NOW,
|
|
194
|
+
),
|
|
195
|
+
).toBe("less than a minute");
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
describe("CertificateReissueUtil.getCooldownMessage", () => {
|
|
200
|
+
test("tells the customer why and for how long", () => {
|
|
201
|
+
const message: string = CertificateReissueUtil.getCooldownMessage(
|
|
202
|
+
hoursAgo(1),
|
|
203
|
+
NOW,
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
expect(message).toContain("Let's Encrypt");
|
|
207
|
+
expect(message).toContain(
|
|
208
|
+
`${CertificateReissueUtil.COOLDOWN_IN_HOURS} hours`,
|
|
209
|
+
);
|
|
210
|
+
expect(message).toContain(
|
|
211
|
+
CertificateReissueUtil.getTimeRemainingText(hoursAgo(1), NOW),
|
|
212
|
+
);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test("does not leak an internal column name or id at the customer", () => {
|
|
216
|
+
const message: string = CertificateReissueUtil.getCooldownMessage(
|
|
217
|
+
hoursAgo(1),
|
|
218
|
+
NOW,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
expect(message).not.toContain("certificateReissueRequestedAt");
|
|
222
|
+
expect(message).not.toContain("undefined");
|
|
223
|
+
expect(message).not.toContain("NaN");
|
|
224
|
+
});
|
|
225
|
+
});
|