@oneuptime/common 12.0.12 → 12.0.13

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 (234) hide show
  1. package/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.ts +32 -5
  2. package/Models/DatabaseModels/NetworkDevice.ts +64 -0
  3. package/Models/DatabaseModels/StatusPage.ts +59 -0
  4. package/Models/DatabaseModels/StatusPageOidc.ts +2 -0
  5. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +19 -2
  6. package/Server/API/AIChatAPI.ts +55 -56
  7. package/Server/API/CommonAPI.ts +89 -0
  8. package/Server/API/MicrosoftTeamsAPI.ts +20 -9
  9. package/Server/API/SlackAPI.ts +13 -10
  10. package/Server/API/StatusPageAPI.ts +2197 -2128
  11. package/Server/API/TelemetryAPI.ts +72 -3
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.ts +30 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.ts +47 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  15. package/Server/Middleware/UserAuthorization.ts +11 -2
  16. package/Server/Services/ApiKeyPermissionService.ts +116 -2
  17. package/Server/Services/DatabaseService.ts +17 -6
  18. package/Server/Services/LogAggregationService.ts +34 -0
  19. package/Server/Services/LogService.ts +21 -0
  20. package/Server/Services/NetworkSiteService.ts +12 -0
  21. package/Server/Services/SpanService.ts +21 -0
  22. package/Server/Services/TraceAggregationService.ts +15 -0
  23. package/Server/Services/WorkspaceNotificationRuleService.ts +172 -220
  24. package/Server/Types/AnalyticsDatabase/ModelPermission.ts +9 -0
  25. package/Server/Types/Database/Permissions/AccessControlPermission.ts +47 -16
  26. package/Server/Utils/APIKey/AccessPermission.ts +16 -40
  27. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +144 -0
  28. package/Server/Utils/LogRedaction.ts +576 -0
  29. package/Server/Utils/Logger.ts +123 -46
  30. package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +94 -23
  31. package/Server/Utils/Monitor/MonitorResource.ts +29 -9
  32. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +28 -7
  33. package/Server/Utils/SessionReplay/SessionReplayGateCacheStore.ts +56 -10
  34. package/Server/Utils/StartServer.ts +30 -9
  35. package/Server/Utils/StatusPageSearchEngineIndexing.ts +33 -0
  36. package/Server/Utils/Telemetry/ResourceEntityFilter.ts +441 -0
  37. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +488 -46
  38. package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +300 -0
  39. package/Tests/App/StatusPage/StatusPageIndexPageRobotsMeta.test.ts +130 -0
  40. package/Tests/Models/DatabaseModels/DatabaseBaseModelToJSONObjectCache.test.ts +180 -0
  41. package/Tests/Models/StatusPageEnableSearchEngineIndexing.test.ts +157 -0
  42. package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +23 -1
  43. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +785 -0
  44. package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +21 -8
  45. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +290 -0
  46. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +7 -1
  47. package/Tests/Server/API/StatusPageOverviewCache.test.ts +474 -0
  48. package/Tests/Server/API/StatusPageSeoSearchEngineIndexing.test.ts +189 -0
  49. package/Tests/Server/Infrastructure/InMemoryTTLCache.test.ts +158 -0
  50. package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +9 -60
  51. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +24 -15
  52. package/Tests/Server/Middleware/UserAuthorization.test.ts +79 -0
  53. package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +332 -0
  54. package/Tests/Server/Services/ApiKeyPermissionService.test.ts +347 -0
  55. package/Tests/Server/Services/DatabaseServiceUpdateDebugLogging.test.ts +282 -0
  56. package/Tests/Server/Services/NetworkSiteService.test.ts +56 -2
  57. package/Tests/Server/Services/TelemetryResourceFacetFilters.test.ts +267 -0
  58. package/Tests/Server/Services/WorkspaceNotificationRuleTestRuleChannels.test.ts +702 -0
  59. package/Tests/Server/Types/Database/Permissions/AccessControlPermission.test.ts +165 -0
  60. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +48 -36
  61. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +213 -0
  62. package/Tests/Server/Utils/LogRedaction.test.ts +415 -0
  63. package/Tests/Server/Utils/LoggerCredentialLeak.test.ts +245 -0
  64. package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +374 -4
  65. package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +455 -0
  66. package/Tests/Server/Utils/Monitor/NetworkDeviceReachabilityRoundTrip.test.ts +369 -0
  67. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +86 -8
  68. package/Tests/Server/Utils/PrivacyFilterUtil.test.ts +64 -0
  69. package/Tests/Server/Utils/SessionReplay/SessionReplayGateCacheStore.test.ts +207 -0
  70. package/Tests/Server/Utils/StartServerBodyVerify.test.ts +335 -0
  71. package/Tests/Server/Utils/StatusPageSearchEngineIndexing.test.ts +60 -0
  72. package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +435 -0
  73. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +607 -62
  74. package/Tests/Server/Utils/Workspace/MicrosoftTeamsInstalledTeamIdSpace.test.ts +778 -0
  75. package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +44 -10
  76. package/Tests/Types/API/HostnameFromAuthority.test.ts +164 -0
  77. package/Tests/Types/Database/AccessControl/ColumnAccessControlCache.test.ts +344 -0
  78. package/Tests/Types/Database/DatabasePropertyFindOperator.test.ts +518 -0
  79. package/Tests/Types/Database/TableColumnMetadataCache.test.ts +286 -0
  80. package/Tests/Types/Monitor/MonitorStep.test.ts +100 -0
  81. package/Tests/Types/Monitor/MonitorTypeKeywords.test.ts +360 -0
  82. package/Tests/Types/StatusPage/SearchEngineIndexing.test.ts +86 -0
  83. package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +254 -0
  84. package/Tests/UI/Components/CardSelect.test.tsx +1021 -0
  85. package/Tests/UI/Components/MasterPage.test.tsx +36 -1
  86. package/Tests/Utils/Monitor/MonitorMetricType.test.ts +66 -1
  87. package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +115 -6
  88. package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +610 -0
  89. package/Tests/Utils/NetworkSite/SiteStatusRollupUtil.test.ts +114 -45
  90. package/Types/API/Hostname.ts +79 -0
  91. package/Types/Database/AccessControl/ColumnAccessControl.ts +33 -11
  92. package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +33 -11
  93. package/Types/Database/DatabaseProperty.ts +38 -0
  94. package/Types/Database/TableColumn.ts +34 -7
  95. package/Types/Events/Recurring.ts +16 -1
  96. package/Types/Monitor/MonitorStep.ts +13 -1
  97. package/Types/Monitor/MonitorType.ts +345 -40
  98. package/Types/Monitor/SSLMonitor/SslMonitorResponse.ts +43 -0
  99. package/Types/StatusPage/SearchEngineIndexing.ts +61 -0
  100. package/Types/Telemetry/ResourceEntityFacet.ts +211 -0
  101. package/UI/Components/CardSelect/CardSelect.tsx +714 -105
  102. package/UI/Components/EmptyState/EmptyState.tsx +8 -1
  103. package/UI/Components/Footer/Footer.tsx +32 -6
  104. package/UI/Components/Forms/Fields/FormField.tsx +3 -0
  105. package/UI/Components/Forms/Types/Field.ts +8 -0
  106. package/UI/Components/Loader/PageLoader.tsx +29 -8
  107. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +26 -4
  108. package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +17 -1
  109. package/UI/Components/MasterPage/MasterPage.tsx +24 -11
  110. package/Utils/Monitor/MonitorMetricType.ts +16 -1
  111. package/Utils/Monitor/NetworkTopologyUtil.ts +60 -8
  112. package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +262 -0
  113. package/Utils/NetworkSite/SiteStatusRollupUtil.ts +41 -24
  114. package/Utils/Telemetry/CrossSignalScope.ts +75 -4
  115. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +20 -4
  116. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  117. package/build/dist/Models/DatabaseModels/NetworkDevice.js +66 -0
  118. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  119. package/build/dist/Models/DatabaseModels/StatusPage.js +60 -0
  120. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  121. package/build/dist/Models/DatabaseModels/StatusPageOidc.js +2 -0
  122. package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
  123. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  124. package/build/dist/Server/API/AIChatAPI.js +48 -35
  125. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  126. package/build/dist/Server/API/CommonAPI.js +59 -0
  127. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  128. package/build/dist/Server/API/MicrosoftTeamsAPI.js +20 -6
  129. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  130. package/build/dist/Server/API/SlackAPI.js +13 -10
  131. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  132. package/build/dist/Server/API/StatusPageAPI.js +745 -673
  133. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  134. package/build/dist/Server/API/TelemetryAPI.js +34 -3
  135. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  136. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js +23 -0
  137. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js.map +1 -0
  138. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js +34 -0
  139. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js.map +1 -0
  140. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  141. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  142. package/build/dist/Server/Middleware/UserAuthorization.js +6 -2
  143. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  144. package/build/dist/Server/Services/ApiKeyPermissionService.js +90 -1
  145. package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
  146. package/build/dist/Server/Services/DatabaseService.js +15 -6
  147. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  148. package/build/dist/Server/Services/LogAggregationService.js +2 -0
  149. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  150. package/build/dist/Server/Services/LogService.js +16 -0
  151. package/build/dist/Server/Services/LogService.js.map +1 -1
  152. package/build/dist/Server/Services/NetworkSiteService.js +12 -0
  153. package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
  154. package/build/dist/Server/Services/SpanService.js +16 -0
  155. package/build/dist/Server/Services/SpanService.js.map +1 -1
  156. package/build/dist/Server/Services/TraceAggregationService.js +2 -0
  157. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  158. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +137 -164
  159. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  160. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +9 -0
  161. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
  162. package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js +21 -13
  163. package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js.map +1 -1
  164. package/build/dist/Server/Utils/APIKey/AccessPermission.js +9 -32
  165. package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
  166. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +87 -0
  167. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  168. package/build/dist/Server/Utils/LogRedaction.js +449 -0
  169. package/build/dist/Server/Utils/LogRedaction.js.map +1 -0
  170. package/build/dist/Server/Utils/Logger.js +98 -45
  171. package/build/dist/Server/Utils/Logger.js.map +1 -1
  172. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +65 -13
  173. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
  174. package/build/dist/Server/Utils/Monitor/MonitorResource.js +20 -3
  175. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  176. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +27 -7
  177. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
  178. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js +29 -10
  179. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js.map +1 -1
  180. package/build/dist/Server/Utils/StartServer.js +10 -7
  181. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  182. package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js +24 -0
  183. package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js.map +1 -0
  184. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +311 -0
  185. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -0
  186. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +366 -37
  187. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  188. package/build/dist/Types/API/Hostname.js +63 -0
  189. package/build/dist/Types/API/Hostname.js.map +1 -1
  190. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +20 -6
  191. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
  192. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +20 -6
  193. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
  194. package/build/dist/Types/Database/DatabaseProperty.js +38 -0
  195. package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
  196. package/build/dist/Types/Database/TableColumn.js +24 -6
  197. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  198. package/build/dist/Types/Events/Recurring.js +13 -1
  199. package/build/dist/Types/Events/Recurring.js.map +1 -1
  200. package/build/dist/Types/Monitor/MonitorStep.js +9 -1
  201. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  202. package/build/dist/Types/Monitor/MonitorType.js +330 -31
  203. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  204. package/build/dist/Types/StatusPage/SearchEngineIndexing.js +55 -0
  205. package/build/dist/Types/StatusPage/SearchEngineIndexing.js.map +1 -0
  206. package/build/dist/Types/Telemetry/ResourceEntityFacet.js +156 -0
  207. package/build/dist/Types/Telemetry/ResourceEntityFacet.js.map +1 -0
  208. package/build/dist/UI/Components/CardSelect/CardSelect.js +360 -40
  209. package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
  210. package/build/dist/UI/Components/EmptyState/EmptyState.js +1 -1
  211. package/build/dist/UI/Components/EmptyState/EmptyState.js.map +1 -1
  212. package/build/dist/UI/Components/Footer/Footer.js +7 -4
  213. package/build/dist/UI/Components/Footer/Footer.js.map +1 -1
  214. package/build/dist/UI/Components/Forms/Fields/FormField.js +1 -1
  215. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  216. package/build/dist/UI/Components/Loader/PageLoader.js +15 -5
  217. package/build/dist/UI/Components/Loader/PageLoader.js.map +1 -1
  218. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +16 -3
  219. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  220. package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js +13 -1
  221. package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
  222. package/build/dist/UI/Components/MasterPage/MasterPage.js +10 -2
  223. package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
  224. package/build/dist/Utils/Monitor/MonitorMetricType.js +13 -1
  225. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  226. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +34 -7
  227. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  228. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +177 -0
  229. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -0
  230. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js +23 -27
  231. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js.map +1 -1
  232. package/build/dist/Utils/Telemetry/CrossSignalScope.js +44 -4
  233. package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
  234. package/package.json +1 -1
@@ -0,0 +1,158 @@
1
+ import InMemoryTTLCache from "../../../Server/Infrastructure/InMemoryTTLCache";
2
+ import { afterEach, describe, expect, jest, test } from "@jest/globals";
3
+
4
+ /*
5
+ * Contract under test — the bounded TTL cache the telemetry ingest rule
6
+ * caches (log/trace pipelines, drop filters, scrub rules, metric rules)
7
+ * sit on. Two properties matter for those callers:
8
+ *
9
+ * 1. Expiry physically deletes: a read past the TTL must both miss AND
10
+ * shrink the store, or per-project entries accumulate for every tenant
11
+ * ever seen by a long-lived ingest process.
12
+ * 2. Capacity is a hard cap with oldest-write eviction, so the cache can
13
+ * never grow past maxEntries no matter how many distinct keys arrive.
14
+ */
15
+
16
+ /*
17
+ * Minimal structural view of a jest spy — the @jest/globals and @types/jest
18
+ * spy types disagree in this repo, so annotate with just the surface these
19
+ * tests use.
20
+ */
21
+ type NowSpyLike = {
22
+ mockReturnValue: (value: number) => unknown;
23
+ mockRestore: () => void;
24
+ };
25
+
26
+ function spyNow(value: number): NowSpyLike {
27
+ const spy: NowSpyLike = jest.spyOn(Date, "now") as unknown as NowSpyLike;
28
+ spy.mockReturnValue(value);
29
+ return spy;
30
+ }
31
+
32
+ afterEach(() => {
33
+ jest.restoreAllMocks();
34
+ });
35
+
36
+ describe("InMemoryTTLCache TTL behavior", () => {
37
+ test("get returns the value within its TTL", () => {
38
+ const cache: InMemoryTTLCache<string> = new InMemoryTTLCache<string>(3);
39
+
40
+ cache.set("a", "value-a", 60_000);
41
+
42
+ expect(cache.get("a")).toBe("value-a");
43
+ expect(cache.has("a")).toBe(true);
44
+ expect(cache.size()).toBe(1);
45
+ });
46
+
47
+ test("get past the TTL misses AND physically deletes the entry", () => {
48
+ const t0: number = 1_000_000;
49
+ const nowSpy: NowSpyLike = spyNow(t0);
50
+
51
+ const cache: InMemoryTTLCache<string> = new InMemoryTTLCache<string>(3);
52
+ cache.set("a", "value-a", 1_000);
53
+ expect(cache.size()).toBe(1);
54
+
55
+ // Boundary: at exactly expiresAt the entry is still valid (strict >).
56
+ nowSpy.mockReturnValue(t0 + 1_000);
57
+ expect(cache.get("a")).toBe("value-a");
58
+ expect(cache.size()).toBe(1);
59
+
60
+ nowSpy.mockReturnValue(t0 + 1_001);
61
+ expect(cache.get("a")).toBeUndefined();
62
+ expect(cache.size()).toBe(0);
63
+ });
64
+
65
+ test("has() reports false for an expired entry and deletes it", () => {
66
+ const t0: number = 1_000_000;
67
+ const nowSpy: NowSpyLike = spyNow(t0);
68
+
69
+ const cache: InMemoryTTLCache<string> = new InMemoryTTLCache<string>(3);
70
+ cache.set("a", "value-a", 1_000);
71
+
72
+ nowSpy.mockReturnValue(t0 + 5_000);
73
+ expect(cache.has("a")).toBe(false);
74
+ expect(cache.size()).toBe(0);
75
+ });
76
+ });
77
+
78
+ describe("InMemoryTTLCache capacity eviction", () => {
79
+ test("insert beyond capacity evicts the oldest key", () => {
80
+ const cache: InMemoryTTLCache<string> = new InMemoryTTLCache<string>(3);
81
+
82
+ cache.set("a", "value-a", 60_000);
83
+ cache.set("b", "value-b", 60_000);
84
+ cache.set("c", "value-c", 60_000);
85
+ cache.set("d", "value-d", 60_000);
86
+
87
+ expect(cache.size()).toBe(3);
88
+ expect(cache.get("a")).toBeUndefined();
89
+ expect(cache.get("b")).toBe("value-b");
90
+ expect(cache.get("c")).toBe("value-c");
91
+ expect(cache.get("d")).toBe("value-d");
92
+ });
93
+
94
+ test("re-setting an existing key refreshes insertion order so it is not the eviction victim", () => {
95
+ const cache: InMemoryTTLCache<string> = new InMemoryTTLCache<string>(3);
96
+
97
+ cache.set("a", "value-a", 60_000);
98
+ cache.set("b", "value-b", 60_000);
99
+ cache.set("c", "value-c", 60_000);
100
+
101
+ // Overwrite at capacity: no eviction, "a" moves to newest position.
102
+ cache.set("a", "value-a2", 60_000);
103
+ expect(cache.size()).toBe(3);
104
+ expect(cache.get("b")).toBe("value-b");
105
+ expect(cache.get("c")).toBe("value-c");
106
+
107
+ // Next new key evicts "b" (now the oldest), not the refreshed "a".
108
+ cache.set("d", "value-d", 60_000);
109
+ expect(cache.size()).toBe(3);
110
+ expect(cache.get("a")).toBe("value-a2");
111
+ expect(cache.get("b")).toBeUndefined();
112
+ expect(cache.get("c")).toBe("value-c");
113
+ expect(cache.get("d")).toBe("value-d");
114
+ });
115
+ });
116
+
117
+ describe("InMemoryTTLCache basics", () => {
118
+ test("values are stored by reference", () => {
119
+ const cache: InMemoryTTLCache<Array<string>> = new InMemoryTTLCache<
120
+ Array<string>
121
+ >(3);
122
+
123
+ const value: Array<string> = [];
124
+ cache.set("a", value, 60_000);
125
+
126
+ expect(cache.get("a")).toBe(value);
127
+ });
128
+
129
+ test("delete removes an entry", () => {
130
+ const cache: InMemoryTTLCache<string> = new InMemoryTTLCache<string>(3);
131
+
132
+ cache.set("a", "value-a", 60_000);
133
+ cache.delete("a");
134
+
135
+ expect(cache.get("a")).toBeUndefined();
136
+ expect(cache.has("a")).toBe(false);
137
+ expect(cache.size()).toBe(0);
138
+ });
139
+
140
+ test("clear empties the cache", () => {
141
+ const cache: InMemoryTTLCache<string> = new InMemoryTTLCache<string>(3);
142
+
143
+ cache.set("a", "value-a", 60_000);
144
+ cache.set("b", "value-b", 60_000);
145
+ cache.clear();
146
+
147
+ expect(cache.size()).toBe(0);
148
+ expect(cache.get("a")).toBeUndefined();
149
+ expect(cache.get("b")).toBeUndefined();
150
+ });
151
+
152
+ test("has returns false for a key that was never set", () => {
153
+ const cache: InMemoryTTLCache<string> = new InMemoryTTLCache<string>(3);
154
+
155
+ expect(cache.has("missing")).toBe(false);
156
+ expect(cache.get("missing")).toBeUndefined();
157
+ });
158
+ });
@@ -20,16 +20,14 @@ import { AddDeviceRoleAndDeclaredLinkParent1787400000000 } from "../../../../Ser
20
20
  * either would silently restate every device and every link in every
21
21
  * project as something an operator had chosen.
22
22
  *
23
- * The second is ORDERING. This is the newest registered migration, so it
24
- * inherits the guard that used to live in
25
- * EpisodeMemberNotifyIndexesMigration.test.ts: its timestamp must sort
26
- * above every other. A migration generated with `generate-postgres-
27
- * migration` carries a WALL-CLOCK timestamp, and the recent migrations in
28
- * this repo use hand-picked round numbers that run ahead of it so the
29
- * generated file lands below several already-registered migrations unless
30
- * somebody renumbers it. That is exactly what happened to this one.
31
- * Whoever adds the next migration should move this test's ordering block
32
- * to theirs.
23
+ * The second is REGISTRATION: an import that never reaches the default
24
+ * export array leaves the migration unregistered, and it silently never
25
+ * runs.
26
+ *
27
+ * The ordering guard this file used to carry "this is the newest
28
+ * migration, so its timestamp must sort above every other" has moved on
29
+ * to AddNetworkDeviceReachabilityColumnsMigration.test.ts, which is now the
30
+ * newest. Whoever adds the next migration should move it again.
33
31
  */
34
32
 
35
33
  const MIGRATION_PATH: string = path.join(
@@ -39,15 +37,6 @@ const MIGRATION_PATH: string = path.join(
39
37
 
40
38
  const SOURCE: string = fs.readFileSync(MIGRATION_PATH, "utf8");
41
39
 
42
- interface MigrationClass {
43
- name: string;
44
- }
45
-
46
- function timestampOf(migrationClass: MigrationClass): number | null {
47
- const match: RegExpMatchArray | null = migrationClass.name.match(/(\d+)$/);
48
- return match ? Number(match[1]) : null;
49
- }
50
-
51
40
  describe("the columns it adds", () => {
52
41
  test("NetworkDevice gains deviceRole", () => {
53
42
  expect(SOURCE).toContain(
@@ -190,50 +179,10 @@ describe("the migration runs", () => {
190
179
  );
191
180
  });
192
181
 
193
- // The guard described in the header. Hand it to the next migration.
194
- test("its timestamp sorts after every other registered migration", () => {
195
- const ourTimestamp: number | null = timestampOf(
196
- AddDeviceRoleAndDeclaredLinkParent1787400000000,
197
- );
198
-
199
- expect(ourTimestamp).not.toBeNull();
200
-
201
- const otherTimestamps: Array<number> = [];
202
-
203
- for (const migrationClass of SchemaMigrations) {
204
- if (migrationClass === AddDeviceRoleAndDeclaredLinkParent1787400000000) {
205
- continue;
206
- }
207
-
208
- const timestamp: number | null = timestampOf(
209
- migrationClass as MigrationClass,
210
- );
211
-
212
- if (timestamp !== null) {
213
- otherTimestamps.push(timestamp);
214
- }
215
- }
216
-
217
- /*
218
- * Math.max() of an empty list is -Infinity, which every timestamp beats.
219
- * Prove the list was actually enumerated before leaning on the maximum.
220
- */
221
- expect(otherTimestamps.length).toBeGreaterThan(100);
222
-
223
- expect(ourTimestamp).toBeGreaterThan(Math.max(...otherTimestamps));
224
- });
225
-
226
- test("it is registered last, matching that timestamp", () => {
227
- expect(SchemaMigrations[SchemaMigrations.length - 1]).toBe(
228
- AddDeviceRoleAndDeclaredLinkParent1787400000000,
229
- );
230
- });
231
-
232
182
  /*
233
183
  * ...and the timestamp in the class name is the one on disk. A class
234
184
  * renamed without renaming its file (or two migrations landing on the
235
- * same timestamp) leaves the ordering above asserting something that is
236
- * not what actually runs.
185
+ * same timestamp) runs in an order nobody declared.
237
186
  */
238
187
  test("exactly one file on disk carries its timestamp, and it is this one", () => {
239
188
  const directory: string = path.dirname(MIGRATION_PATH);
@@ -1,4 +1,3 @@
1
- import APIKeyPermission from "../../../Models/DatabaseModels/ApiKeyPermission";
2
1
  import GlobalConfig from "../../../Models/DatabaseModels/GlobalConfig";
3
2
  import User from "../../../Models/DatabaseModels/User";
4
3
  import ProjectMiddleware from "../../../Server/Middleware/ProjectAuthorization";
@@ -37,6 +36,19 @@ import {
37
36
  test,
38
37
  } from "@jest/globals";
39
38
 
39
+ /*
40
+ * PasswordHash has a known, pre-existing TS5.9 compile failure under ts-jest
41
+ * (Buffer vs BinaryLike) that breaks every suite whose import graph reaches
42
+ * it. Nothing here touches password hashing; stub the module before the
43
+ * service import graph drags it into compilation.
44
+ */
45
+ jest.mock("../../../Server/Utils/PasswordHash", () => {
46
+ return {
47
+ __esModule: true,
48
+ default: class PasswordHashStub {},
49
+ };
50
+ });
51
+
40
52
  /*
41
53
  * ProjectAuthorization.isValidProjectIdAndApiKeyMiddleware is the single door
42
54
  * every API-key request in the product walks through, and it had no test at all.
@@ -67,9 +79,9 @@ describe("ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware", () => {
67
79
  ApiKeyService,
68
80
  "findApiKey",
69
81
  );
70
- const spyApiKeyPermissionFindBy: jest.SpyInstance = getJestSpyOn(
82
+ const spyFindApiKeyPermissions: jest.SpyInstance = getJestSpyOn(
71
83
  ApiKeyPermissionService,
72
- "findBy",
84
+ "findPermissionsByApiKeyId",
73
85
  );
74
86
  const spyGlobalConfigFindOneBy: jest.SpyInstance = getJestSpyOn(
75
87
  GlobalConfigService,
@@ -89,7 +101,7 @@ describe("ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware", () => {
89
101
  jest.clearAllMocks();
90
102
  next = getJestMockFunction();
91
103
  spyFindApiKey.mockResolvedValue(null);
92
- spyApiKeyPermissionFindBy.mockResolvedValue([]);
104
+ spyFindApiKeyPermissions.mockResolvedValue([]);
93
105
  spyGlobalConfigFindOneBy.mockResolvedValue(null);
94
106
  spyUserFindOneBy.mockResolvedValue(null);
95
107
  });
@@ -129,16 +141,17 @@ describe("ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware", () => {
129
141
  type MockResolvedApiKeyFunction = () => void;
130
142
 
131
143
  const mockResolvedApiKey: MockResolvedApiKeyFunction = (): void => {
132
- const apiKeyPermission: APIKeyPermission = new APIKeyPermission();
133
- apiKeyPermission.permission = Permission.ProjectMember;
134
- apiKeyPermission.labels = [];
135
- apiKeyPermission.isBlockPermission = false;
136
-
137
144
  spyFindApiKey.mockResolvedValue({
138
145
  id: apiKeyId,
139
146
  projectId: keyProjectId,
140
147
  });
141
- spyApiKeyPermissionFindBy.mockResolvedValue([apiKeyPermission]);
148
+ spyFindApiKeyPermissions.mockResolvedValue([
149
+ {
150
+ permission: Permission.ProjectMember,
151
+ labelIds: [],
152
+ isBlockPermission: false,
153
+ },
154
+ ]);
142
155
  };
143
156
 
144
157
  test("should call next with no argument and stamp API identity, tenant and permissions on the request", async () => {
@@ -211,11 +224,7 @@ describe("ProjectMiddleware.isValidProjectIdAndApiKeyMiddleware", () => {
211
224
 
212
225
  await runMiddleware(req);
213
226
 
214
- expect(spyApiKeyPermissionFindBy).toHaveBeenCalledWith(
215
- expect.objectContaining({
216
- query: { apiKeyId: apiKeyId },
217
- }),
218
- );
227
+ expect(spyFindApiKeyPermissions).toHaveBeenCalledWith(apiKeyId);
219
228
 
220
229
  const permissions: Array<UserPermission> =
221
230
  (req as OneUptimeRequest).userTenantAccessPermission?.[
@@ -50,6 +50,25 @@ jest.mock("../../../Server/Services/ProjectService");
50
50
  jest.mock("../../../Server/Services/TeamMemberService");
51
51
  jest.mock("../../../Types/HashedString");
52
52
  jest.mock("../../../Types/JSONFunctions");
53
+ /*
54
+ * PasswordHash carries a pre-existing TS5.9 diagnostic that fails any suite
55
+ * whose runtime require graph reaches it (DatabaseService, the base class of
56
+ * every concrete service, imports it). Nothing password-related is under
57
+ * test here, so the module is replaced WITH A FACTORY — an automock would
58
+ * still require (and type-check) the real file.
59
+ */
60
+ jest.mock("../../../Server/Utils/PasswordHash", () => {
61
+ return {
62
+ __esModule: true,
63
+ default: {
64
+ hash: jest.fn(),
65
+ verify: jest.fn(),
66
+ generateSalt: jest.fn(),
67
+ needsUpgrade: jest.fn(),
68
+ applyPepper: jest.fn(),
69
+ },
70
+ };
71
+ });
53
72
 
54
73
  type StringOrUndefined = string | undefined;
55
74
 
@@ -409,6 +428,66 @@ describe("UserMiddleware", () => {
409
428
  expect(spyGetUserGlobalAccessPermission).toHaveBeenCalledWith(userId);
410
429
  });
411
430
 
431
+ test("should not set global-permissions and global-permissions-hash in the response header, when the global-permissions-hash set in the request header equals the globalPermissionsHash computed from userGlobalAccessPermission", async () => {
432
+ const mockedRequest: Partial<ExpressRequest> = {
433
+ headers: { "global-permissions-hash": hashValue },
434
+ };
435
+
436
+ getJestSpyOn(ProjectMiddleware, "getProjectId").mockReturnValueOnce(null);
437
+ getJestSpyOn(JSONFunctions, "serialize").mockReturnValueOnce({});
438
+ const spyGetUserGlobalAccessPermission: jest.SpyInstance = getJestSpyOn(
439
+ AccessTokenService,
440
+ "getUserGlobalAccessPermission",
441
+ ).mockResolvedValueOnce(mockedGlobalAccessPermission);
442
+
443
+ await UserMiddleware.getUserMiddleware(
444
+ mockedRequest as ExpressRequest,
445
+ res,
446
+ next,
447
+ );
448
+
449
+ expect(res.set).not.toHaveBeenCalledWith(
450
+ "global-permissions",
451
+ expect.anything(),
452
+ );
453
+ expect(res.set).not.toHaveBeenCalledWith(
454
+ "global-permissions-hash",
455
+ expect.anything(),
456
+ );
457
+ expect(next).toHaveBeenCalled();
458
+ expect(spyGetUserGlobalAccessPermission).toHaveBeenCalledWith(userId);
459
+ });
460
+
461
+ test("should set global-permissions and global-permissions-hash in the response header, when the global-permissions-hash set in the request header does not equal the globalPermissionsHash computed from userGlobalAccessPermission", async () => {
462
+ const mockedRequest: Partial<ExpressRequest> = {
463
+ headers: { "global-permissions-hash": "different-hash" },
464
+ };
465
+
466
+ getJestSpyOn(ProjectMiddleware, "getProjectId").mockReturnValueOnce(null);
467
+ getJestSpyOn(JSONFunctions, "serialize").mockReturnValueOnce({});
468
+ const spyGetUserGlobalAccessPermission: jest.SpyInstance = getJestSpyOn(
469
+ AccessTokenService,
470
+ "getUserGlobalAccessPermission",
471
+ ).mockResolvedValueOnce(mockedGlobalAccessPermission);
472
+
473
+ await UserMiddleware.getUserMiddleware(
474
+ mockedRequest as ExpressRequest,
475
+ res,
476
+ next,
477
+ );
478
+
479
+ expect(res.set).toHaveBeenCalledWith(
480
+ "global-permissions",
481
+ JSON.stringify({}),
482
+ );
483
+ expect(res.set).toHaveBeenCalledWith(
484
+ "global-permissions-hash",
485
+ hashValue,
486
+ );
487
+ expect(next).toHaveBeenCalled();
488
+ expect(spyGetUserGlobalAccessPermission).toHaveBeenCalledWith(userId);
489
+ });
490
+
412
491
  test("should call Response.sendErrorResponse, when tenantId is passed in the header and getUserTenantAccessPermissionWithTenantId throws an exception", async () => {
413
492
  const spyGetUserTenantAccessPermissionWithTenantId: jest.SpyInstance =
414
493
  getJestSpyOn(