@oneuptime/common 12.0.14 → 12.0.15
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/GlobalOidc.ts +20 -0
- package/Models/DatabaseModels/GlobalSso.ts +20 -0
- package/Models/DatabaseModels/MarketingConversion.ts +19 -9
- package/Models/DatabaseModels/NetworkDeviceLinkRule.ts +46 -0
- package/Models/DatabaseModels/StatusPage.ts +7 -4
- package/Server/API/StatusPageAPI.ts +29 -4
- package/Server/EnvironmentConfig.ts +7 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.ts +40 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Middleware/SlackAuthorization.ts +43 -6
- package/Server/Middleware/UserAuthorization.ts +236 -26
- package/Server/Middleware/WhatsAppAuthorization.ts +43 -3
- package/Server/Services/GlobalOidcProjectService.ts +126 -1
- package/Server/Services/GlobalOidcService.ts +132 -0
- package/Server/Services/GlobalSsoProjectService.ts +126 -1
- package/Server/Services/GlobalSsoService.ts +132 -0
- package/Server/Services/UserEmailService.ts +4 -4
- package/Server/Utils/CronTab.ts +73 -0
- package/Server/Utils/GlobalSsoAuthorization.ts +175 -0
- package/Server/Utils/Marketing/ConversionUploadProvider.ts +35 -5
- package/Server/Utils/Marketing/Providers/GoogleAds.ts +1 -1
- package/Server/Utils/Marketing/Providers/LinkedIn.ts +1 -1
- package/Server/Utils/Marketing/Providers/Meta.ts +1 -1
- package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +1 -1
- package/Server/Utils/Marketing/Providers/Reddit.ts +1 -1
- package/Server/Utils/Monitor/Criteria/APIRequestCriteria.ts +48 -33
- package/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.ts +35 -30
- package/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.ts +33 -28
- package/Server/Utils/Monitor/Criteria/EvaluateOverTime.ts +475 -47
- package/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.ts +35 -30
- package/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.ts +34 -28
- package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +33 -28
- package/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.ts +69 -40
- package/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.ts +35 -29
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +8 -0
- package/Server/Utils/Monitor/MonitorResource.ts +161 -21
- package/Server/Utils/StatusPageContentSecurityPolicy.ts +56 -0
- package/Server/Utils/StatusPageCustomizationAccess.ts +117 -0
- package/Tests/Server/API/StatusPageCustomizationOrigin.test.ts +348 -0
- package/Tests/Server/Infrastructure/Postgres/FixTotpOtpUrlAlgorithmMigration.test.ts +9 -59
- package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +227 -0
- package/Tests/Server/Middleware/SlackAuthorization.test.ts +149 -0
- package/Tests/Server/Middleware/UserAuthorization.test.ts +27 -13
- package/Tests/Server/Middleware/UserAuthorizationGlobalSsoGovernance.test.ts +1455 -0
- package/Tests/Server/Middleware/UserAuthorizationSSOProvider.test.ts +91 -2
- package/Tests/Server/Middleware/WhatsAppAuthorization.test.ts +233 -0
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +21 -8
- package/Tests/Server/Services/GlobalSsoProviderAuthorization.test.ts +1242 -0
- package/Tests/Server/Services/MonitorResourceProbeAgreementHydration.test.ts +459 -0
- package/Tests/Server/Services/MonitorResourceProbeAgreementReuse.test.ts +10 -0
- package/Tests/Server/Types/AnalyticsDatabase/AggregateUtilBucketTimestamp.test.ts +416 -0
- package/Tests/Server/Types/Database/QueryHelperManyToManyAndEmptyValues.test.ts +551 -0
- package/Tests/Server/Types/Database/SelectUtil.test.ts +348 -0
- package/Tests/Server/Utils/CronTab.test.ts +116 -0
- package/Tests/Server/Utils/GlobalSSOToken.test.ts +581 -0
- package/Tests/Server/Utils/GlobalSsoAuthorization.test.ts +1401 -0
- package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +254 -0
- package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +1 -1
- package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaEvaluateOverTime.test.ts +508 -0
- package/Tests/Server/Utils/Monitor/Criteria/APIRequestCriteriaPortTimings.test.ts +30 -9
- package/Tests/Server/Utils/Monitor/Criteria/EvaluateOverTime.test.ts +987 -0
- package/Tests/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.test.ts +916 -0
- package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +382 -0
- package/Tests/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.test.ts +1219 -0
- package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +86 -0
- package/Tests/Server/Utils/StatusPageContentSecurityPolicy.test.ts +136 -0
- package/Tests/Server/Utils/StatusPageCustomizationAccess.test.ts +223 -0
- package/Tests/Types/Monitor/MonitorStepConfigHelpers.test.ts +165 -0
- package/Tests/Types/NetworkDevice/NetworkDeviceLinkRuleScope.test.ts +215 -0
- package/Tests/UI/Components/Forms/FormSubmitAnalytics.test.tsx +206 -0
- package/Tests/UI/Utils/AIChatExport/ConversationMarkdown.test.ts +774 -0
- package/Tests/UI/Utils/AIChatExport/MarkdownBlocks.test.ts +791 -0
- package/Tests/UI/Utils/AIChatExport/MarkdownSafety.test.ts +544 -0
- package/Tests/Utils/Analytics.test.ts +187 -0
- package/Tests/Utils/Monitor/MonitorMetricType.test.ts +80 -0
- package/Tests/Utils/Monitor/NetworkDeviceLinkRuleUtil.test.ts +1291 -0
- package/Tests/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.test.ts +214 -0
- package/Types/Marketing/MarketingConversion.ts +12 -0
- package/Types/Monitor/CriteriaFilter.ts +9 -0
- package/Types/NetworkDevice/NetworkDeviceLinkRuleScope.ts +51 -0
- package/UI/Components/FormModal/BasicFormModal.tsx +12 -0
- package/UI/Components/Forms/BasicForm.tsx +13 -1
- package/UI/Components/Forms/BasicModelForm.tsx +2 -1
- package/UI/Components/Forms/ModelForm.tsx +10 -1
- package/UI/Components/Forms/Utils/FormAnalyticsName.ts +39 -0
- package/UI/Components/ModelFormModal/ModelFormModal.tsx +6 -1
- package/Utils/Analytics.ts +55 -2
- package/Utils/Monitor/MonitorMetricType.ts +40 -0
- package/Utils/Monitor/NetworkDeviceLinkRuleUtil.ts +587 -37
- package/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.ts +115 -0
- package/build/dist/Models/DatabaseModels/GlobalOidc.js +21 -0
- package/build/dist/Models/DatabaseModels/GlobalOidc.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalSso.js +21 -0
- package/build/dist/Models/DatabaseModels/GlobalSso.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MarketingConversion.js +20 -10
- package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js +47 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceLinkRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +4 -4
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/StatusPageAPI.js +25 -4
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +6 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787800000000-AddScopeToNetworkDeviceLinkRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.js +29 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787900000000-AddRestrictToAttachedProjectsToGlobalSso.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/SlackAuthorization.js +28 -4
- package/build/dist/Server/Middleware/SlackAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +197 -22
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/WhatsAppAuthorization.js +32 -2
- package/build/dist/Server/Middleware/WhatsAppAuthorization.js.map +1 -1
- package/build/dist/Server/Services/GlobalOidcProjectService.js +105 -0
- package/build/dist/Server/Services/GlobalOidcProjectService.js.map +1 -1
- package/build/dist/Server/Services/GlobalOidcService.js +127 -0
- package/build/dist/Server/Services/GlobalOidcService.js.map +1 -1
- package/build/dist/Server/Services/GlobalSsoProjectService.js +105 -0
- package/build/dist/Server/Services/GlobalSsoProjectService.js.map +1 -1
- package/build/dist/Server/Services/GlobalSsoService.js +127 -0
- package/build/dist/Server/Services/GlobalSsoService.js.map +1 -1
- package/build/dist/Server/Services/UserEmailService.js +4 -4
- package/build/dist/Server/Utils/CronTab.js +66 -0
- package/build/dist/Server/Utils/CronTab.js.map +1 -1
- package/build/dist/Server/Utils/GlobalSsoAuthorization.js +112 -0
- package/build/dist/Server/Utils/GlobalSsoAuthorization.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +17 -1
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +1 -1
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js +37 -35
- package/build/dist/Server/Utils/Monitor/Criteria/APIRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/DnsMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js +20 -22
- package/build/dist/Server/Utils/Monitor/Criteria/DomainMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js +370 -42
- package/build/dist/Server/Utils/Monitor/Criteria/EvaluateOverTime.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/ExternalStatusPageMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/IncomingRequestCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +21 -25
- package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js +64 -52
- package/build/dist/Server/Utils/Monitor/Criteria/ServerMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js +25 -29
- package/build/dist/Server/Utils/Monitor/Criteria/SnmpMonitorCriteria.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +8 -0
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +123 -23
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/StatusPageContentSecurityPolicy.js +39 -0
- package/build/dist/Server/Utils/StatusPageContentSecurityPolicy.js.map +1 -0
- package/build/dist/Server/Utils/StatusPageCustomizationAccess.js +85 -0
- package/build/dist/Server/Utils/StatusPageCustomizationAccess.js.map +1 -0
- package/build/dist/Types/Marketing/MarketingConversion.js +10 -0
- package/build/dist/Types/Marketing/MarketingConversion.js.map +1 -1
- package/build/dist/Types/Monitor/CriteriaFilter.js +1 -0
- package/build/dist/Types/Monitor/CriteriaFilter.js.map +1 -1
- package/build/dist/Types/NetworkDevice/NetworkDeviceLinkRuleScope.js +46 -0
- package/build/dist/Types/NetworkDevice/NetworkDeviceLinkRuleScope.js.map +1 -0
- package/build/dist/UI/Components/FormModal/BasicFormModal.js +2 -1
- package/build/dist/UI/Components/FormModal/BasicFormModal.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicForm.js +10 -1
- package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicModelForm.js +2 -1
- package/build/dist/UI/Components/Forms/BasicModelForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/ModelForm.js +4 -1
- package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/Utils/FormAnalyticsName.js +33 -0
- package/build/dist/UI/Components/Forms/Utils/FormAnalyticsName.js.map +1 -0
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js +2 -1
- package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
- package/build/dist/Utils/Analytics.js +50 -2
- package/build/dist/Utils/Analytics.js.map +1 -1
- package/build/dist/Utils/Monitor/MonitorMetricType.js +37 -0
- package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js +318 -37
- package/build/dist/Utils/Monitor/NetworkDeviceLinkRuleUtil.js.map +1 -1
- package/build/dist/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.js +87 -0
- package/build/dist/Utils/NetworkDiscovery/UnclaimedScanDiagnosis.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import BaseModel from "../../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
2
|
+
import Monitor from "../../../../Models/DatabaseModels/Monitor";
|
|
3
|
+
import OnCallDutyPolicyEscalationRuleSchedule from "../../../../Models/DatabaseModels/OnCallDutyPolicyEscalationRuleSchedule";
|
|
4
|
+
import RelationSelect from "../../../../Server/Types/Database/RelationSelect";
|
|
5
|
+
import Select from "../../../../Server/Types/Database/Select";
|
|
6
|
+
import SelectUtil from "../../../../Server/Types/Database/SelectUtil";
|
|
7
|
+
import BadDataException from "../../../../Types/Exception/BadDataException";
|
|
8
|
+
import { describe, expect, it } from "@jest/globals";
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* SelectUtil.sanitizeSelect walks the top level of a caller-supplied select and
|
|
12
|
+
* splits it into two shapes TypeORM needs kept apart: the scalar `select` (left
|
|
13
|
+
* as-is) and a `relationSelect` naming which relations to join. A key is a
|
|
14
|
+
* relation when model.isEntityColumn(key) is true - that is, its column
|
|
15
|
+
* metadata type is Entity or EntityArray.
|
|
16
|
+
*
|
|
17
|
+
* For every relation key it does two things regardless of what the caller
|
|
18
|
+
* asked for:
|
|
19
|
+
* - records `relationSelect[key] = true`, and
|
|
20
|
+
* - rewrites `select[key]` to an object that ALWAYS carries `_id: true`.
|
|
21
|
+
*
|
|
22
|
+
* The forced `_id: true` is deliberate and security-relevant: the source
|
|
23
|
+
* comment says a whole-object relation select is narrowed to just the id, and
|
|
24
|
+
* the id is merged in AFTER the caller's own keys so it cannot be turned off.
|
|
25
|
+
* Non-relation keys are never touched and never appear in relationSelect. The
|
|
26
|
+
* `select` object is MUTATED in place and returned; relationSelect is fresh.
|
|
27
|
+
*
|
|
28
|
+
* These models are used as fixtures because their column metadata is stable:
|
|
29
|
+
* - OnCallDutyPolicyEscalationRuleSchedule: `project` /
|
|
30
|
+
* `onCallDutyPolicySchedule` are Entity relations, `projectId` /
|
|
31
|
+
* `onCallDutyPolicyScheduleId` / `_id` are scalar ObjectID columns. It has
|
|
32
|
+
* no `name` column, so `name` is used below only as the unknown-column case.
|
|
33
|
+
* - Monitor: `labels` / `dependsOnMonitors` are EntityArray relations and
|
|
34
|
+
* `name` is a real scalar column, covering the second entity enum value.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
type SanitizeResult<TBaseModel extends BaseModel> = {
|
|
38
|
+
select: Select<TBaseModel>;
|
|
39
|
+
relationSelect: RelationSelect<TBaseModel>;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
describe("SelectUtil.sanitizeSelect - relation key set to a boolean", () => {
|
|
43
|
+
it("replaces `true` on a relation with an id-only object", () => {
|
|
44
|
+
/*
|
|
45
|
+
* The else branch: the value is not an object, so spreading it contributes
|
|
46
|
+
* nothing and the relation collapses to exactly `{ _id: true }`.
|
|
47
|
+
*/
|
|
48
|
+
const input: Select<OnCallDutyPolicyEscalationRuleSchedule> = {
|
|
49
|
+
project: true,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const result: SanitizeResult<OnCallDutyPolicyEscalationRuleSchedule> =
|
|
53
|
+
SelectUtil.sanitizeSelect(OnCallDutyPolicyEscalationRuleSchedule, input);
|
|
54
|
+
|
|
55
|
+
expect(result.select).toEqual({ project: { _id: true } });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("names that relation in relationSelect", () => {
|
|
59
|
+
const input: Select<OnCallDutyPolicyEscalationRuleSchedule> = {
|
|
60
|
+
project: true,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const result: SanitizeResult<OnCallDutyPolicyEscalationRuleSchedule> =
|
|
64
|
+
SelectUtil.sanitizeSelect(OnCallDutyPolicyEscalationRuleSchedule, input);
|
|
65
|
+
|
|
66
|
+
expect(result.relationSelect).toEqual({ project: true });
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("SelectUtil.sanitizeSelect - relation key set to an object", () => {
|
|
71
|
+
it("keeps the caller's inner keys and adds _id", () => {
|
|
72
|
+
/*
|
|
73
|
+
* The object branch: the requested inner keys survive and `_id: true` is
|
|
74
|
+
* merged alongside them. Inner keys are never validated by sanitizeSelect -
|
|
75
|
+
* only top-level keys are checked against the model - so `name` here rides
|
|
76
|
+
* through untouched even on a model whose relation happens to expose it.
|
|
77
|
+
*/
|
|
78
|
+
const input: Select<OnCallDutyPolicyEscalationRuleSchedule> = {
|
|
79
|
+
onCallDutyPolicySchedule: { name: true },
|
|
80
|
+
} as Select<OnCallDutyPolicyEscalationRuleSchedule>;
|
|
81
|
+
|
|
82
|
+
const result: SanitizeResult<OnCallDutyPolicyEscalationRuleSchedule> =
|
|
83
|
+
SelectUtil.sanitizeSelect(OnCallDutyPolicyEscalationRuleSchedule, input);
|
|
84
|
+
|
|
85
|
+
expect(result.select).toEqual({
|
|
86
|
+
onCallDutyPolicySchedule: { name: true, _id: true },
|
|
87
|
+
});
|
|
88
|
+
expect(result.relationSelect).toEqual({ onCallDutyPolicySchedule: true });
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("forces _id back on even when the caller set it false", () => {
|
|
92
|
+
/*
|
|
93
|
+
* Precedence: `_id: true` is spread in AFTER the caller's object, so an
|
|
94
|
+
* explicit `_id: false` is overwritten rather than honoured. This is the
|
|
95
|
+
* security guarantee - a relation read cannot suppress the joined id.
|
|
96
|
+
*/
|
|
97
|
+
const input: Select<OnCallDutyPolicyEscalationRuleSchedule> = {
|
|
98
|
+
onCallDutyPolicySchedule: { _id: false },
|
|
99
|
+
} as Select<OnCallDutyPolicyEscalationRuleSchedule>;
|
|
100
|
+
|
|
101
|
+
const result: SanitizeResult<OnCallDutyPolicyEscalationRuleSchedule> =
|
|
102
|
+
SelectUtil.sanitizeSelect(OnCallDutyPolicyEscalationRuleSchedule, input);
|
|
103
|
+
|
|
104
|
+
expect(result.select).toEqual({
|
|
105
|
+
onCallDutyPolicySchedule: { _id: true },
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("leaves an already-correct id-only object idempotent", () => {
|
|
110
|
+
const input: Select<OnCallDutyPolicyEscalationRuleSchedule> = {
|
|
111
|
+
onCallDutyPolicySchedule: { _id: true },
|
|
112
|
+
} as Select<OnCallDutyPolicyEscalationRuleSchedule>;
|
|
113
|
+
|
|
114
|
+
const result: SanitizeResult<OnCallDutyPolicyEscalationRuleSchedule> =
|
|
115
|
+
SelectUtil.sanitizeSelect(OnCallDutyPolicyEscalationRuleSchedule, input);
|
|
116
|
+
|
|
117
|
+
expect(result.select).toEqual({
|
|
118
|
+
onCallDutyPolicySchedule: { _id: true },
|
|
119
|
+
});
|
|
120
|
+
expect(result.relationSelect).toEqual({ onCallDutyPolicySchedule: true });
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("SelectUtil.sanitizeSelect - non-relation keys", () => {
|
|
125
|
+
it("leaves scalar columns exactly as passed", () => {
|
|
126
|
+
/*
|
|
127
|
+
* projectId and _id are ObjectID columns, not Entity/EntityArray, so
|
|
128
|
+
* isEntityColumn is false and the branch is skipped entirely.
|
|
129
|
+
*/
|
|
130
|
+
const input: Select<OnCallDutyPolicyEscalationRuleSchedule> = {
|
|
131
|
+
projectId: true,
|
|
132
|
+
_id: true,
|
|
133
|
+
} as Select<OnCallDutyPolicyEscalationRuleSchedule>;
|
|
134
|
+
|
|
135
|
+
const result: SanitizeResult<OnCallDutyPolicyEscalationRuleSchedule> =
|
|
136
|
+
SelectUtil.sanitizeSelect(OnCallDutyPolicyEscalationRuleSchedule, input);
|
|
137
|
+
|
|
138
|
+
expect(result.select).toEqual({ projectId: true, _id: true });
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("never lists a scalar column in relationSelect", () => {
|
|
142
|
+
const input: Select<OnCallDutyPolicyEscalationRuleSchedule> = {
|
|
143
|
+
projectId: true,
|
|
144
|
+
_id: true,
|
|
145
|
+
} as Select<OnCallDutyPolicyEscalationRuleSchedule>;
|
|
146
|
+
|
|
147
|
+
const result: SanitizeResult<OnCallDutyPolicyEscalationRuleSchedule> =
|
|
148
|
+
SelectUtil.sanitizeSelect(OnCallDutyPolicyEscalationRuleSchedule, input);
|
|
149
|
+
|
|
150
|
+
expect(result.relationSelect).toEqual({});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
describe("SelectUtil.sanitizeSelect - empty and mixed selects", () => {
|
|
155
|
+
it("returns empty shapes for an empty select", () => {
|
|
156
|
+
const input: Select<OnCallDutyPolicyEscalationRuleSchedule> = {};
|
|
157
|
+
|
|
158
|
+
const result: SanitizeResult<OnCallDutyPolicyEscalationRuleSchedule> =
|
|
159
|
+
SelectUtil.sanitizeSelect(OnCallDutyPolicyEscalationRuleSchedule, input);
|
|
160
|
+
|
|
161
|
+
expect(result.select).toEqual({});
|
|
162
|
+
expect(result.relationSelect).toEqual({});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("transforms relations and passes scalars through in one call", () => {
|
|
166
|
+
/*
|
|
167
|
+
* A realistic select: a scalar, a boolean relation, and an object relation
|
|
168
|
+
* together. Each key follows its own branch and the results coexist.
|
|
169
|
+
*/
|
|
170
|
+
const input: Select<OnCallDutyPolicyEscalationRuleSchedule> = {
|
|
171
|
+
projectId: true,
|
|
172
|
+
project: true,
|
|
173
|
+
onCallDutyPolicySchedule: { _id: false },
|
|
174
|
+
} as Select<OnCallDutyPolicyEscalationRuleSchedule>;
|
|
175
|
+
|
|
176
|
+
const result: SanitizeResult<OnCallDutyPolicyEscalationRuleSchedule> =
|
|
177
|
+
SelectUtil.sanitizeSelect(OnCallDutyPolicyEscalationRuleSchedule, input);
|
|
178
|
+
|
|
179
|
+
expect(result.select).toEqual({
|
|
180
|
+
projectId: true,
|
|
181
|
+
project: { _id: true },
|
|
182
|
+
onCallDutyPolicySchedule: { _id: true },
|
|
183
|
+
});
|
|
184
|
+
expect(result.relationSelect).toEqual({
|
|
185
|
+
project: true,
|
|
186
|
+
onCallDutyPolicySchedule: true,
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("lists exactly the relation keys, and only those, in relationSelect", () => {
|
|
191
|
+
const input: Select<OnCallDutyPolicyEscalationRuleSchedule> = {
|
|
192
|
+
projectId: true,
|
|
193
|
+
project: true,
|
|
194
|
+
onCallDutyPolicyScheduleId: true,
|
|
195
|
+
onCallDutyPolicySchedule: { _id: true },
|
|
196
|
+
} as Select<OnCallDutyPolicyEscalationRuleSchedule>;
|
|
197
|
+
|
|
198
|
+
const result: SanitizeResult<OnCallDutyPolicyEscalationRuleSchedule> =
|
|
199
|
+
SelectUtil.sanitizeSelect(OnCallDutyPolicyEscalationRuleSchedule, input);
|
|
200
|
+
|
|
201
|
+
const relationKeys: Array<string> = Object.keys(
|
|
202
|
+
result.relationSelect as Record<string, unknown>,
|
|
203
|
+
).sort();
|
|
204
|
+
|
|
205
|
+
expect(relationKeys).toEqual(["onCallDutyPolicySchedule", "project"]);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe("SelectUtil.sanitizeSelect - object identity and mutation", () => {
|
|
210
|
+
it("mutates and returns the very select object it was handed", () => {
|
|
211
|
+
/*
|
|
212
|
+
* The returned `select` is the same reference, not a copy - callers rely on
|
|
213
|
+
* the input being rewritten in place.
|
|
214
|
+
*/
|
|
215
|
+
const input: Select<OnCallDutyPolicyEscalationRuleSchedule> = {
|
|
216
|
+
project: true,
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const result: SanitizeResult<OnCallDutyPolicyEscalationRuleSchedule> =
|
|
220
|
+
SelectUtil.sanitizeSelect(OnCallDutyPolicyEscalationRuleSchedule, input);
|
|
221
|
+
|
|
222
|
+
expect(result.select).toBe(input);
|
|
223
|
+
expect((input as Record<string, unknown>)["project"]).toEqual({
|
|
224
|
+
_id: true,
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("builds relationSelect as a fresh object distinct from select", () => {
|
|
229
|
+
const input: Select<OnCallDutyPolicyEscalationRuleSchedule> = {
|
|
230
|
+
project: true,
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const result: SanitizeResult<OnCallDutyPolicyEscalationRuleSchedule> =
|
|
234
|
+
SelectUtil.sanitizeSelect(OnCallDutyPolicyEscalationRuleSchedule, input);
|
|
235
|
+
|
|
236
|
+
expect(result.relationSelect).not.toBe(result.select);
|
|
237
|
+
expect(result.relationSelect).not.toBe(input);
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe("SelectUtil.sanitizeSelect - typeof edge cases on relation values", () => {
|
|
242
|
+
it("treats a null relation value as the object branch", () => {
|
|
243
|
+
/*
|
|
244
|
+
* `typeof null === "object"`, so null takes the object branch. Spreading
|
|
245
|
+
* null yields nothing, leaving `{ _id: true }` - the relation is still
|
|
246
|
+
* joined, not dropped.
|
|
247
|
+
*/
|
|
248
|
+
const input: Select<OnCallDutyPolicyEscalationRuleSchedule> = {
|
|
249
|
+
project: null,
|
|
250
|
+
} as unknown as Select<OnCallDutyPolicyEscalationRuleSchedule>;
|
|
251
|
+
|
|
252
|
+
const result: SanitizeResult<OnCallDutyPolicyEscalationRuleSchedule> =
|
|
253
|
+
SelectUtil.sanitizeSelect(OnCallDutyPolicyEscalationRuleSchedule, input);
|
|
254
|
+
|
|
255
|
+
expect(result.select).toEqual({ project: { _id: true } });
|
|
256
|
+
expect(result.relationSelect).toEqual({ project: true });
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("treats a numeric relation value as the non-object branch", () => {
|
|
260
|
+
/*
|
|
261
|
+
* `typeof 5 === "number"` takes the else branch; spreading a number adds no
|
|
262
|
+
* keys, so the result is the same id-only object as the boolean case.
|
|
263
|
+
*/
|
|
264
|
+
const input: Select<OnCallDutyPolicyEscalationRuleSchedule> = {
|
|
265
|
+
project: 5,
|
|
266
|
+
} as unknown as Select<OnCallDutyPolicyEscalationRuleSchedule>;
|
|
267
|
+
|
|
268
|
+
const result: SanitizeResult<OnCallDutyPolicyEscalationRuleSchedule> =
|
|
269
|
+
SelectUtil.sanitizeSelect(OnCallDutyPolicyEscalationRuleSchedule, input);
|
|
270
|
+
|
|
271
|
+
expect(result.select).toEqual({ project: { _id: true } });
|
|
272
|
+
expect(result.relationSelect).toEqual({ project: true });
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe("SelectUtil.sanitizeSelect - EntityArray relations", () => {
|
|
277
|
+
it("handles a boolean EntityArray relation like any other relation", () => {
|
|
278
|
+
/*
|
|
279
|
+
* `labels` is an EntityArray column on Monitor. isEntityColumn returns true
|
|
280
|
+
* for both Entity and EntityArray, so the same collapse-to-id happens.
|
|
281
|
+
*/
|
|
282
|
+
const input: Select<Monitor> = {
|
|
283
|
+
name: true,
|
|
284
|
+
labels: true,
|
|
285
|
+
} as Select<Monitor>;
|
|
286
|
+
|
|
287
|
+
const result: SanitizeResult<Monitor> = SelectUtil.sanitizeSelect(
|
|
288
|
+
Monitor,
|
|
289
|
+
input,
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
expect(result.select).toEqual({
|
|
293
|
+
name: true,
|
|
294
|
+
labels: { _id: true },
|
|
295
|
+
});
|
|
296
|
+
expect(result.relationSelect).toEqual({ labels: true });
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("merges _id into an object-valued EntityArray relation", () => {
|
|
300
|
+
const input: Select<Monitor> = {
|
|
301
|
+
dependsOnMonitors: { name: true },
|
|
302
|
+
} as Select<Monitor>;
|
|
303
|
+
|
|
304
|
+
const result: SanitizeResult<Monitor> = SelectUtil.sanitizeSelect(
|
|
305
|
+
Monitor,
|
|
306
|
+
input,
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
expect(result.select).toEqual({
|
|
310
|
+
dependsOnMonitors: { name: true, _id: true },
|
|
311
|
+
});
|
|
312
|
+
expect(result.relationSelect).toEqual({ dependsOnMonitors: true });
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
describe("SelectUtil.sanitizeSelect - unknown column error path", () => {
|
|
317
|
+
it("throws BadDataException for a key that is not a column on the model", () => {
|
|
318
|
+
/*
|
|
319
|
+
* isEntityColumn throws when a key has no column metadata, and
|
|
320
|
+
* sanitizeSelect checks every top-level key, so an unknown column fails the
|
|
321
|
+
* whole call. `name` is not a column on this model, which makes it the
|
|
322
|
+
* unknown-key case here.
|
|
323
|
+
*/
|
|
324
|
+
const input: Select<OnCallDutyPolicyEscalationRuleSchedule> = {
|
|
325
|
+
name: true,
|
|
326
|
+
} as unknown as Select<OnCallDutyPolicyEscalationRuleSchedule>;
|
|
327
|
+
|
|
328
|
+
expect(() => {
|
|
329
|
+
return SelectUtil.sanitizeSelect(
|
|
330
|
+
OnCallDutyPolicyEscalationRuleSchedule,
|
|
331
|
+
input,
|
|
332
|
+
);
|
|
333
|
+
}).toThrow(BadDataException);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it("reports the offending column name in the error message", () => {
|
|
337
|
+
const input: Select<OnCallDutyPolicyEscalationRuleSchedule> = {
|
|
338
|
+
totallyMadeUpColumn: true,
|
|
339
|
+
} as unknown as Select<OnCallDutyPolicyEscalationRuleSchedule>;
|
|
340
|
+
|
|
341
|
+
expect(() => {
|
|
342
|
+
return SelectUtil.sanitizeSelect(
|
|
343
|
+
OnCallDutyPolicyEscalationRuleSchedule,
|
|
344
|
+
input,
|
|
345
|
+
);
|
|
346
|
+
}).toThrow("TableColumnMetadata not found for totallyMadeUpColumn column");
|
|
347
|
+
});
|
|
348
|
+
});
|
|
@@ -41,4 +41,120 @@ describe("CronTab", () => {
|
|
|
41
41
|
CronTab.getNextExecutionTime(crontab);
|
|
42
42
|
}).toThrowError(`Invalid cron expression: ${crontab}`);
|
|
43
43
|
});
|
|
44
|
+
|
|
45
|
+
/*
|
|
46
|
+
* getIntervalInSeconds answers "how long can a monitor go between checks",
|
|
47
|
+
* which is what over-time criteria use to tell a window that is genuinely
|
|
48
|
+
* all-breaching from one that has simply not filled up yet.
|
|
49
|
+
*/
|
|
50
|
+
describe("getIntervalInSeconds", () => {
|
|
51
|
+
// Fixed so the walk over upcoming executions never touches the wall clock.
|
|
52
|
+
const referenceDate: Date = new Date("2026-08-20T12:00:00.000Z");
|
|
53
|
+
|
|
54
|
+
it("returns 60 seconds for an every-minute schedule", () => {
|
|
55
|
+
expect(CronTab.getIntervalInSeconds("* * * * *", referenceDate)).toBe(60);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("returns 300 seconds for an every-five-minute schedule", () => {
|
|
59
|
+
expect(CronTab.getIntervalInSeconds("*/5 * * * *", referenceDate)).toBe(
|
|
60
|
+
300,
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("returns 3600 seconds for an hourly schedule", () => {
|
|
65
|
+
expect(CronTab.getIntervalInSeconds("0 * * * *", referenceDate)).toBe(
|
|
66
|
+
3600,
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("returns a day for a daily schedule", () => {
|
|
71
|
+
expect(CronTab.getIntervalInSeconds("0 0 * * *", referenceDate)).toBe(
|
|
72
|
+
24 * 60 * 60,
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
/*
|
|
77
|
+
* An irregular schedule has both short and long gaps. The longest one is
|
|
78
|
+
* what callers need: demanding data at the shortest cadence would make
|
|
79
|
+
* them wait for samples the schedule never produces.
|
|
80
|
+
*/
|
|
81
|
+
it("reports the longest gap for an irregular schedule", () => {
|
|
82
|
+
// Fires at :00 :01 :30 - gaps of 1, 29 and 30 minutes.
|
|
83
|
+
expect(
|
|
84
|
+
CronTab.getIntervalInSeconds("0,1,30 * * * *", referenceDate),
|
|
85
|
+
).toBe(30 * 60);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("is unaffected by which point in the schedule it starts from", () => {
|
|
89
|
+
const fromMidCycle: number | null = CronTab.getIntervalInSeconds(
|
|
90
|
+
"*/5 * * * *",
|
|
91
|
+
new Date("2026-08-20T12:02:37.000Z"),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect(fromMidCycle).toBe(300);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("handles a schedule that wraps the hour", () => {
|
|
98
|
+
/*
|
|
99
|
+
* An every-seven-minute schedule fires at :00 :07 ... :56 and then
|
|
100
|
+
* :00 of the next hour, so the wrap gap is only four minutes - the
|
|
101
|
+
* seven minute gap still governs.
|
|
102
|
+
*/
|
|
103
|
+
expect(CronTab.getIntervalInSeconds("*/7 * * * *", referenceDate)).toBe(
|
|
104
|
+
7 * 60,
|
|
105
|
+
);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
/*
|
|
109
|
+
* Every preset the monitoring-interval picker offers, so a new one added
|
|
110
|
+
* to the dropdown without a matching gap here shows up as a failure
|
|
111
|
+
* rather than as silently unknown cadence.
|
|
112
|
+
*/
|
|
113
|
+
it("resolves every interval the monitor picker offers", () => {
|
|
114
|
+
const presets: Record<string, number> = {
|
|
115
|
+
"* * * * *": 60,
|
|
116
|
+
"*/2 * * * *": 120,
|
|
117
|
+
"*/5 * * * *": 300,
|
|
118
|
+
"*/10 * * * *": 600,
|
|
119
|
+
"*/15 * * * *": 900,
|
|
120
|
+
"*/30 * * * *": 1800,
|
|
121
|
+
"0 * * * *": 3600,
|
|
122
|
+
"0 0 * * *": 24 * 60 * 60,
|
|
123
|
+
"0 0 * * 0": 7 * 24 * 60 * 60,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
for (const expression of Object.keys(presets)) {
|
|
127
|
+
expect(CronTab.getIntervalInSeconds(expression, referenceDate)).toBe(
|
|
128
|
+
presets[expression],
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
/*
|
|
134
|
+
* monitoringInterval is free text and this repo's own Terraform examples
|
|
135
|
+
* write the human label, so the label form must degrade to "unknown"
|
|
136
|
+
* rather than throwing on the criteria hot path.
|
|
137
|
+
*/
|
|
138
|
+
it("returns null for the human label form", () => {
|
|
139
|
+
expect(
|
|
140
|
+
CronTab.getIntervalInSeconds("Every 5 minutes", referenceDate),
|
|
141
|
+
).toBeNull();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("returns null for an unparseable expression", () => {
|
|
145
|
+
expect(
|
|
146
|
+
CronTab.getIntervalInSeconds("not-a-cron", referenceDate),
|
|
147
|
+
).toBeNull();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("returns null when no schedule is configured", () => {
|
|
151
|
+
expect(CronTab.getIntervalInSeconds(undefined)).toBeNull();
|
|
152
|
+
expect(CronTab.getIntervalInSeconds(null)).toBeNull();
|
|
153
|
+
expect(CronTab.getIntervalInSeconds("")).toBeNull();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("falls back to the wall clock when no reference date is given", () => {
|
|
157
|
+
expect(CronTab.getIntervalInSeconds("*/5 * * * *")).toBe(300);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
44
160
|
});
|