@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.
Files changed (196) hide show
  1. package/Models/DatabaseModels/Index.ts +4 -0
  2. package/Models/DatabaseModels/ServiceLevelObjectiveFeed.ts +2 -0
  3. package/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.ts +522 -0
  4. package/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.ts +38 -4
  5. package/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.ts +603 -0
  6. package/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.ts +248 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.ts +19 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  9. package/Server/Services/DatabaseService.ts +10 -0
  10. package/Server/Services/Index.ts +4 -0
  11. package/Server/Services/MonitorService.ts +3 -2
  12. package/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.ts +385 -0
  13. package/Server/Services/ServiceLevelObjectiveLabelRuleService.ts +51 -0
  14. package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +92 -16
  15. package/Server/Services/ServiceLevelObjectiveMonitorRuleService.ts +36 -1
  16. package/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.ts +521 -0
  17. package/Server/Services/ServiceLevelObjectiveOwnerRuleService.ts +51 -0
  18. package/Server/Services/ServiceLevelObjectiveService.ts +28 -0
  19. package/Server/Types/Workflow/Components/Telegram/SendMessageToChat.ts +27 -1
  20. package/Server/Utils/AI/Toolbox/SloTools.ts +1 -0
  21. package/Server/Utils/Monitor/AffectedResourceList.ts +31 -73
  22. package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +1 -1
  23. package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +1 -1
  24. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +181 -136
  25. package/Server/Utils/Monitor/RootCauseList.ts +139 -0
  26. package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +28 -0
  27. package/Server/Utils/Slo/SloRulePatternValidator.ts +42 -0
  28. package/Tests/App/Dashboard/FeedFilterSortIntegration.test.tsx +1632 -0
  29. package/Tests/App/Dashboard/FeedOptionsLocales.test.ts +250 -0
  30. package/Tests/App/Dashboard/FeedOrdering.test.ts +279 -9
  31. package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +60 -1
  32. package/Tests/App/Dashboard/InvestigationPanel.test.tsx +594 -3
  33. package/Tests/App/Dashboard/ProductOrientation.test.tsx +646 -0
  34. package/Tests/App/Dashboard/ResourceFeed.test.tsx +612 -2
  35. package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +30 -0
  36. package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +1202 -0
  37. package/Tests/App/Dashboard/ScheduledMaintenanceFeedRefresh.test.tsx +52 -1
  38. package/Tests/App/Dashboard/SloFeed.test.tsx +106 -1
  39. package/Tests/App/Dashboard/SloMonitorRuleCriteria.test.tsx +566 -0
  40. package/Tests/App/Dashboard/SloNavigationWiring.test.tsx +6 -4
  41. package/Tests/App/Dashboard/SloNoticeBanner.test.tsx +186 -0
  42. package/Tests/App/Dashboard/SloOverviewKpiStrip.test.tsx +388 -44
  43. package/Tests/App/Dashboard/SloOverviewLayout.test.tsx +613 -0
  44. package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +0 -8
  45. package/Tests/Models/DatabaseModels/SloLabelAndOwnerRuleModels.test.ts +404 -0
  46. package/Tests/Models/DatabaseModels/SloOverhaulModels.test.ts +17 -1
  47. package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
  48. package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
  49. package/Tests/Server/Infrastructure/Postgres/AddSloLabelAndOwnerRulesMigration.test.ts +407 -0
  50. package/Tests/Server/Infrastructure/Postgres/AddSloMonitorRuleMonitorTypeMigration.test.ts +171 -0
  51. package/Tests/Server/Infrastructure/Postgres/SloProductOverhaulMigration.test.ts +88 -4
  52. package/Tests/Server/Services/DatabaseServiceRuleCriteriaValidation.test.ts +104 -0
  53. package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +48 -1
  54. package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -0
  55. package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleEngines.test.ts +1170 -0
  56. package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleServices.test.ts +172 -0
  57. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +323 -0
  58. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleService.test.ts +115 -0
  59. package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleTriggers.test.ts +21 -2
  60. package/Tests/Server/Services/ServiceLevelObjectiveMonitorTypePostgres.test.ts +480 -0
  61. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +162 -0
  62. package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +45 -6
  63. package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +745 -0
  64. package/Tests/Server/Types/Markdown.test.ts +2 -1
  65. package/Tests/Server/Types/Workflow/Components/ApiComponentSsrf.test.ts +49 -0
  66. package/Tests/Server/Types/Workflow/Components/TelegramSendMessageToChat.test.ts +331 -0
  67. package/Tests/Server/Utils/AI/SloTools.test.ts +10 -0
  68. package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +1 -1
  69. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +405 -0
  70. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorBreachingSamples.test.ts +1101 -0
  71. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +106 -80
  72. package/Tests/Server/Utils/Monitor/RootCauseList.test.ts +252 -0
  73. package/Tests/Server/Utils/Rules/RuleRun/RuleRunPermission.test.ts +154 -0
  74. package/Tests/Server/Utils/SSRFProtection.test.ts +111 -0
  75. package/Tests/Server/Utils/Slo/SloRulePatternValidator.test.ts +67 -0
  76. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +213 -0
  77. package/Tests/Types/API/Route.test.ts +56 -0
  78. package/Tests/Types/API/URLSchemeLikePath.test.ts +304 -0
  79. package/Tests/UI/Components/FeedOptions.test.ts +1404 -0
  80. package/Tests/UI/Components/FeedOptionsButton.test.tsx +2603 -0
  81. package/Tests/UI/Components/Icon.test.tsx +26 -0
  82. package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
  83. package/Tests/UI/Components/useFeedItemsViewKey.test.tsx +1017 -0
  84. package/Tests/UI/Components/useFeedOptions.test.tsx +1107 -0
  85. package/Tests/Utils/LabelRuleImportExport.test.ts +102 -0
  86. package/Tests/Utils/Slo/SloDuration.test.ts +115 -24
  87. package/Tests/Utils/Slo/SloHealth.test.ts +93 -10
  88. package/Tests/Utils/Slo/SloMonitorRuleCriteria.test.ts +109 -0
  89. package/Tests/Utils/Slo/SloOverviewText.test.ts +64 -0
  90. package/Types/API/Route.ts +19 -2
  91. package/Types/API/URL.ts +26 -2
  92. package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +6 -5
  93. package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +9 -0
  94. package/Types/Permission.ts +90 -0
  95. package/Types/Rules/RuleCriteriaFieldRegistry.ts +11 -0
  96. package/Types/Rules/RuleRun.ts +4 -0
  97. package/UI/Components/Feed/FeedOptions.ts +427 -0
  98. package/UI/Components/Feed/FeedOptionsButton.tsx +692 -0
  99. package/UI/Components/Feed/useFeedItems.ts +28 -9
  100. package/UI/Components/Feed/useFeedOptions.ts +225 -0
  101. package/UI/Components/Icon/Icon.tsx +1 -1
  102. package/Utils/LabelRuleImportExport.ts +27 -9
  103. package/Utils/Monitor/MetricValueFormatter.ts +2 -2
  104. package/Utils/Rules/MonitorTypeRuleCriteria.ts +31 -0
  105. package/Utils/Slo/SloDuration.ts +22 -6
  106. package/Utils/Slo/SloHealth.ts +8 -7
  107. package/Utils/Slo/SloMonitorRuleCriteria.ts +33 -1
  108. package/Utils/Slo/SloOverviewText.ts +33 -0
  109. package/Utils/Slo/SloProjection.ts +4 -3
  110. package/build/dist/Models/DatabaseModels/Index.js +4 -0
  111. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  112. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js +2 -0
  113. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js.map +1 -1
  114. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js +530 -0
  115. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js.map +1 -0
  116. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js +39 -4
  117. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js.map +1 -1
  118. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js +612 -0
  119. package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js.map +1 -0
  120. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js +93 -0
  121. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js.map +1 -0
  122. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js +12 -0
  123. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js.map +1 -0
  124. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  125. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  126. package/build/dist/Server/Services/DatabaseService.js +7 -0
  127. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  128. package/build/dist/Server/Services/Index.js +4 -0
  129. package/build/dist/Server/Services/Index.js.map +1 -1
  130. package/build/dist/Server/Services/MonitorService.js +3 -2
  131. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  132. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js +295 -0
  133. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js.map +1 -0
  134. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js +56 -0
  135. package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js.map +1 -0
  136. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +59 -14
  137. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -1
  138. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js +22 -1
  139. package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js.map +1 -1
  140. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js +400 -0
  141. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js.map +1 -0
  142. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js +56 -0
  143. package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js.map +1 -0
  144. package/build/dist/Server/Services/ServiceLevelObjectiveService.js +21 -0
  145. package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
  146. package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js +20 -1
  147. package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js.map +1 -1
  148. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +1 -0
  149. package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -1
  150. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +22 -51
  151. package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -1
  152. package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +1 -1
  153. package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
  154. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +139 -109
  155. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  156. package/build/dist/Server/Utils/Monitor/RootCauseList.js +101 -0
  157. package/build/dist/Server/Utils/Monitor/RootCauseList.js.map +1 -0
  158. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +28 -0
  159. package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
  160. package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js +28 -0
  161. package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js.map +1 -0
  162. package/build/dist/Types/API/Route.js +18 -2
  163. package/build/dist/Types/API/Route.js.map +1 -1
  164. package/build/dist/Types/API/URL.js +23 -2
  165. package/build/dist/Types/API/URL.js.map +1 -1
  166. package/build/dist/Types/Permission.js +84 -0
  167. package/build/dist/Types/Permission.js.map +1 -1
  168. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +11 -0
  169. package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
  170. package/build/dist/Types/Rules/RuleRun.js +4 -0
  171. package/build/dist/Types/Rules/RuleRun.js.map +1 -1
  172. package/build/dist/UI/Components/Feed/FeedOptions.js +235 -0
  173. package/build/dist/UI/Components/Feed/FeedOptions.js.map +1 -0
  174. package/build/dist/UI/Components/Feed/FeedOptionsButton.js +346 -0
  175. package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -0
  176. package/build/dist/UI/Components/Feed/useFeedItems.js +17 -7
  177. package/build/dist/UI/Components/Feed/useFeedItems.js.map +1 -1
  178. package/build/dist/UI/Components/Feed/useFeedOptions.js +130 -0
  179. package/build/dist/UI/Components/Feed/useFeedOptions.js.map +1 -0
  180. package/build/dist/UI/Components/Icon/Icon.js +1 -1
  181. package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
  182. package/build/dist/Utils/LabelRuleImportExport.js +22 -6
  183. package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
  184. package/build/dist/Utils/Monitor/MetricValueFormatter.js +2 -2
  185. package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js +18 -0
  186. package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js.map +1 -0
  187. package/build/dist/Utils/Slo/SloDuration.js +13 -5
  188. package/build/dist/Utils/Slo/SloDuration.js.map +1 -1
  189. package/build/dist/Utils/Slo/SloHealth.js +8 -7
  190. package/build/dist/Utils/Slo/SloHealth.js.map +1 -1
  191. package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js +16 -1
  192. package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js.map +1 -1
  193. package/build/dist/Utils/Slo/SloOverviewText.js +16 -0
  194. package/build/dist/Utils/Slo/SloOverviewText.js.map +1 -1
  195. package/build/dist/Utils/Slo/SloProjection.js.map +1 -1
  196. 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 carries a
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
- * table renders them. σ is NOT one of those quantities: it is a count of
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
  */