@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
@@ -0,0 +1,304 @@
1
+ import Hostname from "../../../Types/API/Hostname";
2
+ import Protocol from "../../../Types/API/Protocol";
3
+ import Route from "../../../Types/API/Route";
4
+ import URL from "../../../Types/API/URL";
5
+ import BadDataException from "../../../Types/Exception/BadDataException";
6
+ import { describe, expect, test } from "@jest/globals";
7
+
8
+ /*
9
+ * Route refuses any value that starts with a scheme ("javascript:...", but
10
+ * equally "bot123:ABC/sendMessage"), so a scheme can never reach a navigation
11
+ * sink. URL.fromString stores a parsed path WITHOUT the "/" that separates it
12
+ * from the authority, and handed that bare path to Route. So any URL whose
13
+ * first path segment contains a ":" stopped parsing:
14
+ *
15
+ * URL.fromString("https://api.telegram.org/bot123:ABC/sendMessage")
16
+ * => BadDataException "Invalid route: bot123:ABC/sendMessage"
17
+ *
18
+ * Telegram puts the bot token - always "<id>:<secret>" - in exactly that
19
+ * segment. That broke the Telegram workflow component (and put the token in
20
+ * the run log), every API component call to Telegram, and every sandboxed
21
+ * axios call to Telegram, where the SSRF guard's parse failure surfaced as
22
+ * "Request URL is not a valid URL".
23
+ */
24
+
25
+ // Same shape as a real token: 10-digit bot id, 35-character secret.
26
+ const BOT_TOKEN: string = "8000000001:AAFakeFakeFakeFakeFakeFakeFake_-12345";
27
+ const TELEGRAM_URL: string = `https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`;
28
+
29
+ describe("URL.fromString - Telegram Bot API URLs", () => {
30
+ test("parses the sendMessage URL instead of throwing Invalid route", () => {
31
+ expect(() => {
32
+ return URL.fromString(TELEGRAM_URL);
33
+ }).not.toThrow();
34
+ });
35
+
36
+ test("round-trips to exactly the string that was parsed", () => {
37
+ expect(URL.fromString(TELEGRAM_URL).toString()).toBe(TELEGRAM_URL);
38
+ });
39
+
40
+ test("puts the whole bot token in the path, not in the host or port", () => {
41
+ const url: URL = URL.fromString(TELEGRAM_URL);
42
+
43
+ expect(url.protocol).toBe(Protocol.HTTPS);
44
+ expect(url.hostname.hostname).toBe("api.telegram.org");
45
+ expect(url.hostname.port).toBeUndefined();
46
+ expect(url.route.toString()).toBe(`/bot${BOT_TOKEN}/sendMessage`);
47
+ });
48
+
49
+ test("agrees with the WHATWG parser the HTTP client uses", () => {
50
+ const whatwg: globalThis.URL = new globalThis.URL(TELEGRAM_URL);
51
+ const url: URL = URL.fromString(TELEGRAM_URL);
52
+
53
+ expect(url.hostname.hostname).toBe(whatwg.hostname);
54
+ expect(url.route.toString()).toBe(whatwg.pathname);
55
+ expect(url.toString()).toBe(whatwg.href);
56
+ });
57
+
58
+ test.each([
59
+ "getMe",
60
+ "sendMessage",
61
+ "sendPhoto",
62
+ "getUpdates",
63
+ "setWebhook",
64
+ "getWebhookInfo",
65
+ ])("parses the %s method URL", (method: string) => {
66
+ const raw: string = `https://api.telegram.org/bot${BOT_TOKEN}/${method}`;
67
+
68
+ expect(URL.fromString(raw).toString()).toBe(raw);
69
+ });
70
+
71
+ test("keeps query parameters on a Telegram URL", () => {
72
+ const raw: string = `${TELEGRAM_URL}?chat_id=@mychannel&text=hello`;
73
+ const url: URL = URL.fromString(raw);
74
+
75
+ expect(url.route.toString()).toBe(`/bot${BOT_TOKEN}/sendMessage`);
76
+ expect(url.getQueryParam("chat_id")).toBe("@mychannel");
77
+ expect(url.getQueryParam("text")).toBe("hello");
78
+ expect(url.toString()).toBe(raw);
79
+ });
80
+
81
+ test("keeps the file download path, whose token is not the first segment", () => {
82
+ const raw: string = `https://api.telegram.org/file/bot${BOT_TOKEN}/photos/file_1.jpg`;
83
+
84
+ expect(URL.fromString(raw).toString()).toBe(raw);
85
+ });
86
+
87
+ test("fromStringLenient returns a real URL, not a malformed placeholder", () => {
88
+ const url: URL = URL.fromStringLenient(TELEGRAM_URL);
89
+
90
+ expect(url.isMalformed()).toBe(false);
91
+ expect(url.hostname.hostname).toBe("api.telegram.org");
92
+ expect(url.toString()).toBe(TELEGRAM_URL);
93
+ });
94
+
95
+ test("survives fromURL, toJSON and fromJSON", () => {
96
+ const url: URL = URL.fromString(TELEGRAM_URL);
97
+
98
+ expect(URL.fromURL(url).toString()).toBe(TELEGRAM_URL);
99
+
100
+ const revived: URL = URL.fromJSON(url.toJSON());
101
+ expect(revived.isMalformed()).toBe(false);
102
+ expect(revived.toString()).toBe(TELEGRAM_URL);
103
+ });
104
+
105
+ test("removeQueryString keeps the token path", () => {
106
+ const url: URL = URL.fromString(`${TELEGRAM_URL}?chat_id=1`);
107
+
108
+ expect(url.removeQueryString().toString()).toBe(TELEGRAM_URL);
109
+ });
110
+
111
+ test("addQueryParam appends to a Telegram URL", () => {
112
+ const url: URL = URL.fromString(TELEGRAM_URL).addQueryParam(
113
+ "chat_id",
114
+ "@my channel",
115
+ true,
116
+ );
117
+
118
+ expect(url.toString()).toBe(`${TELEGRAM_URL}?chat_id=%40my%20channel`);
119
+ });
120
+
121
+ test("getLastRoute reads the method and the token segment", () => {
122
+ const url: URL = URL.fromString(TELEGRAM_URL);
123
+
124
+ expect(url.getLastRoute()?.toString()).toBe("/sendMessage");
125
+ expect(url.getLastRoute(1)?.toString()).toBe(`/bot${BOT_TOKEN}`);
126
+ });
127
+ });
128
+
129
+ describe("URL.fromString - any first path segment containing a ':'", () => {
130
+ test.each([
131
+ ["an id:secret pair", "https://example.com/abc:def"],
132
+ ["a URN", "https://example.com/urn:uuid:6e8bc430-9c3a-11d9-9669"],
133
+ ["a trailing slash", "https://example.com/bot1:abc/"],
134
+ ["a single segment", "https://example.com/bot1:abc"],
135
+ ["a port", "https://example.com:8443/bot1:abc/sendMessage"],
136
+ ["userinfo", "https://user:pw@example.com/bot1:abc/sendMessage"],
137
+ ["plain http", "http://example.com/bot1:abc/sendMessage"],
138
+ ["a fragment", "https://example.com/bot1:abc/sendMessage#top"],
139
+ ["a query", "https://example.com/bot1:abc/sendMessage?a=1&b=2"],
140
+ ["a dotted scheme-like name", "https://example.com/a.b+c-d:e/f"],
141
+ ])("parses and round-trips a path with %s", (_label: string, raw: string) => {
142
+ const url: URL = URL.fromString(raw);
143
+
144
+ expect(url.toString()).toBe(raw);
145
+ });
146
+
147
+ test("an uppercase scheme on the URL is normalised, the path is kept", () => {
148
+ const url: URL = URL.fromString(
149
+ "HTTPS://api.telegram.org/bot1:abc/sendMessage",
150
+ );
151
+
152
+ expect(url.toString()).toBe(
153
+ "https://api.telegram.org/bot1:abc/sendMessage",
154
+ );
155
+ });
156
+
157
+ test("a path that merely looks like a dangerous scheme stays a path", () => {
158
+ /*
159
+ * Before Route refused scheme-like values this parsed to a route of
160
+ * "javascript:alert(1)" - a value that is a live href on its own. It now
161
+ * keeps its "/", so the Route is a same-origin path and can never be
162
+ * mistaken for a scheme by anything that renders it.
163
+ */
164
+ const url: URL = URL.fromString("https://example.com/javascript:alert(1)");
165
+ const route: string = url.route.toString();
166
+
167
+ expect(url.hostname.hostname).toBe("example.com");
168
+ expect(route).toBe("/javascript:alert(1)");
169
+ expect(route.startsWith("/")).toBe(true);
170
+ expect(Route.hasSchemePrefix(route)).toBe(false);
171
+ expect(url.toString()).toBe("https://example.com/javascript:alert(1)");
172
+ });
173
+
174
+ test.each([
175
+ "https://example.com/bot1:abc/sendMessage",
176
+ "https://example.com/mailto:someone@example.com",
177
+ "https://example.com/data:text/plain,hi",
178
+ "https://example.com/urn:isbn:0451450523",
179
+ ])("the route of %s is never itself scheme-like", (raw: string) => {
180
+ expect(Route.hasSchemePrefix(URL.fromString(raw).route.toString())).toBe(
181
+ false,
182
+ );
183
+ });
184
+
185
+ test("the host is still read off the authority, never off the path", () => {
186
+ // The ":" in the path must not be mistaken for a host:port separator.
187
+ const url: URL = URL.fromString("https://10.0.0.5/bot1:80/sendMessage");
188
+
189
+ expect(url.hostname.hostname).toBe("10.0.0.5");
190
+ expect(url.hostname.port).toBeUndefined();
191
+ });
192
+ });
193
+
194
+ describe("URL.fromString - paths that already parsed are unchanged", () => {
195
+ /*
196
+ * The leading "/" is only kept when dropping it would make the path read as
197
+ * a scheme. Every other path keeps the bare representation it always had,
198
+ * so nothing that read url.route before this change sees a different value.
199
+ */
200
+ test.each([
201
+ ["https://example.com/api/v1/items", "api/v1/items"],
202
+ ["https://example.com/docs#section", "docs#section"],
203
+ ["https://example.com#section", "#section"],
204
+ ["https://example.com/p/", "p/"],
205
+ ["https://vision.googleapis.com/v1/images:annotate", "v1/images:annotate"],
206
+ ["https://example.com/api/:resourceId", "api/:resourceId"],
207
+ ["https://example.com/1:2/x", "1:2/x"],
208
+ [
209
+ "https://example.com/_matrix/client/v3/rooms/!r:m.org",
210
+ "_matrix/client/v3/rooms/!r:m.org",
211
+ ],
212
+ ["https://example.com/:leading-colon", ":leading-colon"],
213
+ ["https://example.com/events?at=12:30", "events"],
214
+ ["https://example.com", ""],
215
+ ["https://example.com/", ""],
216
+ ])("%s keeps route %s", (raw: string, expectedRoute: string) => {
217
+ expect(URL.fromString(raw).route.toString()).toBe(expectedRoute);
218
+ });
219
+ });
220
+
221
+ describe("URL.fromString - '~' in a path", () => {
222
+ /*
223
+ * "~" is an RFC 3986 unreserved character, but Route's character allowlist
224
+ * left it out, so "https://example.com/~user" could not be parsed - and the
225
+ * sandboxed axios bridge refused it as "Request URL is not a valid URL".
226
+ */
227
+ test.each([
228
+ "https://example.com/~user/hook",
229
+ "https://example.com/files/~backup",
230
+ "https://example.com/a~b/c",
231
+ "https://example.com/path?x=~y",
232
+ ])("parses and round-trips %s", (raw: string) => {
233
+ expect(URL.fromString(raw).toString()).toBe(raw);
234
+ });
235
+ });
236
+
237
+ describe("URL.addRoute with a scheme-like segment", () => {
238
+ test("appends a string segment that starts with a token", () => {
239
+ const url: URL = URL.fromString("https://api.telegram.org").addRoute(
240
+ `bot${BOT_TOKEN}/sendMessage`,
241
+ );
242
+
243
+ expect(url.toString()).toBe(TELEGRAM_URL);
244
+ });
245
+
246
+ test("appends the same segment given with its leading slash", () => {
247
+ const url: URL = URL.fromString("https://api.telegram.org").addRoute(
248
+ `/bot${BOT_TOKEN}/sendMessage`,
249
+ );
250
+
251
+ expect(url.toString()).toBe(TELEGRAM_URL);
252
+ });
253
+
254
+ test("appends a method to a URL that already ends in the token", () => {
255
+ const url: URL = URL.fromString(
256
+ `https://api.telegram.org/bot${BOT_TOKEN}`,
257
+ ).addRoute("sendMessage");
258
+
259
+ expect(url.toString()).toBe(TELEGRAM_URL);
260
+ });
261
+
262
+ test("appends to a URL built from the constructor", () => {
263
+ const url: URL = new URL(
264
+ Protocol.HTTPS,
265
+ new Hostname("api.telegram.org"),
266
+ ).addRoute(`bot${BOT_TOKEN}/sendMessage`);
267
+
268
+ expect(url.toString()).toBe(TELEGRAM_URL);
269
+ });
270
+
271
+ test("still behaves as before for an ordinary segment", () => {
272
+ expect(
273
+ URL.fromString("https://example.com/api").addRoute("v1/items").toString(),
274
+ ).toBe("https://example.com/api/v1/items");
275
+ expect(
276
+ URL.fromString("https://example.com/api").addRoute("/v1").toString(),
277
+ ).toBe("https://example.com/api/v1");
278
+ });
279
+
280
+ test("still refuses characters no route may contain", () => {
281
+ expect(() => {
282
+ URL.fromString("https://example.com").addRoute("a b");
283
+ }).toThrowError(BadDataException);
284
+ });
285
+ });
286
+
287
+ describe("URL.fromString - the scheme checks are not loosened", () => {
288
+ test.each([
289
+ "javascript:alert(1)",
290
+ "data:text/html,<script>alert(1)</script>",
291
+ "file:///etc/passwd",
292
+ "ftp://example.com/bot1:abc/sendMessage",
293
+ ])("still refuses %s", (raw: string) => {
294
+ expect(() => {
295
+ return URL.fromString(raw);
296
+ }).toThrowError(BadDataException);
297
+ });
298
+
299
+ test("a bare scheme-like Route is still refused on its own", () => {
300
+ expect(() => {
301
+ return new Route(`bot${BOT_TOKEN}/sendMessage`);
302
+ }).toThrowError(BadDataException);
303
+ });
304
+ });