@oneuptime/common 12.0.11 → 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 (251) 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/Types/Workflow/Components/Conditions/IfElse.ts +9 -0
  27. package/Server/Types/Workflow/Components/JavaScript.ts +9 -0
  28. package/Server/Utils/APIKey/AccessPermission.ts +16 -40
  29. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +144 -0
  30. package/Server/Utils/LogRedaction.ts +576 -0
  31. package/Server/Utils/Logger.ts +123 -46
  32. package/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.ts +94 -23
  33. package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +12 -0
  34. package/Server/Utils/Monitor/MonitorResource.ts +29 -9
  35. package/Server/Utils/Monitor/NetworkInventoryUtil.ts +28 -7
  36. package/Server/Utils/SessionReplay/SessionReplayGateCacheStore.ts +56 -10
  37. package/Server/Utils/StartServer.ts +30 -9
  38. package/Server/Utils/StatusPageSearchEngineIndexing.ts +33 -0
  39. package/Server/Utils/Telemetry/ResourceEntityFilter.ts +441 -0
  40. package/Server/Utils/VM/VMRunner.ts +693 -874
  41. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +488 -46
  42. package/Tests/App/Dashboard/MonitorTypePicker.test.tsx +300 -0
  43. package/Tests/App/StatusPage/StatusPageIndexPageRobotsMeta.test.ts +130 -0
  44. package/Tests/Models/DatabaseModels/DatabaseBaseModelToJSONObjectCache.test.ts +180 -0
  45. package/Tests/Models/StatusPageEnableSearchEngineIndexing.test.ts +157 -0
  46. package/Tests/Server/API/AIChatCancelAndFeedback.test.ts +23 -1
  47. package/Tests/Server/API/AIChatRouteAuthorization.test.ts +785 -0
  48. package/Tests/Server/API/BaseAPIApiKeyAuth.test.ts +21 -8
  49. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +290 -0
  50. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +7 -1
  51. package/Tests/Server/API/StatusPageOverviewCache.test.ts +474 -0
  52. package/Tests/Server/API/StatusPageSeoSearchEngineIndexing.test.ts +189 -0
  53. package/Tests/Server/Infrastructure/InMemoryTTLCache.test.ts +158 -0
  54. package/Tests/Server/Infrastructure/Postgres/DeviceRoleAndDeclaredLinkParentMigration.test.ts +9 -60
  55. package/Tests/Server/Middleware/ProjectAuthorizationApiKeyMiddleware.test.ts +24 -15
  56. package/Tests/Server/Middleware/UserAuthorization.test.ts +79 -0
  57. package/Tests/Server/Services/AddNetworkDeviceReachabilityColumnsMigration.test.ts +332 -0
  58. package/Tests/Server/Services/ApiKeyPermissionService.test.ts +347 -0
  59. package/Tests/Server/Services/DatabaseServiceUpdateDebugLogging.test.ts +282 -0
  60. package/Tests/Server/Services/NetworkSiteService.test.ts +56 -2
  61. package/Tests/Server/Services/TelemetryResourceFacetFilters.test.ts +267 -0
  62. package/Tests/Server/Services/WorkspaceNotificationRuleTestRuleChannels.test.ts +702 -0
  63. package/Tests/Server/Types/Database/Permissions/AccessControlPermission.test.ts +165 -0
  64. package/Tests/Server/Types/Workflow/Components/CustomCodeScriptError.test.ts +121 -0
  65. package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +48 -36
  66. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +213 -0
  67. package/Tests/Server/Utils/LogRedaction.test.ts +415 -0
  68. package/Tests/Server/Utils/LoggerCredentialLeak.test.ts +245 -0
  69. package/Tests/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.test.ts +374 -4
  70. package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorJavascriptExpression.test.ts +92 -0
  71. package/Tests/Server/Utils/Monitor/MonitorResourceIngestedStepSelection.test.ts +455 -0
  72. package/Tests/Server/Utils/Monitor/NetworkDeviceReachabilityRoundTrip.test.ts +369 -0
  73. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +86 -8
  74. package/Tests/Server/Utils/PrivacyFilterUtil.test.ts +64 -0
  75. package/Tests/Server/Utils/SessionReplay/SessionReplayGateCacheStore.test.ts +207 -0
  76. package/Tests/Server/Utils/StartServerBodyVerify.test.ts +335 -0
  77. package/Tests/Server/Utils/StatusPageSearchEngineIndexing.test.ts +60 -0
  78. package/Tests/Server/Utils/Telemetry/ResourceEntityFilter.test.ts +435 -0
  79. package/Tests/Server/Utils/VM/VMRunnerIsolation.test.ts +385 -0
  80. package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +3 -0
  81. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelSend.test.ts +607 -62
  82. package/Tests/Server/Utils/Workspace/MicrosoftTeamsInstalledTeamIdSpace.test.ts +778 -0
  83. package/Tests/Server/Utils/Workspace/MicrosoftTeamsTeamInstalls.test.ts +44 -10
  84. package/Tests/Types/API/HostnameFromAuthority.test.ts +164 -0
  85. package/Tests/Types/Database/AccessControl/ColumnAccessControlCache.test.ts +344 -0
  86. package/Tests/Types/Database/DatabasePropertyFindOperator.test.ts +518 -0
  87. package/Tests/Types/Database/TableColumnMetadataCache.test.ts +286 -0
  88. package/Tests/Types/Monitor/MonitorStep.test.ts +100 -0
  89. package/Tests/Types/Monitor/MonitorTypeKeywords.test.ts +360 -0
  90. package/Tests/Types/StatusPage/SearchEngineIndexing.test.ts +86 -0
  91. package/Tests/Types/Telemetry/ResourceEntityFacet.test.ts +254 -0
  92. package/Tests/UI/Components/CardSelect.test.tsx +1021 -0
  93. package/Tests/UI/Components/MasterPage.test.tsx +36 -1
  94. package/Tests/Utils/Monitor/MonitorMetricType.test.ts +66 -1
  95. package/Tests/Utils/Monitor/NetworkTopologyUtil.test.ts +115 -6
  96. package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +610 -0
  97. package/Tests/Utils/NetworkSite/SiteStatusRollupUtil.test.ts +114 -45
  98. package/Types/API/Hostname.ts +79 -0
  99. package/Types/Database/AccessControl/ColumnAccessControl.ts +33 -11
  100. package/Types/Database/AccessControl/ColumnBillingAccessControl.ts +33 -11
  101. package/Types/Database/DatabaseProperty.ts +38 -0
  102. package/Types/Database/TableColumn.ts +34 -7
  103. package/Types/Events/Recurring.ts +16 -1
  104. package/Types/Monitor/CustomCodeMonitor/CustomCodeMonitorResponse.ts +14 -2
  105. package/Types/Monitor/MonitorStep.ts +13 -1
  106. package/Types/Monitor/MonitorType.ts +345 -40
  107. package/Types/Monitor/SSLMonitor/SslMonitorResponse.ts +43 -0
  108. package/Types/StatusPage/SearchEngineIndexing.ts +61 -0
  109. package/Types/Telemetry/ResourceEntityFacet.ts +211 -0
  110. package/UI/Components/CardSelect/CardSelect.tsx +714 -105
  111. package/UI/Components/EmptyState/EmptyState.tsx +8 -1
  112. package/UI/Components/Footer/Footer.tsx +32 -6
  113. package/UI/Components/Forms/Fields/FormField.tsx +3 -0
  114. package/UI/Components/Forms/Types/Field.ts +8 -0
  115. package/UI/Components/Loader/PageLoader.tsx +29 -8
  116. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +26 -4
  117. package/UI/Components/Markdown.tsx/LazyMarkdownViewer.tsx +17 -1
  118. package/UI/Components/MasterPage/MasterPage.tsx +24 -11
  119. package/Utils/Monitor/MonitorMetricType.ts +16 -1
  120. package/Utils/Monitor/NetworkTopologyUtil.ts +60 -8
  121. package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +262 -0
  122. package/Utils/NetworkSite/SiteStatusRollupUtil.ts +41 -24
  123. package/Utils/Telemetry/CrossSignalScope.ts +75 -4
  124. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +20 -4
  125. package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
  126. package/build/dist/Models/DatabaseModels/NetworkDevice.js +66 -0
  127. package/build/dist/Models/DatabaseModels/NetworkDevice.js.map +1 -1
  128. package/build/dist/Models/DatabaseModels/StatusPage.js +60 -0
  129. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  130. package/build/dist/Models/DatabaseModels/StatusPageOidc.js +2 -0
  131. package/build/dist/Models/DatabaseModels/StatusPageOidc.js.map +1 -1
  132. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  133. package/build/dist/Server/API/AIChatAPI.js +48 -35
  134. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  135. package/build/dist/Server/API/CommonAPI.js +59 -0
  136. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  137. package/build/dist/Server/API/MicrosoftTeamsAPI.js +20 -6
  138. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  139. package/build/dist/Server/API/SlackAPI.js +13 -10
  140. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  141. package/build/dist/Server/API/StatusPageAPI.js +745 -673
  142. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  143. package/build/dist/Server/API/TelemetryAPI.js +34 -3
  144. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  145. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js +23 -0
  146. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787500000000-AddEnableSearchEngineIndexingToStatusPage.js.map +1 -0
  147. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js +34 -0
  148. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1787600000000-AddNetworkDeviceReachabilityColumns.js.map +1 -0
  149. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  150. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  151. package/build/dist/Server/Middleware/UserAuthorization.js +6 -2
  152. package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
  153. package/build/dist/Server/Services/ApiKeyPermissionService.js +90 -1
  154. package/build/dist/Server/Services/ApiKeyPermissionService.js.map +1 -1
  155. package/build/dist/Server/Services/DatabaseService.js +15 -6
  156. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  157. package/build/dist/Server/Services/LogAggregationService.js +2 -0
  158. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  159. package/build/dist/Server/Services/LogService.js +16 -0
  160. package/build/dist/Server/Services/LogService.js.map +1 -1
  161. package/build/dist/Server/Services/NetworkSiteService.js +12 -0
  162. package/build/dist/Server/Services/NetworkSiteService.js.map +1 -1
  163. package/build/dist/Server/Services/SpanService.js +16 -0
  164. package/build/dist/Server/Services/SpanService.js.map +1 -1
  165. package/build/dist/Server/Services/TraceAggregationService.js +2 -0
  166. package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
  167. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +137 -164
  168. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  169. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js +9 -0
  170. package/build/dist/Server/Types/AnalyticsDatabase/ModelPermission.js.map +1 -1
  171. package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js +21 -13
  172. package/build/dist/Server/Types/Database/Permissions/AccessControlPermission.js.map +1 -1
  173. package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js +8 -0
  174. package/build/dist/Server/Types/Workflow/Components/Conditions/IfElse.js.map +1 -1
  175. package/build/dist/Server/Types/Workflow/Components/JavaScript.js +8 -0
  176. package/build/dist/Server/Types/Workflow/Components/JavaScript.js.map +1 -1
  177. package/build/dist/Server/Utils/APIKey/AccessPermission.js +9 -32
  178. package/build/dist/Server/Utils/APIKey/AccessPermission.js.map +1 -1
  179. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +87 -0
  180. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  181. package/build/dist/Server/Utils/LogRedaction.js +449 -0
  182. package/build/dist/Server/Utils/LogRedaction.js.map +1 -0
  183. package/build/dist/Server/Utils/Logger.js +98 -45
  184. package/build/dist/Server/Utils/Logger.js.map +1 -1
  185. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js +65 -13
  186. package/build/dist/Server/Utils/Monitor/Criteria/SSLMonitorCriteria.js.map +1 -1
  187. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +12 -1
  188. package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
  189. package/build/dist/Server/Utils/Monitor/MonitorResource.js +20 -3
  190. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  191. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js +27 -7
  192. package/build/dist/Server/Utils/Monitor/NetworkInventoryUtil.js.map +1 -1
  193. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js +29 -10
  194. package/build/dist/Server/Utils/SessionReplay/SessionReplayGateCacheStore.js.map +1 -1
  195. package/build/dist/Server/Utils/StartServer.js +10 -7
  196. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  197. package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js +24 -0
  198. package/build/dist/Server/Utils/StatusPageSearchEngineIndexing.js.map +1 -0
  199. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js +311 -0
  200. package/build/dist/Server/Utils/Telemetry/ResourceEntityFilter.js.map +1 -0
  201. package/build/dist/Server/Utils/VM/VMRunner.js +402 -525
  202. package/build/dist/Server/Utils/VM/VMRunner.js.map +1 -1
  203. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +366 -37
  204. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  205. package/build/dist/Types/API/Hostname.js +63 -0
  206. package/build/dist/Types/API/Hostname.js.map +1 -1
  207. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js +20 -6
  208. package/build/dist/Types/Database/AccessControl/ColumnAccessControl.js.map +1 -1
  209. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js +20 -6
  210. package/build/dist/Types/Database/AccessControl/ColumnBillingAccessControl.js.map +1 -1
  211. package/build/dist/Types/Database/DatabaseProperty.js +38 -0
  212. package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
  213. package/build/dist/Types/Database/TableColumn.js +24 -6
  214. package/build/dist/Types/Database/TableColumn.js.map +1 -1
  215. package/build/dist/Types/Events/Recurring.js +13 -1
  216. package/build/dist/Types/Events/Recurring.js.map +1 -1
  217. package/build/dist/Types/Monitor/MonitorStep.js +9 -1
  218. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  219. package/build/dist/Types/Monitor/MonitorType.js +330 -31
  220. package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
  221. package/build/dist/Types/StatusPage/SearchEngineIndexing.js +55 -0
  222. package/build/dist/Types/StatusPage/SearchEngineIndexing.js.map +1 -0
  223. package/build/dist/Types/Telemetry/ResourceEntityFacet.js +156 -0
  224. package/build/dist/Types/Telemetry/ResourceEntityFacet.js.map +1 -0
  225. package/build/dist/UI/Components/CardSelect/CardSelect.js +360 -40
  226. package/build/dist/UI/Components/CardSelect/CardSelect.js.map +1 -1
  227. package/build/dist/UI/Components/EmptyState/EmptyState.js +1 -1
  228. package/build/dist/UI/Components/EmptyState/EmptyState.js.map +1 -1
  229. package/build/dist/UI/Components/Footer/Footer.js +7 -4
  230. package/build/dist/UI/Components/Footer/Footer.js.map +1 -1
  231. package/build/dist/UI/Components/Forms/Fields/FormField.js +1 -1
  232. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  233. package/build/dist/UI/Components/Loader/PageLoader.js +15 -5
  234. package/build/dist/UI/Components/Loader/PageLoader.js.map +1 -1
  235. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +16 -3
  236. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  237. package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js +13 -1
  238. package/build/dist/UI/Components/Markdown.tsx/LazyMarkdownViewer.js.map +1 -1
  239. package/build/dist/UI/Components/MasterPage/MasterPage.js +10 -2
  240. package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
  241. package/build/dist/Utils/Monitor/MonitorMetricType.js +13 -1
  242. package/build/dist/Utils/Monitor/MonitorMetricType.js.map +1 -1
  243. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js +34 -7
  244. package/build/dist/Utils/Monitor/NetworkTopologyUtil.js.map +1 -1
  245. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +177 -0
  246. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -0
  247. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js +23 -27
  248. package/build/dist/Utils/NetworkSite/SiteStatusRollupUtil.js.map +1 -1
  249. package/build/dist/Utils/Telemetry/CrossSignalScope.js +44 -4
  250. package/build/dist/Utils/Telemetry/CrossSignalScope.js.map +1 -1
  251. package/package.json +1 -1
@@ -3,6 +3,9 @@ import AccessControlPermission from "../../../../../Server/Types/Database/Permis
3
3
  import QueryUtil from "../../../../../Server/Types/Database/QueryUtil";
4
4
  import Monitor from "../../../../../Models/DatabaseModels/Monitor";
5
5
  import DatabaseCommonInteractionProps from "../../../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
6
+ import DatabaseCommonInteractionPropsUtil, {
7
+ PermissionType,
8
+ } from "../../../../../Types/BaseDatabase/DatabaseCommonInteractionPropsUtil";
6
9
  import ObjectID from "../../../../../Types/ObjectID";
7
10
  import Permission, {
8
11
  UserTenantAccessPermission,
@@ -187,3 +190,165 @@ describe("AccessControlPermission.addAccessControlIdsToQuery", () => {
187
190
  expect(result._id).toBeUndefined();
188
191
  });
189
192
  });
193
+
194
+ describe("AccessControlPermission.getAccessControlIdsForQuery", () => {
195
+ const projectId: ObjectID = ObjectID.generate();
196
+ const userId: ObjectID = ObjectID.generate();
197
+ const permittedLabelA: ObjectID = ObjectID.generate();
198
+ const permittedLabelB: ObjectID = ObjectID.generate();
199
+
200
+ function makeLabelRestrictedProps(): DatabaseCommonInteractionProps {
201
+ // A user whose ONLY read grant on Monitor is label-restricted.
202
+ const tenantPermission: UserTenantAccessPermission = {
203
+ projectId,
204
+ _type: "UserTenantAccessPermission",
205
+ permissions: [
206
+ {
207
+ _type: "UserPermission",
208
+ permission: Permission.ReadProjectMonitor,
209
+ labelIds: [permittedLabelA, permittedLabelB],
210
+ isBlockPermission: false,
211
+ },
212
+ ],
213
+ };
214
+
215
+ return {
216
+ userId,
217
+ tenantId: projectId,
218
+ userTenantAccessPermission: {
219
+ [projectId.toString()]: tenantPermission,
220
+ },
221
+ };
222
+ }
223
+
224
+ function toStrings(ids: Array<ObjectID>): Array<string> {
225
+ return ids.map((id: ObjectID) => {
226
+ return id.toString();
227
+ });
228
+ }
229
+
230
+ afterEach(() => {
231
+ jest.restoreAllMocks();
232
+ });
233
+
234
+ it("returns the permitted label ids deduped for a multi-column select, same as a query-only call", () => {
235
+ const withSelect: Array<ObjectID> =
236
+ AccessControlPermission.getAccessControlIdsForQuery(
237
+ Monitor,
238
+ { projectId } as any,
239
+ { _id: true, createdAt: true, name: true } as any,
240
+ makeLabelRestrictedProps(),
241
+ DatabaseRequestType.Read,
242
+ );
243
+
244
+ const queryOnly: Array<ObjectID> =
245
+ AccessControlPermission.getAccessControlIdsForQuery(
246
+ Monitor,
247
+ { projectId } as any,
248
+ null,
249
+ makeLabelRestrictedProps(),
250
+ DatabaseRequestType.Read,
251
+ );
252
+
253
+ expect(toStrings(withSelect)).toEqual([
254
+ permittedLabelA.toString(),
255
+ permittedLabelB.toString(),
256
+ ]);
257
+ expect(toStrings(withSelect)).toEqual(toStrings(queryOnly));
258
+ });
259
+
260
+ it("returns an empty array for users with an unrestricted grant regardless of column count", () => {
261
+ const tenantPermission: UserTenantAccessPermission = {
262
+ projectId,
263
+ _type: "UserTenantAccessPermission",
264
+ permissions: [
265
+ {
266
+ _type: "UserPermission",
267
+ permission: Permission.ProjectMember,
268
+ labelIds: [],
269
+ isBlockPermission: false,
270
+ },
271
+ ],
272
+ };
273
+
274
+ const result: Array<ObjectID> =
275
+ AccessControlPermission.getAccessControlIdsForQuery(
276
+ Monitor,
277
+ { projectId } as any,
278
+ { _id: true, createdAt: true, updatedAt: true, name: true } as any,
279
+ {
280
+ userId,
281
+ tenantId: projectId,
282
+ userTenantAccessPermission: {
283
+ [projectId.toString()]: tenantPermission,
284
+ },
285
+ },
286
+ DatabaseRequestType.Read,
287
+ );
288
+
289
+ expect(result).toEqual([]);
290
+ });
291
+
292
+ it("computes user permissions exactly once regardless of column count", () => {
293
+ const getUserPermissionsSpy: jest.SpyInstance = jest.spyOn(
294
+ DatabaseCommonInteractionPropsUtil,
295
+ "getUserPermissions",
296
+ );
297
+
298
+ const props: DatabaseCommonInteractionProps = makeLabelRestrictedProps();
299
+
300
+ AccessControlPermission.getAccessControlIdsForQuery(
301
+ Monitor,
302
+ { projectId } as any,
303
+ { _id: true, createdAt: true, updatedAt: true, name: true } as any,
304
+ props,
305
+ DatabaseRequestType.Read,
306
+ );
307
+
308
+ /*
309
+ * Perf regression guard: the old code rebuilt the full user-permission
310
+ * set once per access-controlled column plus once for the model-level
311
+ * check. The context must be built exactly once per query.
312
+ */
313
+ expect(getUserPermissionsSpy).toHaveBeenCalledTimes(1);
314
+ expect(getUserPermissionsSpy).toHaveBeenCalledWith(
315
+ props,
316
+ PermissionType.Allow,
317
+ );
318
+ });
319
+
320
+ it("still applies the idempotent Public push to props exactly once", () => {
321
+ const props: DatabaseCommonInteractionProps = makeLabelRestrictedProps();
322
+
323
+ AccessControlPermission.getAccessControlIdsForQuery(
324
+ Monitor,
325
+ { projectId } as any,
326
+ { _id: true, createdAt: true, name: true } as any,
327
+ props,
328
+ DatabaseRequestType.Read,
329
+ );
330
+
331
+ const globalPermissions: Array<Permission> =
332
+ props.userGlobalAccessPermission?.globalPermissions || [];
333
+
334
+ expect(
335
+ globalPermissions.filter((permission: Permission) => {
336
+ return permission === Permission.Public;
337
+ }),
338
+ ).toHaveLength(1);
339
+ });
340
+
341
+ it("getAccessControlIdsForModel with three args still returns the model-level permitted ids", () => {
342
+ const result: Array<ObjectID> =
343
+ AccessControlPermission.getAccessControlIdsForModel(
344
+ Monitor,
345
+ makeLabelRestrictedProps(),
346
+ DatabaseRequestType.Read,
347
+ );
348
+
349
+ expect(toStrings(result)).toEqual([
350
+ permittedLabelA.toString(),
351
+ permittedLabelB.toString(),
352
+ ]);
353
+ });
354
+ });
@@ -0,0 +1,121 @@
1
+ import JavaScriptCode from "../../../../../Server/Types/Workflow/Components/JavaScript";
2
+ import IfElse from "../../../../../Server/Types/Workflow/Components/Conditions/IfElse";
3
+ import {
4
+ RunOptions,
5
+ RunReturnType,
6
+ } from "../../../../../Server/Types/Workflow/ComponentCode";
7
+ import VMUtil from "../../../../../Server/Utils/VM/VMAPI";
8
+ import ReturnResult from "../../../../../Types/IsolatedVM/ReturnResult";
9
+ import Exception from "../../../../../Types/Exception/Exception";
10
+ import ObjectID from "../../../../../Types/ObjectID";
11
+ import { describe, expect, test, afterEach } from "@jest/globals";
12
+
13
+ interface OptionsFixture {
14
+ options: RunOptions;
15
+ log: jest.Mock;
16
+ onError: jest.Mock;
17
+ }
18
+
19
+ function makeOptions(): OptionsFixture {
20
+ const log: jest.Mock = jest.fn();
21
+ const onError: jest.Mock = jest.fn((exception: Exception): Exception => {
22
+ return exception;
23
+ });
24
+
25
+ return {
26
+ log,
27
+ onError,
28
+ options: {
29
+ log: log as RunOptions["log"],
30
+ workflowLogId: ObjectID.generate(),
31
+ workflowId: ObjectID.generate(),
32
+ projectId: ObjectID.generate(),
33
+ onError: onError as RunOptions["onError"],
34
+ executeWorkflow: async (): Promise<void> => {},
35
+ } as RunOptions,
36
+ };
37
+ }
38
+
39
+ afterEach(() => {
40
+ jest.restoreAllMocks();
41
+ });
42
+
43
+ describe("Custom JavaScript component scriptError routing", () => {
44
+ test("routes to the error port when the sandbox resolves with scriptError", async () => {
45
+ const sandboxResult: ReturnResult = {
46
+ returnValue: undefined,
47
+ logMessages: ["before failure"],
48
+ capturedMetrics: [],
49
+ scriptError: new Error("script exploded"),
50
+ };
51
+ jest.spyOn(VMUtil, "runCodeInSandbox").mockResolvedValue(sandboxResult);
52
+
53
+ const { options, log }: OptionsFixture = makeOptions();
54
+ const result: RunReturnType = await new JavaScriptCode().run(
55
+ { code: "throw new Error('script exploded');" },
56
+ options,
57
+ );
58
+
59
+ expect(result.executePort?.id).toBe("error");
60
+ expect(log).toHaveBeenCalledWith("before failure");
61
+ expect(log).toHaveBeenCalledWith("Error running script");
62
+ expect(log).toHaveBeenCalledWith("script exploded");
63
+ });
64
+
65
+ test("routes to the success port with the return value on success", async () => {
66
+ const sandboxResult: ReturnResult = {
67
+ returnValue: { answer: 42 },
68
+ logMessages: [],
69
+ capturedMetrics: [],
70
+ };
71
+ jest.spyOn(VMUtil, "runCodeInSandbox").mockResolvedValue(sandboxResult);
72
+
73
+ const { options }: OptionsFixture = makeOptions();
74
+ const result: RunReturnType = await new JavaScriptCode().run(
75
+ { code: "return { answer: 42 };" },
76
+ options,
77
+ );
78
+
79
+ expect(result.executePort?.id).toBe("success");
80
+ expect(result.returnValues).toEqual({ returnValue: { answer: 42 } });
81
+ });
82
+ });
83
+
84
+ describe("If/Else component scriptError handling", () => {
85
+ test("fails the run instead of silently taking a branch when the expression errors", async () => {
86
+ const sandboxResult: ReturnResult = {
87
+ returnValue: undefined,
88
+ logMessages: [],
89
+ capturedMetrics: [],
90
+ scriptError: new Error("expression exploded"),
91
+ };
92
+ jest.spyOn(VMUtil, "runCodeInSandbox").mockResolvedValue(sandboxResult);
93
+
94
+ const { options, onError }: OptionsFixture = makeOptions();
95
+
96
+ await expect(
97
+ new IfElse().run(
98
+ { "input-1": "a", "input-2": "b", operator: "==" },
99
+ options,
100
+ ),
101
+ ).rejects.toThrow("expression exploded");
102
+ expect(onError).toHaveBeenCalled();
103
+ });
104
+
105
+ test("takes the Yes branch when the expression evaluates truthy", async () => {
106
+ const sandboxResult: ReturnResult = {
107
+ returnValue: true,
108
+ logMessages: [],
109
+ capturedMetrics: [],
110
+ };
111
+ jest.spyOn(VMUtil, "runCodeInSandbox").mockResolvedValue(sandboxResult);
112
+
113
+ const { options }: OptionsFixture = makeOptions();
114
+ const result: RunReturnType = await new IfElse().run(
115
+ { "input-1": "a", "input-2": "a", operator: "==" },
116
+ options,
117
+ );
118
+
119
+ expect(result.executePort?.id).toBe("yes");
120
+ });
121
+ });
@@ -6,10 +6,24 @@ import {
6
6
  it,
7
7
  jest,
8
8
  } from "@jest/globals";
9
+
10
+ /*
11
+ * PasswordHash has a known, pre-existing TS5.9 compile failure under ts-jest
12
+ * (Buffer vs BinaryLike) that breaks every suite whose import graph reaches
13
+ * it. Nothing here touches password hashing; stub the module before the
14
+ * service import graph drags it into compilation.
15
+ */
16
+ jest.mock("../../../../Server/Utils/PasswordHash", () => {
17
+ return {
18
+ __esModule: true,
19
+ default: class PasswordHashStub {},
20
+ };
21
+ });
22
+
9
23
  import APIKeyAccessPermission from "../../../../Server/Utils/APIKey/AccessPermission";
10
- import ApiKeyPermissionService from "../../../../Server/Services/ApiKeyPermissionService";
11
- import APIKeyPermission from "../../../../Models/DatabaseModels/ApiKeyPermission";
12
- import Label from "../../../../Models/DatabaseModels/Label";
24
+ import ApiKeyPermissionService, {
25
+ ApiKeyPermissionRow,
26
+ } from "../../../../Server/Services/ApiKeyPermissionService";
13
27
  import ObjectID from "../../../../Types/ObjectID";
14
28
  import Permission, {
15
29
  UserGlobalAccessPermission,
@@ -27,10 +41,11 @@ import { getJestSpyOn } from "../../../Spy";
27
41
  * key full control of the project; one that dropped it from the master path
28
42
  * would break trusted internal automation. Both directions are pinned below.
29
43
  *
30
- * getApiTenantAccessPermission also reshapes stored permission rows into the
31
- * runtime UserPermission shape (labels -> labelIds, block flag preserved). That
32
- * mapping is where a dropped label would quietly widen a scoped permission, so
33
- * it is tested against a spied service rather than a live database.
44
+ * getApiTenantAccessPermission also reshapes the (cached) permission rows into
45
+ * the runtime UserPermission shape (labelIds carried over, block flag
46
+ * preserved). That mapping is where a dropped label would quietly widen a
47
+ * scoped permission, so it is tested against a spied service rather than a
48
+ * live database.
34
49
  */
35
50
 
36
51
  const projectId: ObjectID = ObjectID.generate();
@@ -118,9 +133,9 @@ describe("APIKeyAccessPermission.getMasterApiTenantAccessPermission", () => {
118
133
  });
119
134
 
120
135
  describe("APIKeyAccessPermission.getApiTenantAccessPermission", () => {
121
- const spyFindBy: jest.SpyInstance = getJestSpyOn(
136
+ const spyFindPermissions: jest.SpyInstance = getJestSpyOn(
122
137
  ApiKeyPermissionService,
123
- "findBy",
138
+ "findPermissionsByApiKeyId",
124
139
  );
125
140
 
126
141
  beforeEach(() => {
@@ -135,40 +150,33 @@ describe("APIKeyAccessPermission.getApiTenantAccessPermission", () => {
135
150
  permission: Permission;
136
151
  labelIds?: Array<ObjectID>;
137
152
  isBlockPermission?: boolean;
138
- }) => APIKeyPermission = (data: {
153
+ }) => ApiKeyPermissionRow = (data: {
139
154
  permission: Permission;
140
155
  labelIds?: Array<ObjectID>;
141
156
  isBlockPermission?: boolean;
142
- }): APIKeyPermission => {
143
- const row: APIKeyPermission = new APIKeyPermission();
144
- row.permission = data.permission;
145
- if (data.labelIds) {
146
- row.labels = data.labelIds.map((id: ObjectID) => {
147
- const label: Label = new Label();
148
- label.id = id;
149
- return label;
150
- });
151
- }
152
- row.isBlockPermission = data.isBlockPermission ?? false;
153
- return row;
157
+ }): ApiKeyPermissionRow => {
158
+ return {
159
+ permission: data.permission,
160
+ labelIds: data.labelIds ?? [],
161
+ isBlockPermission: data.isBlockPermission ?? false,
162
+ };
154
163
  };
155
164
 
156
165
  it("queries the permissions for exactly this api key", async () => {
157
- spyFindBy.mockResolvedValue([] as never);
166
+ spyFindPermissions.mockResolvedValue([] as never);
158
167
 
159
168
  await APIKeyAccessPermission.getApiTenantAccessPermission(
160
169
  projectId,
161
170
  apiKeyId,
162
171
  );
163
172
 
164
- expect(spyFindBy).toHaveBeenCalledTimes(1);
165
- const arg: { query: { apiKeyId: ObjectID } } = spyFindBy.mock
166
- .calls[0]![0] as { query: { apiKeyId: ObjectID } };
167
- expect(arg.query.apiKeyId.toString()).toBe(apiKeyId.toString());
173
+ expect(spyFindPermissions).toHaveBeenCalledTimes(1);
174
+ const arg: ObjectID = spyFindPermissions.mock.calls[0]![0] as ObjectID;
175
+ expect(arg.toString()).toBe(apiKeyId.toString());
168
176
  });
169
177
 
170
178
  it("returns just the default baseline when the key has no permissions", async () => {
171
- spyFindBy.mockResolvedValue([] as never);
179
+ spyFindPermissions.mockResolvedValue([] as never);
172
180
 
173
181
  const perm: UserTenantAccessPermission =
174
182
  await APIKeyAccessPermission.getApiTenantAccessPermission(
@@ -187,7 +195,7 @@ describe("APIKeyAccessPermission.getApiTenantAccessPermission", () => {
187
195
 
188
196
  it("maps each stored permission row onto the tenant permission", async () => {
189
197
  const labelId: ObjectID = ObjectID.generate();
190
- spyFindBy.mockResolvedValue([
198
+ spyFindPermissions.mockResolvedValue([
191
199
  makeRow({
192
200
  permission: Permission.CreateProjectIncident,
193
201
  labelIds: [labelId],
@@ -219,7 +227,7 @@ describe("APIKeyAccessPermission.getApiTenantAccessPermission", () => {
219
227
  });
220
228
 
221
229
  it("preserves the block flag so a deny row stays a deny row", async () => {
222
- spyFindBy.mockResolvedValue([
230
+ spyFindPermissions.mockResolvedValue([
223
231
  makeRow({
224
232
  permission: Permission.CreateProjectIncident,
225
233
  labelIds: [],
@@ -243,11 +251,16 @@ describe("APIKeyAccessPermission.getApiTenantAccessPermission", () => {
243
251
  });
244
252
 
245
253
  it("treats a row with no labels as an empty (unscoped) label set", async () => {
246
- const row: APIKeyPermission = new APIKeyPermission();
247
- row.permission = Permission.CreateProjectIncident;
248
- // labels intentionally left undefined.
249
- row.isBlockPermission = false;
250
- spyFindBy.mockResolvedValue([row] as never);
254
+ /*
255
+ * The service DTO normalizes a stored row's undefined labels to [] (pinned
256
+ * in ApiKeyPermissionService.test.ts); here the mapping must keep that
257
+ * empty set an array rather than dropping it to undefined.
258
+ */
259
+ spyFindPermissions.mockResolvedValue([
260
+ makeRow({
261
+ permission: Permission.CreateProjectIncident,
262
+ }),
263
+ ] as never);
251
264
 
252
265
  const perm: UserTenantAccessPermission =
253
266
  await APIKeyAccessPermission.getApiTenantAccessPermission(
@@ -261,7 +274,6 @@ describe("APIKeyAccessPermission.getApiTenantAccessPermission", () => {
261
274
  },
262
275
  );
263
276
  expect(mapped).toBeDefined();
264
- // No crash on undefined labels, and it becomes [] rather than undefined.
265
277
  expect(mapped!.labelIds).toEqual([]);
266
278
  });
267
279
  });
@@ -956,6 +956,219 @@ describe("StatementGenerator", () => {
956
956
  });
957
957
  });
958
958
 
959
+ /*
960
+ * resourceEntityScopes: the compiled form of a resource-facet selection
961
+ * (a Kubernetes cluster, a host, ...). Unlike entityScope it carries an
962
+ * `entityIds` branch, because the same resource can be a row's PRIMARY
963
+ * entity (agent-ingested telemetry) or merely one of its memberships
964
+ * (OTLP telemetry primary-keyed on its Service). Matching only
965
+ * primaryEntityId is what made a cluster filter return zero rows for
966
+ * collector-ingested logs.
967
+ */
968
+ describe("resourceEntityScopes synthetic key", () => {
969
+ class ResourceScopeModel extends AnalyticsBaseModel {
970
+ public constructor() {
971
+ super({
972
+ tableName: "<resource-scope-table>",
973
+ singularName: "<singular>",
974
+ pluralName: "<plural>",
975
+ tableColumns: [
976
+ new AnalyticsTableColumn({
977
+ key: "_id",
978
+ title: "<title>",
979
+ description: "<description>",
980
+ required: true,
981
+ type: TableColumnType.ObjectID,
982
+ }),
983
+ new AnalyticsTableColumn({
984
+ key: "primaryEntityId",
985
+ title: "<title>",
986
+ description: "<description>",
987
+ required: true,
988
+ type: TableColumnType.ObjectID,
989
+ }),
990
+ new AnalyticsTableColumn({
991
+ key: "entityKeys",
992
+ title: "<title>",
993
+ description: "<description>",
994
+ required: true,
995
+ defaultValue: [],
996
+ type: TableColumnType.ArrayText,
997
+ }),
998
+ new AnalyticsTableColumn({
999
+ key: "attributes",
1000
+ title: "<title>",
1001
+ description: "<description>",
1002
+ required: true,
1003
+ defaultValue: {},
1004
+ type: TableColumnType.MapStringString,
1005
+ }),
1006
+ ],
1007
+ crudApiPath: new Route("route"),
1008
+ primaryKeys: ["_id"],
1009
+ sortKeys: ["_id"],
1010
+ partitionKey: "_id",
1011
+ tableEngine: AnalyticsTableEngine.MergeTree,
1012
+ });
1013
+ }
1014
+ }
1015
+
1016
+ const CLUSTER_ID: string = "8c0f2f1e-2e4f-4a8c-9a1a-2f5b6c7d8e9f";
1017
+ const HOST_ID: string = "5f4e3d2c-1b0a-4998-8776-655443322110";
1018
+
1019
+ let resourceGenerator: StatementGenerator<ResourceScopeModel>;
1020
+ beforeEach(() => {
1021
+ resourceGenerator = new StatementGenerator<ResourceScopeModel>({
1022
+ modelType: ResourceScopeModel,
1023
+ database: ClickhouseAppInstance,
1024
+ });
1025
+ });
1026
+
1027
+ test("ORs the three membership branches inside one scope", () => {
1028
+ const statement: Statement = resourceGenerator.toWhereStatement({
1029
+ resourceEntityScopes: [
1030
+ {
1031
+ entityIds: [CLUSTER_ID],
1032
+ entityKeys: ["210dac24142f1baa"],
1033
+ attributeKey: "resource.k8s.cluster.name",
1034
+ attributeValues: ["prod-eu"],
1035
+ },
1036
+ ],
1037
+ } as any);
1038
+
1039
+ expect(statement.query).toBe(
1040
+ "AND ({p0:Identifier} IN {p1:Array(String)} OR hasAny({p2:Identifier}, {p3:Array(String)}) OR {p4:Identifier}[{p5:String}] IN {p6:Array(String)})",
1041
+ );
1042
+ expect(statement.query_params).toStrictEqual({
1043
+ p0: "primaryEntityId",
1044
+ p1: [CLUSTER_ID],
1045
+ p2: "entityKeys",
1046
+ p3: ["210dac24142f1baa"],
1047
+ p4: "attributes",
1048
+ p5: "resource.k8s.cluster.name",
1049
+ p6: ["prod-eu"],
1050
+ });
1051
+ });
1052
+
1053
+ test("ANDs separate scopes so two facets intersect", () => {
1054
+ const statement: Statement = resourceGenerator.toWhereStatement({
1055
+ resourceEntityScopes: [
1056
+ { entityIds: [CLUSTER_ID], entityKeys: ["210dac24142f1baa"] },
1057
+ { entityIds: [HOST_ID], entityKeys: ["9f8e7d6c5b4a3928"] },
1058
+ ],
1059
+ } as any);
1060
+
1061
+ expect(statement.query).toBe(
1062
+ "AND ({p0:Identifier} IN {p1:Array(String)} OR hasAny({p2:Identifier}, {p3:Array(String)})) " +
1063
+ "AND ({p4:Identifier} IN {p5:Array(String)} OR hasAny({p6:Identifier}, {p7:Array(String)}))",
1064
+ );
1065
+ });
1066
+
1067
+ test("composes with a Services predicate — cluster AND service", () => {
1068
+ const statement: Statement = resourceGenerator.toWhereStatement({
1069
+ primaryEntityId: "<service-id>",
1070
+ resourceEntityScopes: [
1071
+ { entityIds: [CLUSTER_ID], entityKeys: ["210dac24142f1baa"] },
1072
+ ],
1073
+ } as any);
1074
+
1075
+ /*
1076
+ * The regression the issue reports: these two used to be coalesced
1077
+ * into ONE primaryEntityId IN (...) list, so adding a service made
1078
+ * results reappear while silently dropping the cluster.
1079
+ */
1080
+ expect(statement.query).toBe(
1081
+ "AND {p0:Identifier} = {p1:String} " +
1082
+ "AND ({p2:Identifier} IN {p3:Array(String)} OR hasAny({p4:Identifier}, {p5:Array(String)}))",
1083
+ );
1084
+ });
1085
+
1086
+ test("a scope that resolved to ids only still narrows by primaryEntityId", () => {
1087
+ const statement: Statement = resourceGenerator.toWhereStatement({
1088
+ resourceEntityScopes: [{ entityIds: [CLUSTER_ID], entityKeys: [] }],
1089
+ } as any);
1090
+
1091
+ expect(statement.query).toBe(
1092
+ "AND ({p0:Identifier} IN {p1:Array(String)})",
1093
+ );
1094
+ });
1095
+
1096
+ test("an empty scope emits no predicate rather than an empty IN", () => {
1097
+ const statement: Statement = resourceGenerator.toWhereStatement({
1098
+ resourceEntityScopes: [{ entityIds: [], entityKeys: [] }],
1099
+ } as any);
1100
+
1101
+ expect(statement.query).toBe("");
1102
+ expect(statement.query_params).toStrictEqual({});
1103
+ });
1104
+
1105
+ test("an empty scope does not break separators for later predicates", () => {
1106
+ const statement: Statement = resourceGenerator.toWhereStatement({
1107
+ resourceEntityScopes: [{ entityIds: [], entityKeys: [] }],
1108
+ _id: "<value>",
1109
+ } as any);
1110
+
1111
+ expect(statement.query).toBe("AND {p0:Identifier} = {p1:String}");
1112
+ });
1113
+
1114
+ test("a non-array value carries no predicate", () => {
1115
+ const statement: Statement = resourceGenerator.toWhereStatement({
1116
+ resourceEntityScopes: { entityIds: [CLUSTER_ID] },
1117
+ } as any);
1118
+
1119
+ expect(statement.query).toBe("");
1120
+ });
1121
+
1122
+ test("blank ids and keys are filtered out of their branches", () => {
1123
+ const statement: Statement = resourceGenerator.toWhereStatement({
1124
+ resourceEntityScopes: [
1125
+ { entityIds: ["", CLUSTER_ID], entityKeys: [""] },
1126
+ ],
1127
+ } as any);
1128
+
1129
+ expect(statement.query).toBe(
1130
+ "AND ({p0:Identifier} IN {p1:Array(String)})",
1131
+ );
1132
+ expect(statement.query_params).toStrictEqual({
1133
+ p0: "primaryEntityId",
1134
+ p1: [CLUSTER_ID],
1135
+ });
1136
+ });
1137
+
1138
+ test("drops the branches whose backing column the model lacks", () => {
1139
+ // TestModel (outer generator) has neither primaryEntityId nor entityKeys.
1140
+ const statement: Statement = generator.toWhereStatement({
1141
+ resourceEntityScopes: [
1142
+ { entityIds: [CLUSTER_ID], entityKeys: ["210dac24142f1baa"] },
1143
+ ],
1144
+ } as any);
1145
+
1146
+ expect(statement.query).toBe("");
1147
+ });
1148
+
1149
+ test("qualifies every branch when a table alias is in play", () => {
1150
+ const statement: Statement = resourceGenerator.toWhereStatement(
1151
+ {
1152
+ resourceEntityScopes: [
1153
+ {
1154
+ entityIds: [CLUSTER_ID],
1155
+ entityKeys: ["210dac24142f1baa"],
1156
+ attributeKey: "resource.k8s.cluster.name",
1157
+ attributeValues: ["prod-eu"],
1158
+ },
1159
+ ],
1160
+ } as any,
1161
+ { tableAlias: "t" },
1162
+ );
1163
+
1164
+ expect(statement.query).toBe(
1165
+ "AND ({p0_t:Identifier}.{p0_c:Identifier} IN {p1:Array(String)} OR " +
1166
+ "hasAny({p2_t:Identifier}.{p2_c:Identifier}, {p3:Array(String)}) OR " +
1167
+ "{p4_t:Identifier}.{p4_c:Identifier}[{p5:String}] IN {p6:Array(String)})",
1168
+ );
1169
+ });
1170
+ });
1171
+
959
1172
  /*
960
1173
  * Table qualification (tableAlias option). Aggregate statements
961
1174
  * alias expressions to real column names (`sum(col) as col`, and