@oneuptime/common 14.0.0 → 14.0.1
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/Index.ts +4 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveFeed.ts +2 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.ts +522 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.ts +38 -4
- package/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.ts +603 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.ts +248 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/DatabaseService.ts +10 -0
- package/Server/Services/Index.ts +4 -0
- package/Server/Services/MonitorService.ts +3 -2
- package/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.ts +385 -0
- package/Server/Services/ServiceLevelObjectiveLabelRuleService.ts +51 -0
- package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +92 -16
- package/Server/Services/ServiceLevelObjectiveMonitorRuleService.ts +36 -1
- package/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.ts +521 -0
- package/Server/Services/ServiceLevelObjectiveOwnerRuleService.ts +51 -0
- package/Server/Services/ServiceLevelObjectiveService.ts +28 -0
- package/Server/Types/Workflow/Components/Telegram/SendMessageToChat.ts +27 -1
- package/Server/Utils/AI/Toolbox/SloTools.ts +1 -0
- package/Server/Utils/Monitor/AffectedResourceList.ts +31 -73
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +1 -1
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +1 -1
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +181 -136
- package/Server/Utils/Monitor/RootCauseList.ts +139 -0
- package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +28 -0
- package/Server/Utils/Slo/SloRulePatternValidator.ts +42 -0
- package/Tests/App/Dashboard/FeedFilterSortIntegration.test.tsx +1632 -0
- package/Tests/App/Dashboard/FeedOptionsLocales.test.ts +250 -0
- package/Tests/App/Dashboard/FeedOrdering.test.ts +279 -9
- package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +60 -1
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +594 -3
- package/Tests/App/Dashboard/ProductOrientation.test.tsx +646 -0
- package/Tests/App/Dashboard/ResourceFeed.test.tsx +612 -2
- package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +30 -0
- package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +1202 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceFeedRefresh.test.tsx +52 -1
- package/Tests/App/Dashboard/SloFeed.test.tsx +106 -1
- package/Tests/App/Dashboard/SloMonitorRuleCriteria.test.tsx +566 -0
- package/Tests/App/Dashboard/SloNavigationWiring.test.tsx +6 -4
- package/Tests/App/Dashboard/SloNoticeBanner.test.tsx +186 -0
- package/Tests/App/Dashboard/SloOverviewKpiStrip.test.tsx +388 -44
- package/Tests/App/Dashboard/SloOverviewLayout.test.tsx +613 -0
- package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +0 -8
- package/Tests/Models/DatabaseModels/SloLabelAndOwnerRuleModels.test.ts +404 -0
- package/Tests/Models/DatabaseModels/SloOverhaulModels.test.ts +17 -1
- package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
- package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
- package/Tests/Server/Infrastructure/Postgres/AddSloLabelAndOwnerRulesMigration.test.ts +407 -0
- package/Tests/Server/Infrastructure/Postgres/AddSloMonitorRuleMonitorTypeMigration.test.ts +171 -0
- package/Tests/Server/Infrastructure/Postgres/SloProductOverhaulMigration.test.ts +88 -4
- package/Tests/Server/Services/DatabaseServiceRuleCriteriaValidation.test.ts +104 -0
- package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +48 -1
- package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -0
- package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleEngines.test.ts +1170 -0
- package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleServices.test.ts +172 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +323 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleService.test.ts +115 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleTriggers.test.ts +21 -2
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorTypePostgres.test.ts +480 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +162 -0
- package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +45 -6
- package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +745 -0
- package/Tests/Server/Types/Markdown.test.ts +2 -1
- package/Tests/Server/Types/Workflow/Components/ApiComponentSsrf.test.ts +49 -0
- package/Tests/Server/Types/Workflow/Components/TelegramSendMessageToChat.test.ts +331 -0
- package/Tests/Server/Utils/AI/SloTools.test.ts +10 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +1 -1
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +405 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorBreachingSamples.test.ts +1101 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +106 -80
- package/Tests/Server/Utils/Monitor/RootCauseList.test.ts +252 -0
- package/Tests/Server/Utils/Rules/RuleRun/RuleRunPermission.test.ts +154 -0
- package/Tests/Server/Utils/SSRFProtection.test.ts +111 -0
- package/Tests/Server/Utils/Slo/SloRulePatternValidator.test.ts +67 -0
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +213 -0
- package/Tests/Types/API/Route.test.ts +56 -0
- package/Tests/Types/API/URLSchemeLikePath.test.ts +304 -0
- package/Tests/UI/Components/FeedOptions.test.ts +1404 -0
- package/Tests/UI/Components/FeedOptionsButton.test.tsx +2603 -0
- package/Tests/UI/Components/Icon.test.tsx +26 -0
- package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
- package/Tests/UI/Components/useFeedItemsViewKey.test.tsx +1017 -0
- package/Tests/UI/Components/useFeedOptions.test.tsx +1107 -0
- package/Tests/Utils/LabelRuleImportExport.test.ts +102 -0
- package/Tests/Utils/Slo/SloDuration.test.ts +115 -24
- package/Tests/Utils/Slo/SloHealth.test.ts +93 -10
- package/Tests/Utils/Slo/SloMonitorRuleCriteria.test.ts +109 -0
- package/Tests/Utils/Slo/SloOverviewText.test.ts +64 -0
- package/Types/API/Route.ts +19 -2
- package/Types/API/URL.ts +26 -2
- package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +6 -5
- package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +9 -0
- package/Types/Permission.ts +90 -0
- package/Types/Rules/RuleCriteriaFieldRegistry.ts +11 -0
- package/Types/Rules/RuleRun.ts +4 -0
- package/UI/Components/Feed/FeedOptions.ts +427 -0
- package/UI/Components/Feed/FeedOptionsButton.tsx +692 -0
- package/UI/Components/Feed/useFeedItems.ts +28 -9
- package/UI/Components/Feed/useFeedOptions.ts +225 -0
- package/UI/Components/Icon/Icon.tsx +1 -1
- package/Utils/LabelRuleImportExport.ts +27 -9
- package/Utils/Monitor/MetricValueFormatter.ts +2 -2
- package/Utils/Rules/MonitorTypeRuleCriteria.ts +31 -0
- package/Utils/Slo/SloDuration.ts +22 -6
- package/Utils/Slo/SloHealth.ts +8 -7
- package/Utils/Slo/SloMonitorRuleCriteria.ts +33 -1
- package/Utils/Slo/SloOverviewText.ts +33 -0
- package/Utils/Slo/SloProjection.ts +4 -3
- package/build/dist/Models/DatabaseModels/Index.js +4 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js +2 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js +530 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js +39 -4
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js +612 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js +93 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.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/Services/DatabaseService.js +7 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +4 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +3 -2
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js +295 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js +56 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +59 -14
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js +22 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js +400 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js +56 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js +21 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js +20 -1
- package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +22 -51
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +139 -109
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/RootCauseList.js +101 -0
- package/build/dist/Server/Utils/Monitor/RootCauseList.js.map +1 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +28 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js +28 -0
- package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js.map +1 -0
- package/build/dist/Types/API/Route.js +18 -2
- package/build/dist/Types/API/Route.js.map +1 -1
- package/build/dist/Types/API/URL.js +23 -2
- package/build/dist/Types/API/URL.js.map +1 -1
- package/build/dist/Types/Permission.js +84 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +11 -0
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
- package/build/dist/Types/Rules/RuleRun.js +4 -0
- package/build/dist/Types/Rules/RuleRun.js.map +1 -1
- package/build/dist/UI/Components/Feed/FeedOptions.js +235 -0
- package/build/dist/UI/Components/Feed/FeedOptions.js.map +1 -0
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js +346 -0
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -0
- package/build/dist/UI/Components/Feed/useFeedItems.js +17 -7
- package/build/dist/UI/Components/Feed/useFeedItems.js.map +1 -1
- package/build/dist/UI/Components/Feed/useFeedOptions.js +130 -0
- package/build/dist/UI/Components/Feed/useFeedOptions.js.map +1 -0
- package/build/dist/UI/Components/Icon/Icon.js +1 -1
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/Utils/LabelRuleImportExport.js +22 -6
- package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +2 -2
- package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js +18 -0
- package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js.map +1 -0
- package/build/dist/Utils/Slo/SloDuration.js +13 -5
- package/build/dist/Utils/Slo/SloDuration.js.map +1 -1
- package/build/dist/Utils/Slo/SloHealth.js +8 -7
- package/build/dist/Utils/Slo/SloHealth.js.map +1 -1
- package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js +16 -1
- package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js.map +1 -1
- package/build/dist/Utils/Slo/SloOverviewText.js +16 -0
- package/build/dist/Utils/Slo/SloOverviewText.js.map +1 -1
- package/build/dist/Utils/Slo/SloProjection.js.map +1 -1
- package/package.json +1 -1
|
@@ -111,9 +111,10 @@ describe("Markdown.slugify", () => {
|
|
|
111
111
|
* Until this suite existed there was no coverage of convertToHTML at all,
|
|
112
112
|
* for any content type — and the email renderer was a bare `new Renderer()`,
|
|
113
113
|
* so marked's stock output went straight into the message. That mattered
|
|
114
|
-
* most for the alert and incident root cause, which
|
|
114
|
+
* most for the alert and incident root cause, which then carried a
|
|
115
115
|
* GitHub-flavoured table of breaching samples: it arrived as a naked
|
|
116
116
|
* <table> with no borders and no padding, every row running into the next.
|
|
117
|
+
* (Those samples are a list now; tables typed into markdown remain.)
|
|
117
118
|
*
|
|
118
119
|
* Two properties are load-bearing and easy to break:
|
|
119
120
|
*
|
|
@@ -213,3 +213,52 @@ describe.each(apiComponents)(
|
|
|
213
213
|
});
|
|
214
214
|
},
|
|
215
215
|
);
|
|
216
|
+
|
|
217
|
+
/*
|
|
218
|
+
* sanitizeArgs validates the raw URL and then dispatches URL.fromString(url).
|
|
219
|
+
* That parse refused any first path segment containing a ":", so every API
|
|
220
|
+
* component call to the Telegram Bot API (the token is that segment) failed
|
|
221
|
+
* before a request was made, quoting the token in the error.
|
|
222
|
+
*/
|
|
223
|
+
describe.each(apiComponents)(
|
|
224
|
+
"API %s workflow component with a token in the first path segment",
|
|
225
|
+
(
|
|
226
|
+
_label: string,
|
|
227
|
+
apiMethodName: ApiMethodName,
|
|
228
|
+
createComponent: () => ComponentCode,
|
|
229
|
+
) => {
|
|
230
|
+
const telegramUrl: string =
|
|
231
|
+
"https://api.telegram.org/bot8000000001:AAFakeFakeFakeFakeFakeFakeFake_-12345/sendMessage";
|
|
232
|
+
|
|
233
|
+
test("dispatches the Telegram Bot API URL unchanged", async () => {
|
|
234
|
+
getApiMock(apiMethodName).mockResolvedValue(
|
|
235
|
+
new HTTPResponse<JSONObject>(200, { ok: true }, {}),
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
const result: { returnValues: JSONObject } = await createComponent().run(
|
|
239
|
+
{ url: telegramUrl },
|
|
240
|
+
makeOptions(),
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
const request: { url: { toString: () => string } } = getApiMock(
|
|
244
|
+
apiMethodName,
|
|
245
|
+
).mock.calls[0]![0] as { url: { toString: () => string } };
|
|
246
|
+
|
|
247
|
+
expect(request.url.toString()).toBe(telegramUrl);
|
|
248
|
+
expect(result.returnValues["response-status"]).toBe(200);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("still refuses an internal host behind the same path", async () => {
|
|
252
|
+
await expect(
|
|
253
|
+
createComponent().run(
|
|
254
|
+
{
|
|
255
|
+
url: "http://169.254.169.254/bot8000000001:AAFake/sendMessage",
|
|
256
|
+
},
|
|
257
|
+
makeOptions(),
|
|
258
|
+
),
|
|
259
|
+
).rejects.toThrow();
|
|
260
|
+
|
|
261
|
+
expect(getApiMock(apiMethodName)).not.toHaveBeenCalled();
|
|
262
|
+
});
|
|
263
|
+
},
|
|
264
|
+
);
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import SendMessageToChat from "../../../../../Server/Types/Workflow/Components/Telegram/SendMessageToChat";
|
|
2
|
+
import {
|
|
3
|
+
RunOptions,
|
|
4
|
+
RunReturnType,
|
|
5
|
+
} from "../../../../../Server/Types/Workflow/ComponentCode";
|
|
6
|
+
import HTTPErrorResponse from "../../../../../Types/API/HTTPErrorResponse";
|
|
7
|
+
import HTTPResponse from "../../../../../Types/API/HTTPResponse";
|
|
8
|
+
import URL from "../../../../../Types/API/URL";
|
|
9
|
+
import Exception from "../../../../../Types/Exception/Exception";
|
|
10
|
+
import { JSONObject } from "../../../../../Types/JSON";
|
|
11
|
+
import ObjectID from "../../../../../Types/ObjectID";
|
|
12
|
+
import API from "../../../../../Utils/API";
|
|
13
|
+
import { beforeEach, describe, expect, test } from "@jest/globals";
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* "Send Message to Telegram" built its request URL with
|
|
17
|
+
* URL.fromString(`https://api.telegram.org/bot${token}/sendMessage`). Once
|
|
18
|
+
* Route began refusing scheme-like values, the bare path "bot<id>:<secret>/..."
|
|
19
|
+
* was refused, so EVERY run failed before a request was sent - with the error
|
|
20
|
+
* "Invalid route: bot<the whole token>/sendMessage", which put the credential
|
|
21
|
+
* into the run log even though the argument itself is shown as [REDACTED].
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
jest.mock("../../../../../Utils/API", () => {
|
|
25
|
+
return {
|
|
26
|
+
__esModule: true,
|
|
27
|
+
default: { post: jest.fn() },
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const apiPostMock: jest.Mock = API.post as unknown as jest.Mock;
|
|
32
|
+
|
|
33
|
+
// Same shape as a real token: 10-digit bot id, 35-character secret.
|
|
34
|
+
const BOT_TOKEN: string = "8000000001:AAFakeFakeFakeFakeFakeFakeFake_-12345";
|
|
35
|
+
|
|
36
|
+
const HELP_MESSAGE: string =
|
|
37
|
+
" Note: For usernames, the user must have started a conversation with the bot first. For groups/channels, the bot must be added as a member or admin.";
|
|
38
|
+
|
|
39
|
+
function makeOptions(): RunOptions {
|
|
40
|
+
return {
|
|
41
|
+
log: jest.fn() as RunOptions["log"],
|
|
42
|
+
workflowLogId: ObjectID.generate(),
|
|
43
|
+
workflowId: ObjectID.generate(),
|
|
44
|
+
projectId: ObjectID.generate(),
|
|
45
|
+
onError: ((exception: Exception): Exception => {
|
|
46
|
+
return exception;
|
|
47
|
+
}) as RunOptions["onError"],
|
|
48
|
+
executeWorkflow: async (): Promise<void> => {},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function makeArgs(overrides: JSONObject = {}): JSONObject {
|
|
53
|
+
return {
|
|
54
|
+
"bot-token": BOT_TOKEN,
|
|
55
|
+
"chat-id": "@mychannel",
|
|
56
|
+
text: "Deploy finished",
|
|
57
|
+
...overrides,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
interface PostRequest {
|
|
62
|
+
url: URL;
|
|
63
|
+
data: JSONObject;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getPostRequest(): PostRequest {
|
|
67
|
+
return apiPostMock.mock.calls[0]![0] as PostRequest;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function runAndGetError(args: JSONObject): Promise<Error> {
|
|
71
|
+
try {
|
|
72
|
+
await new SendMessageToChat().run(args, makeOptions());
|
|
73
|
+
} catch (err) {
|
|
74
|
+
return err as Error;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
throw new Error("Expected the component to throw");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
beforeEach(() => {
|
|
81
|
+
jest.clearAllMocks();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("Telegram SendMessageToChat - sending", () => {
|
|
85
|
+
test("sends to the Bot API sendMessage URL instead of failing on Invalid route", async () => {
|
|
86
|
+
apiPostMock.mockResolvedValue(
|
|
87
|
+
new HTTPResponse<JSONObject>(200, { ok: true, result: {} }, {}),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const result: RunReturnType = await new SendMessageToChat().run(
|
|
91
|
+
makeArgs(),
|
|
92
|
+
makeOptions(),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
expect(apiPostMock).toHaveBeenCalledTimes(1);
|
|
96
|
+
expect(getPostRequest().url.toString()).toBe(
|
|
97
|
+
`https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`,
|
|
98
|
+
);
|
|
99
|
+
expect(result.executePort?.id).toBe("success");
|
|
100
|
+
expect(result.returnValues).toEqual({});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("sends the chat id and text as the request body", async () => {
|
|
104
|
+
apiPostMock.mockResolvedValue(
|
|
105
|
+
new HTTPResponse<JSONObject>(200, { ok: true }, {}),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
await new SendMessageToChat().run(
|
|
109
|
+
makeArgs({ "chat-id": "-1001234567890", text: "Line 1\nLine 2" }),
|
|
110
|
+
makeOptions(),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
expect(getPostRequest().data).toEqual({
|
|
114
|
+
chat_id: "-1001234567890",
|
|
115
|
+
text: "Line 1\nLine 2",
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("dials api.telegram.org over https with the token only in the path", async () => {
|
|
120
|
+
apiPostMock.mockResolvedValue(
|
|
121
|
+
new HTTPResponse<JSONObject>(200, { ok: true }, {}),
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
await new SendMessageToChat().run(makeArgs(), makeOptions());
|
|
125
|
+
|
|
126
|
+
const url: URL = getPostRequest().url;
|
|
127
|
+
expect(url.isHttps()).toBe(true);
|
|
128
|
+
expect(url.hostname.hostname).toBe("api.telegram.org");
|
|
129
|
+
expect(url.route.toString()).toBe(`/bot${BOT_TOKEN}/sendMessage`);
|
|
130
|
+
expect(Object.keys(url.params)).toHaveLength(0);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test("trims whitespace a pasted or stored token picked up", async () => {
|
|
134
|
+
apiPostMock.mockResolvedValue(
|
|
135
|
+
new HTTPResponse<JSONObject>(200, { ok: true }, {}),
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const result: RunReturnType = await new SendMessageToChat().run(
|
|
139
|
+
makeArgs({ "bot-token": ` ${BOT_TOKEN}\n` }),
|
|
140
|
+
makeOptions(),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
expect(getPostRequest().url.toString()).toBe(
|
|
144
|
+
`https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`,
|
|
145
|
+
);
|
|
146
|
+
expect(result.executePort?.id).toBe("success");
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test.each([
|
|
150
|
+
["a short secret", "1:A"],
|
|
151
|
+
["a hyphen and underscore", "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"],
|
|
152
|
+
["a long bot id", "1234567890123:AAE_underscored-and-dashed"],
|
|
153
|
+
])("accepts a token with %s", async (_label: string, token: string) => {
|
|
154
|
+
apiPostMock.mockResolvedValue(
|
|
155
|
+
new HTTPResponse<JSONObject>(200, { ok: true }, {}),
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const result: RunReturnType = await new SendMessageToChat().run(
|
|
159
|
+
makeArgs({ "bot-token": token }),
|
|
160
|
+
makeOptions(),
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
expect(getPostRequest().url.toString()).toBe(
|
|
164
|
+
`https://api.telegram.org/bot${token}/sendMessage`,
|
|
165
|
+
);
|
|
166
|
+
expect(result.executePort?.id).toBe("success");
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("Telegram SendMessageToChat - Telegram errors go to the error port", () => {
|
|
171
|
+
test("an HTTP error returns Telegram's description", async () => {
|
|
172
|
+
apiPostMock.mockResolvedValue(
|
|
173
|
+
new HTTPErrorResponse(
|
|
174
|
+
400,
|
|
175
|
+
{ ok: false, description: "Bad Request: chat not found" },
|
|
176
|
+
{},
|
|
177
|
+
),
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const result: RunReturnType = await new SendMessageToChat().run(
|
|
181
|
+
makeArgs(),
|
|
182
|
+
makeOptions(),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
expect(result.executePort?.id).toBe("error");
|
|
186
|
+
expect(result.returnValues["error"]).toBe(
|
|
187
|
+
"Bad Request: chat not found" + HELP_MESSAGE,
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("an HTTP error with no description falls back to a generic message", async () => {
|
|
192
|
+
apiPostMock.mockResolvedValue(new HTTPErrorResponse(502, {}, {}));
|
|
193
|
+
|
|
194
|
+
const result: RunReturnType = await new SendMessageToChat().run(
|
|
195
|
+
makeArgs(),
|
|
196
|
+
makeOptions(),
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
expect(result.executePort?.id).toBe("error");
|
|
200
|
+
expect(result.returnValues["error"]).toBe("Server Error." + HELP_MESSAGE);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("a 200 with ok:false is still an error", async () => {
|
|
204
|
+
apiPostMock.mockResolvedValue(
|
|
205
|
+
new HTTPResponse<JSONObject>(
|
|
206
|
+
200,
|
|
207
|
+
{ ok: false, description: "Forbidden: bot was blocked by the user" },
|
|
208
|
+
{},
|
|
209
|
+
),
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const result: RunReturnType = await new SendMessageToChat().run(
|
|
213
|
+
makeArgs(),
|
|
214
|
+
makeOptions(),
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
expect(result.executePort?.id).toBe("error");
|
|
218
|
+
expect(result.returnValues["error"]).toBe(
|
|
219
|
+
"Forbidden: bot was blocked by the user" + HELP_MESSAGE,
|
|
220
|
+
);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("a thrown HTTP error response returns its description", async () => {
|
|
224
|
+
apiPostMock.mockRejectedValue(
|
|
225
|
+
new HTTPErrorResponse(
|
|
226
|
+
401,
|
|
227
|
+
{ ok: false, description: "Unauthorized" },
|
|
228
|
+
{},
|
|
229
|
+
),
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const result: RunReturnType = await new SendMessageToChat().run(
|
|
233
|
+
makeArgs(),
|
|
234
|
+
makeOptions(),
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
expect(result.executePort?.id).toBe("error");
|
|
238
|
+
expect(result.returnValues["error"]).toBe("Unauthorized" + HELP_MESSAGE);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("any other thrown error fails the step without quoting the token", async () => {
|
|
242
|
+
apiPostMock.mockRejectedValue(
|
|
243
|
+
new Error(
|
|
244
|
+
`connect ECONNREFUSED https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`,
|
|
245
|
+
),
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
const error: Error = await runAndGetError(makeArgs());
|
|
249
|
+
|
|
250
|
+
expect(error.message).toBe("Something wrong happened.");
|
|
251
|
+
expect(error.message).not.toContain(BOT_TOKEN);
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
describe("Telegram SendMessageToChat - argument validation", () => {
|
|
256
|
+
test.each([
|
|
257
|
+
["bot-token", "Telegram Bot Token not found"],
|
|
258
|
+
["chat-id", "Telegram Chat ID not found"],
|
|
259
|
+
["text", "Telegram message not found"],
|
|
260
|
+
])(
|
|
261
|
+
"refuses a missing %s without sending",
|
|
262
|
+
async (argument: string, expectedMessage: string) => {
|
|
263
|
+
const args: JSONObject = makeArgs();
|
|
264
|
+
delete args[argument];
|
|
265
|
+
|
|
266
|
+
const error: Error = await runAndGetError(args);
|
|
267
|
+
|
|
268
|
+
expect(error.message).toBe(expectedMessage);
|
|
269
|
+
expect(apiPostMock).not.toHaveBeenCalled();
|
|
270
|
+
},
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
test.each([
|
|
274
|
+
["only whitespace", " "],
|
|
275
|
+
["no colon", "8000000001AAFakeFakeFake"],
|
|
276
|
+
["a non-numeric bot id", "bot8000000001:AAFakeFakeFake"],
|
|
277
|
+
["an empty secret", "8000000001:"],
|
|
278
|
+
["an empty bot id", ":AAFakeFakeFake"],
|
|
279
|
+
["a space inside", "8000000001:AAFake Fake"],
|
|
280
|
+
["a path separator", "8000000001:AAFake/../../getUpdates"],
|
|
281
|
+
["a query", "8000000001:AAFake?offset=1"],
|
|
282
|
+
["a fragment", "8000000001:AAFake#x"],
|
|
283
|
+
["a second colon", "8000000001:AAFake:extra"],
|
|
284
|
+
["an '@'", "8000000001:AAFake@evil.example"],
|
|
285
|
+
["the '<BOT_TOKEN>' placeholder", "<BOT_TOKEN>"],
|
|
286
|
+
])(
|
|
287
|
+
"refuses a token with %s, without sending and without quoting it",
|
|
288
|
+
async (_label: string, token: string) => {
|
|
289
|
+
const error: Error = await runAndGetError(
|
|
290
|
+
makeArgs({ "bot-token": token }),
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
expect(error.message).toMatch(
|
|
294
|
+
/^Telegram Bot Token is not in a valid format\./,
|
|
295
|
+
);
|
|
296
|
+
if (token.trim()) {
|
|
297
|
+
expect(error.message).not.toContain(token.trim());
|
|
298
|
+
}
|
|
299
|
+
expect(apiPostMock).not.toHaveBeenCalled();
|
|
300
|
+
},
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
test("a malformed token never reaches the error message, even partially", async () => {
|
|
304
|
+
const secret: string = "AAFakeFakeFakeFakeFakeFakeFake_-12345";
|
|
305
|
+
|
|
306
|
+
const error: Error = await runAndGetError(
|
|
307
|
+
makeArgs({ "bot-token": `8000000001:${secret} trailing` }),
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
expect(error.message).not.toContain(secret);
|
|
311
|
+
expect(error.message).not.toContain("8000000001");
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
describe("Telegram SendMessageToChat.isValidBotToken", () => {
|
|
316
|
+
test.each([
|
|
317
|
+
[BOT_TOKEN, true],
|
|
318
|
+
["123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11", true],
|
|
319
|
+
["1:a", true],
|
|
320
|
+
["", false],
|
|
321
|
+
["123456", false],
|
|
322
|
+
["abc:def", false],
|
|
323
|
+
["123456:", false],
|
|
324
|
+
[" 123456:abc", false],
|
|
325
|
+
["123456:abc ", false],
|
|
326
|
+
["123456:abc/def", false],
|
|
327
|
+
["123456:abc%2Fdef", false],
|
|
328
|
+
])("isValidBotToken(%j) is %s", (token: string, expected: boolean) => {
|
|
329
|
+
expect(SendMessageToChat.isValidBotToken(token)).toBe(expected);
|
|
330
|
+
});
|
|
331
|
+
});
|
|
@@ -10,6 +10,7 @@ import LabelService from "../../../../Server/Services/LabelService";
|
|
|
10
10
|
import ServiceLevelObjectiveMonitorRule from "../../../../Models/DatabaseModels/ServiceLevelObjectiveMonitorRule";
|
|
11
11
|
import Label from "../../../../Models/DatabaseModels/Label";
|
|
12
12
|
import FilterCondition from "../../../../Types/Filter/FilterCondition";
|
|
13
|
+
import MonitorType from "../../../../Types/Monitor/MonitorType";
|
|
13
14
|
import {
|
|
14
15
|
RULE_CRITERIA_SCHEMA_VERSION,
|
|
15
16
|
RuleCriteriaOperator,
|
|
@@ -750,6 +751,7 @@ describe("query_slos — monitor rules", () => {
|
|
|
750
751
|
legacy.isEnabled = true;
|
|
751
752
|
legacy.monitorLabels = [buildLabel(PRODUCTION_LABEL_ID, "Production")];
|
|
752
753
|
legacy.monitorNamePattern = "^api-";
|
|
754
|
+
legacy.monitorType = MonitorType.API;
|
|
753
755
|
|
|
754
756
|
const configured: ServiceLevelObjectiveMonitorRule =
|
|
755
757
|
new ServiceLevelObjectiveMonitorRule();
|
|
@@ -765,6 +767,11 @@ describe("query_slos — monitor rules", () => {
|
|
|
765
767
|
operator: RuleCriteriaOperator.HasAnyOf,
|
|
766
768
|
value: [TIER1_LABEL_ID.toString()],
|
|
767
769
|
},
|
|
770
|
+
{
|
|
771
|
+
field: "monitorType",
|
|
772
|
+
operator: RuleCriteriaOperator.NotEquals,
|
|
773
|
+
value: MonitorType.Manual,
|
|
774
|
+
},
|
|
768
775
|
],
|
|
769
776
|
};
|
|
770
777
|
|
|
@@ -795,6 +802,8 @@ describe("query_slos — monitor rules", () => {
|
|
|
795
802
|
expect(result.dataForLlm).toContain("Production APIs");
|
|
796
803
|
expect(result.dataForLlm).toContain("Labels has any of");
|
|
797
804
|
expect(result.dataForLlm).toContain("Name matches pattern");
|
|
805
|
+
expect(result.dataForLlm).toContain("Type is");
|
|
806
|
+
expect(result.dataForLlm).toContain("Type is not");
|
|
798
807
|
expect(result.dataForLlm).toContain("Tier one");
|
|
799
808
|
// A criteria rule stores label ids; the name is looked up for the model.
|
|
800
809
|
expect(result.dataForLlm).toContain("Tier 1");
|
|
@@ -805,6 +814,7 @@ describe("query_slos — monitor rules", () => {
|
|
|
805
814
|
).toBe(SLO_ID.toString());
|
|
806
815
|
expect(call["props"]).toBe(ctx.props);
|
|
807
816
|
expect((call["select"] as JSONObject)["criteria"]).toBe(true);
|
|
817
|
+
expect((call["select"] as JSONObject)["monitorType"]).toBe(true);
|
|
808
818
|
expect((call["select"] as JSONObject)["monitorLabels"]).toEqual({
|
|
809
819
|
_id: true,
|
|
810
820
|
name: true,
|
|
@@ -49,7 +49,7 @@ import { beforeEach, describe, expect, jest, test } from "@jest/globals";
|
|
|
49
49
|
* "container.memory.usage value 1073741824.000 By is 4.20σ above ..."
|
|
50
50
|
*
|
|
51
51
|
* The quantities in it are now rendered exactly as the Breaching Samples
|
|
52
|
-
*
|
|
52
|
+
* list renders them. σ is NOT one of those quantities: it is a count of
|
|
53
53
|
* standard deviations, dimensionless by construction, and must stay a
|
|
54
54
|
* bare number no matter what unit the metric carries.
|
|
55
55
|
*/
|