@oneuptime/common 11.7.1 → 11.7.3

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 (285) hide show
  1. package/Models/AnalyticsModels/KubernetesCostAllocation.ts +103 -0
  2. package/Models/DatabaseModels/DashboardDomain.ts +1 -0
  3. package/Models/DatabaseModels/Incident.ts +2 -0
  4. package/Models/DatabaseModels/IncidentEpisode.ts +1 -0
  5. package/Models/DatabaseModels/IncidentEpisodePublicNote.ts +1 -0
  6. package/Models/DatabaseModels/IncidentEpisodeStateTimeline.ts +1 -0
  7. package/Models/DatabaseModels/IncidentPublicNote.ts +1 -0
  8. package/Models/DatabaseModels/IncidentStateTimeline.ts +1 -0
  9. package/Models/DatabaseModels/Monitor.ts +1 -0
  10. package/Models/DatabaseModels/MonitorStatusTimeline.ts +1 -0
  11. package/Models/DatabaseModels/NetworkSite.ts +1 -0
  12. package/Models/DatabaseModels/NetworkSiteStatusTimeline.ts +1 -0
  13. package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +1 -0
  14. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +2 -0
  15. package/Models/DatabaseModels/Probe.ts +13 -13
  16. package/Models/DatabaseModels/ScheduledMaintenance.ts +1 -0
  17. package/Models/DatabaseModels/ScheduledMaintenancePublicNote.ts +1 -0
  18. package/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.ts +1 -0
  19. package/Models/DatabaseModels/ScheduledMaintenanceTemplate.ts +1 -0
  20. package/Models/DatabaseModels/StatusPage.ts +1 -0
  21. package/Models/DatabaseModels/StatusPageAnnouncement.ts +1 -0
  22. package/Models/DatabaseModels/StatusPageDomain.ts +1 -0
  23. package/Models/DatabaseModels/TableView.ts +40 -0
  24. package/Models/DatabaseModels/WorkspaceNotificationSummary.ts +1 -0
  25. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +11 -0
  26. package/Server/API/BaseAPI.ts +17 -1
  27. package/Server/API/CommonAPI.ts +32 -0
  28. package/Server/API/MicrosoftTeamsAPI.ts +99 -2
  29. package/Server/API/ProbeAPI.ts +2 -0
  30. package/Server/API/SlackAPI.ts +62 -0
  31. package/Server/API/StatusPageAPI.ts +157 -192
  32. package/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.ts +89 -0
  33. package/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.ts +61 -0
  34. package/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.ts +280 -0
  35. package/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.ts +13 -0
  36. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  37. package/Server/Services/DatabaseService.ts +11 -3
  38. package/Server/Services/MonitorGroupService.ts +145 -0
  39. package/Server/Services/MonitorService.ts +231 -30
  40. package/Server/Services/ProbeService.ts +319 -16
  41. package/Server/Services/StatusPageService.ts +72 -42
  42. package/Server/Services/WorkspaceNotificationRuleService.ts +232 -2
  43. package/Server/Utils/Database/PostgresErrorTranslator.ts +131 -0
  44. package/Server/Utils/Monitor/MonitorResource.ts +88 -35
  45. package/Server/Utils/Monitor/MonitorStatusTimeline.ts +21 -1
  46. package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +158 -0
  47. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +706 -6
  48. package/Tests/Models/AnalyticsModels/KubernetesCostAllocation.test.ts +16 -0
  49. package/Tests/Models/DatabaseModels/ProbeColumnAccessControl.test.ts +103 -0
  50. package/Tests/Server/API/BaseAPIUpdateNoOp.test.ts +135 -0
  51. package/Tests/Server/API/CommonAPIAuthGuard.test.ts +317 -0
  52. package/Tests/Server/API/MicrosoftTeamsManifest.test.ts +228 -0
  53. package/Tests/Server/API/ProbeAPI.test.ts +6 -0
  54. package/Tests/Server/API/ZZVerifyChatsAuth.test.ts +167 -0
  55. package/Tests/Server/Services/AddHotQueryIndexesMigration.test.ts +255 -0
  56. package/Tests/Server/Services/AddHotQueryIndexesSecondPassMigration.test.ts +258 -0
  57. package/Tests/Server/Services/DatabaseServiceSanitizeUpdateData.test.ts +1 -1
  58. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +121 -0
  59. package/Tests/Server/Services/HeartbeatWriteFastPathSafety.test.ts +200 -0
  60. package/Tests/Server/Services/HookFreeWriteSafetySecondPass.test.ts +181 -0
  61. package/Tests/Server/Services/MonitorGroupServiceBatchedStatuses.test.ts +513 -0
  62. package/Tests/Server/Services/MonitorResourceProbeAgreementReuse.test.ts +492 -0
  63. package/Tests/Server/Services/MonitorServiceProbeSelection.test.ts +401 -0
  64. package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +453 -0
  65. package/Tests/Server/Services/ProbeAuthKeyCacheAndHeartbeat.test.ts +679 -0
  66. package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +3 -3
  67. package/Tests/Server/Services/StatusPageDomainResolutionCache.test.ts +245 -0
  68. package/Tests/Server/Services/StatusPageOverviewTimelineWindow.test.ts +283 -0
  69. package/Tests/Server/Services/StatusPageServiceMcp.test.ts +17 -1
  70. package/Tests/Server/Services/StatusPageServiceMonitorGroupBatching.test.ts +331 -0
  71. package/Tests/Server/Services/UserOnCallLogClaimNotificationRule.test.ts +12 -12
  72. package/Tests/Server/Services/WorkspaceNotificationRuleChats.test.ts +1390 -0
  73. package/Tests/Server/Utils/AI/Insights/InsightFixRouting.test.ts +4 -4
  74. package/Tests/Server/Utils/AI/Insights/InsightScanner.test.ts +2 -2
  75. package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +1 -3
  76. package/Tests/Server/Utils/AI/Insights/InsightStoreHardening.test.ts +1 -3
  77. package/Tests/Server/Utils/AI/Insights/InsightTriage.test.ts +1 -1
  78. package/Tests/Server/Utils/AI/Insights/InsightTriageRunner.test.ts +5 -5
  79. package/Tests/Server/Utils/Alert/AlertPrivacyFilter.test.ts +187 -0
  80. package/Tests/Server/Utils/AlertEpisode/AlertEpisodePrivacyFilter.test.ts +170 -0
  81. package/Tests/Server/Utils/Database/PostgresErrorTranslator.test.ts +125 -0
  82. package/Tests/Server/Utils/Incident/IncidentPrivacyFilter.test.ts +264 -0
  83. package/Tests/Server/Utils/IncidentEpisode/IncidentEpisodePrivacyFilter.test.ts +175 -0
  84. package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +181 -0
  85. package/Tests/Server/Utils/Monitor/NetworkDeviceWalkUtil.test.ts +1 -1
  86. package/Tests/Server/Utils/Monitor/NetworkInventoryUtil.test.ts +2 -2
  87. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChannelsList.test.ts +341 -0
  88. package/Tests/Server/Utils/Workspace/MicrosoftTeamsChats.test.ts +2175 -0
  89. package/Tests/Server/Utils/Workspace/SlackChannelsList.test.ts +585 -0
  90. package/Tests/Types/Billing/MeteredPlan.test.ts +46 -0
  91. package/Tests/Types/JSONFunctions.test.ts +103 -1
  92. package/Tests/Types/Workspace/NotificationRules/NotificationRuleCondition.test.ts +219 -1
  93. package/Tests/UI/Components/BulkUpdateForm.test.tsx +174 -0
  94. package/Tests/UI/Components/CardModelDetailEdit.test.tsx +315 -0
  95. package/Tests/UI/Components/ModelDetailSelect.test.tsx +229 -0
  96. package/Tests/UI/Components/ModelTable/BaseModelTableBulkSelectAll.test.tsx +1258 -0
  97. package/Tests/UI/Components/ModelTable/BaseModelTableColumnCustomization.test.tsx +679 -0
  98. package/Tests/UI/Components/ModelTable/ColumnCustomizationModal.test.tsx +572 -0
  99. package/Tests/UI/Components/ModelTable/ColumnPreference.test.ts +1678 -0
  100. package/Tests/UI/Components/ModelTable/CustomFieldColumns.test.tsx +1094 -0
  101. package/Tests/UI/Components/ModelTableExportFromColumns.test.ts +153 -0
  102. package/Tests/UI/Components/MonacoLoader.test.ts +67 -0
  103. package/Tests/UI/Components/MonacoRuntime.test.ts +121 -0
  104. package/Tests/UI/Components/TableBulkCsvExport.test.tsx +212 -3
  105. package/Tests/UI/Utils/Project.test.ts +809 -0
  106. package/Tests/UI/Utils/TableColumnsToCsv.test.ts +360 -0
  107. package/Tests/Utils/Alerts/AlertMetricType.test.ts +143 -0
  108. package/Tests/Utils/Dashboard/VariableUrlState.test.ts +219 -0
  109. package/Tests/Utils/Incident/IncidentMetricType.test.ts +174 -0
  110. package/Tests/Utils/Monitor/MonitorProbeSelectionUtil.test.ts +145 -0
  111. package/Tests/Utils/StatusPage/ResourceUptime.test.ts +263 -0
  112. package/Tests/Utils/UserPreferences.test.ts +563 -0
  113. package/Types/JSONFunctions.ts +7 -2
  114. package/Types/Kubernetes/KubernetesCostIngest.ts +37 -1
  115. package/Types/Workspace/NotificationRules/BaseNotificationRule.ts +4 -0
  116. package/Types/Workspace/NotificationRules/NotificationRuleCondition.ts +25 -10
  117. package/Types/Workspace/WorkspaceMessagePayload.ts +1 -0
  118. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +63 -8
  119. package/UI/Components/CodeEditor/CodeEditor.tsx +3 -0
  120. package/UI/Components/CodeEditor/MonacoLoader.ts +36 -0
  121. package/UI/Components/Forms/ModelForm.tsx +41 -18
  122. package/UI/Components/MasterPage/MasterPage.tsx +65 -0
  123. package/UI/Components/ModelDetail/CardModelDetail.tsx +23 -1
  124. package/UI/Components/ModelDetail/ModelDetail.tsx +46 -1
  125. package/UI/Components/ModelTable/BaseModelTable.tsx +687 -34
  126. package/UI/Components/ModelTable/Column.ts +29 -0
  127. package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +420 -0
  128. package/UI/Components/ModelTable/ColumnPreference.ts +482 -0
  129. package/UI/Components/ModelTable/CustomFieldColumns.tsx +326 -0
  130. package/UI/Components/ModelTable/ExportFromColumns.ts +57 -0
  131. package/UI/Components/ModelTable/TableView.tsx +24 -2
  132. package/UI/Components/ModelTable/useCustomFieldColumns.ts +150 -0
  133. package/UI/Components/Navbar/NavBar.tsx +66 -1
  134. package/UI/Components/SideMenu/SideMenu.tsx +24 -4
  135. package/UI/Components/Table/Table.tsx +66 -14
  136. package/UI/Components/Table/TableRow.tsx +180 -175
  137. package/UI/Components/Table/Types/Column.ts +20 -0
  138. package/UI/Utils/Project.ts +214 -18
  139. package/UI/Utils/TableColumnsToCsv.ts +86 -6
  140. package/UI/esbuild-config.js +34 -0
  141. package/Utils/Monitor/MonitorProbeSelectionUtil.ts +72 -0
  142. package/Utils/UserPreferences.ts +53 -0
  143. package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js +86 -0
  144. package/build/dist/Models/AnalyticsModels/KubernetesCostAllocation.js.map +1 -1
  145. package/build/dist/Models/DatabaseModels/DashboardDomain.js +1 -0
  146. package/build/dist/Models/DatabaseModels/DashboardDomain.js.map +1 -1
  147. package/build/dist/Models/DatabaseModels/Incident.js +2 -0
  148. package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
  149. package/build/dist/Models/DatabaseModels/IncidentEpisode.js +1 -0
  150. package/build/dist/Models/DatabaseModels/IncidentEpisode.js.map +1 -1
  151. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js +1 -0
  152. package/build/dist/Models/DatabaseModels/IncidentEpisodePublicNote.js.map +1 -1
  153. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js +1 -0
  154. package/build/dist/Models/DatabaseModels/IncidentEpisodeStateTimeline.js.map +1 -1
  155. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js +1 -0
  156. package/build/dist/Models/DatabaseModels/IncidentPublicNote.js.map +1 -1
  157. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js +1 -0
  158. package/build/dist/Models/DatabaseModels/IncidentStateTimeline.js.map +1 -1
  159. package/build/dist/Models/DatabaseModels/Monitor.js +1 -0
  160. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  161. package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js +1 -0
  162. package/build/dist/Models/DatabaseModels/MonitorStatusTimeline.js.map +1 -1
  163. package/build/dist/Models/DatabaseModels/NetworkSite.js +1 -0
  164. package/build/dist/Models/DatabaseModels/NetworkSite.js.map +1 -1
  165. package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js +1 -0
  166. package/build/dist/Models/DatabaseModels/NetworkSiteStatusTimeline.js.map +1 -1
  167. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +1 -0
  168. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
  169. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +2 -0
  170. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  171. package/build/dist/Models/DatabaseModels/Probe.js +13 -13
  172. package/build/dist/Models/DatabaseModels/Probe.js.map +1 -1
  173. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +1 -0
  174. package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
  175. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js +1 -0
  176. package/build/dist/Models/DatabaseModels/ScheduledMaintenancePublicNote.js.map +1 -1
  177. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js +1 -0
  178. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceStateTimeline.js.map +1 -1
  179. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js +1 -0
  180. package/build/dist/Models/DatabaseModels/ScheduledMaintenanceTemplate.js.map +1 -1
  181. package/build/dist/Models/DatabaseModels/StatusPage.js +1 -0
  182. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  183. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js +1 -0
  184. package/build/dist/Models/DatabaseModels/StatusPageAnnouncement.js.map +1 -1
  185. package/build/dist/Models/DatabaseModels/StatusPageDomain.js +1 -0
  186. package/build/dist/Models/DatabaseModels/StatusPageDomain.js.map +1 -1
  187. package/build/dist/Models/DatabaseModels/TableView.js +40 -0
  188. package/build/dist/Models/DatabaseModels/TableView.js.map +1 -1
  189. package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js +1 -0
  190. package/build/dist/Models/DatabaseModels/WorkspaceNotificationSummary.js.map +1 -1
  191. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  192. package/build/dist/Server/API/BaseAPI.js +12 -1
  193. package/build/dist/Server/API/BaseAPI.js.map +1 -1
  194. package/build/dist/Server/API/CommonAPI.js +21 -0
  195. package/build/dist/Server/API/CommonAPI.js.map +1 -1
  196. package/build/dist/Server/API/MicrosoftTeamsAPI.js +72 -2
  197. package/build/dist/Server/API/MicrosoftTeamsAPI.js.map +1 -1
  198. package/build/dist/Server/API/ProbeAPI.js +2 -0
  199. package/build/dist/Server/API/ProbeAPI.js.map +1 -1
  200. package/build/dist/Server/API/SlackAPI.js +44 -0
  201. package/build/dist/Server/API/SlackAPI.js.map +1 -1
  202. package/build/dist/Server/API/StatusPageAPI.js +98 -152
  203. package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
  204. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.js +36 -0
  205. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785140242697-AddHotQueryIndexes.js.map +1 -0
  206. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.js +26 -0
  207. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785148065137-AddHotQueryIndexesSecondPass.js.map +1 -0
  208. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.js +254 -0
  209. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785240000000-RepairCrossProjectMonitorStatusReferences.js.map +1 -0
  210. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.js +12 -0
  211. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1785241000000-AddColumnsToTableView.js.map +1 -0
  212. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  213. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  214. package/build/dist/Server/Services/DatabaseService.js +10 -2
  215. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  216. package/build/dist/Server/Services/MonitorGroupService.js +118 -0
  217. package/build/dist/Server/Services/MonitorGroupService.js.map +1 -1
  218. package/build/dist/Server/Services/MonitorService.js +198 -24
  219. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  220. package/build/dist/Server/Services/ProbeService.js +238 -9
  221. package/build/dist/Server/Services/ProbeService.js.map +1 -1
  222. package/build/dist/Server/Services/StatusPageService.js +46 -39
  223. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  224. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js +178 -3
  225. package/build/dist/Server/Services/WorkspaceNotificationRuleService.js.map +1 -1
  226. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js +80 -0
  227. package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js.map +1 -0
  228. package/build/dist/Server/Utils/Monitor/MonitorResource.js +67 -29
  229. package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
  230. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +31 -14
  231. package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
  232. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +117 -0
  233. package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -0
  234. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +549 -7
  235. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  236. package/build/dist/Types/JSONFunctions.js +8 -3
  237. package/build/dist/Types/JSONFunctions.js.map +1 -1
  238. package/build/dist/Types/Kubernetes/KubernetesCostIngest.js.map +1 -1
  239. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js +17 -7
  240. package/build/dist/Types/Workspace/NotificationRules/NotificationRuleCondition.js.map +1 -1
  241. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +37 -11
  242. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  243. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +2 -0
  244. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  245. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js +34 -0
  246. package/build/dist/UI/Components/CodeEditor/MonacoLoader.js.map +1 -0
  247. package/build/dist/UI/Components/Forms/ModelForm.js +37 -17
  248. package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
  249. package/build/dist/UI/Components/MasterPage/MasterPage.js +50 -1
  250. package/build/dist/UI/Components/MasterPage/MasterPage.js.map +1 -1
  251. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js +22 -1
  252. package/build/dist/UI/Components/ModelDetail/CardModelDetail.js.map +1 -1
  253. package/build/dist/UI/Components/ModelDetail/ModelDetail.js +31 -1
  254. package/build/dist/UI/Components/ModelDetail/ModelDetail.js.map +1 -1
  255. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +472 -27
  256. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  257. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +189 -0
  258. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -0
  259. package/build/dist/UI/Components/ModelTable/ColumnPreference.js +258 -0
  260. package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -0
  261. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js +168 -0
  262. package/build/dist/UI/Components/ModelTable/CustomFieldColumns.js.map +1 -0
  263. package/build/dist/UI/Components/ModelTable/ExportFromColumns.js +30 -0
  264. package/build/dist/UI/Components/ModelTable/ExportFromColumns.js.map +1 -0
  265. package/build/dist/UI/Components/ModelTable/TableView.js +13 -2
  266. package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
  267. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js +84 -0
  268. package/build/dist/UI/Components/ModelTable/useCustomFieldColumns.js.map +1 -0
  269. package/build/dist/UI/Components/Navbar/NavBar.js +39 -1
  270. package/build/dist/UI/Components/Navbar/NavBar.js.map +1 -1
  271. package/build/dist/UI/Components/SideMenu/SideMenu.js +15 -5
  272. package/build/dist/UI/Components/SideMenu/SideMenu.js.map +1 -1
  273. package/build/dist/UI/Components/Table/Table.js +46 -13
  274. package/build/dist/UI/Components/Table/Table.js.map +1 -1
  275. package/build/dist/UI/Components/Table/TableRow.js +11 -6
  276. package/build/dist/UI/Components/Table/TableRow.js.map +1 -1
  277. package/build/dist/UI/Utils/Project.js +161 -15
  278. package/build/dist/UI/Utils/Project.js.map +1 -1
  279. package/build/dist/UI/Utils/TableColumnsToCsv.js +67 -6
  280. package/build/dist/UI/Utils/TableColumnsToCsv.js.map +1 -1
  281. package/build/dist/Utils/Monitor/MonitorProbeSelectionUtil.js +52 -0
  282. package/build/dist/Utils/Monitor/MonitorProbeSelectionUtil.js.map +1 -0
  283. package/build/dist/Utils/UserPreferences.js +33 -0
  284. package/build/dist/Utils/UserPreferences.js.map +1 -1
  285. package/package.json +4 -3
@@ -37,12 +37,15 @@ describe("Analytics KubernetesCostAllocation model", () => {
37
37
  "cpuCoreHours",
38
38
  "cpuCoreRequestAverage",
39
39
  "cpuCoreUsageAverage",
40
+ "cpuCoreLimitAverage",
40
41
  "cpuCost",
41
42
  "gpuHours",
42
43
  "gpuCost",
43
44
  "ramByteHours",
44
45
  "ramBytesRequestAverage",
45
46
  "ramBytesUsageAverage",
47
+ "ramBytesLimitAverage",
48
+ "ramBytesUsageMax",
46
49
  "ramCost",
47
50
  "pvByteHours",
48
51
  "pvCost",
@@ -95,6 +98,19 @@ describe("Analytics KubernetesCostAllocation model", () => {
95
98
  );
96
99
  });
97
100
 
101
+ test("carries the shipment identity the ingest dedup relies on", () => {
102
+ const shipmentId: AnalyticsTableColumn | null =
103
+ model.getTableColumn("shipmentId");
104
+ expect(shipmentId?.type).toBe(TableColumnType.Text);
105
+ // Rows written before the shipment contract read back as "no identity".
106
+ expect(shipmentId?.defaultValue).toBe("");
107
+
108
+ const shipmentChunk: AnalyticsTableColumn | null =
109
+ model.getTableColumn("shipmentChunk");
110
+ expect(shipmentChunk?.type).toBe(TableColumnType.Number);
111
+ expect(shipmentChunk?.defaultValue).toBe(0);
112
+ });
113
+
98
114
  test("stores labels as a map with an extracted key column", () => {
99
115
  const labels: AnalyticsTableColumn | null = model.getTableColumn("labels");
100
116
  expect(labels?.type).toBe(TableColumnType.MapStringString);
@@ -0,0 +1,103 @@
1
+ import Probe from "../../../Models/DatabaseModels/Probe";
2
+ import { ColumnAccessControl } from "../../../Types/BaseDatabase/AccessControl";
3
+ import Permission from "../../../Types/Permission";
4
+ import { describe, expect, it } from "@jest/globals";
5
+
6
+ /*
7
+ * Several Probe columns were gated on status-page permissions
8
+ * (Create/Read/EditProjectStatusPage) instead of the probe ones. The table
9
+ * itself is gated on Create/Read/Edit/DeleteProjectProbe, so a team granted
10
+ * only granular probe permissions could open the probe edit form and change
11
+ * "Enable monitoring automatically on new monitors" - and ModelForm would drop
12
+ * the field from both the form and the request, with no error. The toggle
13
+ * looked unset afterwards, and the probe never got attached to new monitors.
14
+ *
15
+ * Any Probe column gated on a status-page permission is a copy-paste bug, so
16
+ * this asserts the absence outright rather than listing today's offenders.
17
+ */
18
+
19
+ const STATUS_PAGE_PERMISSIONS: Array<Permission> = [
20
+ Permission.CreateProjectStatusPage,
21
+ Permission.ReadProjectStatusPage,
22
+ Permission.EditProjectStatusPage,
23
+ Permission.DeleteProjectStatusPage,
24
+ ];
25
+
26
+ describe("Probe column access control", () => {
27
+ const accessControl: Record<string, ColumnAccessControl> =
28
+ new Probe().getColumnAccessControlForAllColumns();
29
+
30
+ it("never gates a probe column on a status page permission", () => {
31
+ const offenders: Array<string> = [];
32
+
33
+ for (const columnName of Object.keys(accessControl)) {
34
+ const column: ColumnAccessControl | undefined = accessControl[columnName];
35
+
36
+ const allPermissions: Array<Permission> = [
37
+ ...(column?.create || []),
38
+ ...(column?.read || []),
39
+ ...(column?.update || []),
40
+ ];
41
+
42
+ for (const permission of allPermissions) {
43
+ if (STATUS_PAGE_PERMISSIONS.includes(permission)) {
44
+ offenders.push(`${columnName}: ${permission}`);
45
+ }
46
+ }
47
+ }
48
+
49
+ expect(offenders).toEqual([]);
50
+ });
51
+
52
+ it("lets a user with granular probe permissions edit the auto-enable toggle", () => {
53
+ const column: ColumnAccessControl | undefined =
54
+ accessControl["shouldAutoEnableProbeOnNewMonitors"];
55
+
56
+ expect(column).toBeDefined();
57
+ expect(column!.update).toContain(Permission.EditProjectProbe);
58
+ expect(column!.read).toContain(Permission.ReadProjectProbe);
59
+ expect(column!.create).toContain(Permission.CreateProjectProbe);
60
+ });
61
+
62
+ it("keeps every field on the probe edit form readable and writable by a project owner", () => {
63
+ /*
64
+ * Every field the "Probe Details" card edits. If any of these stops
65
+ * intersecting a project owner's permissions, editing a probe silently
66
+ * stops working for the person who created it - which is exactly how the
67
+ * original bug presented.
68
+ */
69
+ const editableColumns: Array<string> = [
70
+ "name",
71
+ "description",
72
+ "iconFile",
73
+ "shouldAutoEnableProbeOnNewMonitors",
74
+ "labels",
75
+ ];
76
+
77
+ // What a signed-in project owner actually carries.
78
+ const ownerPermissions: Array<Permission> = [
79
+ Permission.Public,
80
+ Permission.User,
81
+ Permission.CurrentUser,
82
+ Permission.ProjectOwner,
83
+ ];
84
+
85
+ for (const columnName of editableColumns) {
86
+ const column: ColumnAccessControl | undefined = accessControl[columnName];
87
+
88
+ expect(column).toBeDefined();
89
+
90
+ expect(
91
+ (column!.update || []).some((permission: Permission) => {
92
+ return ownerPermissions.includes(permission);
93
+ }),
94
+ ).toBe(true);
95
+
96
+ expect(
97
+ (column!.read || []).some((permission: Permission) => {
98
+ return ownerPermissions.includes(permission);
99
+ }),
100
+ ).toBe(true);
101
+ }
102
+ });
103
+ });
@@ -0,0 +1,135 @@
1
+ import BaseAPI from "../../../Server/API/BaseAPI";
2
+ import DatabaseService from "../../../Server/Services/DatabaseService";
3
+ import {
4
+ ExpressResponse,
5
+ OneUptimeRequest,
6
+ } from "../../../Server/Utils/Express";
7
+ import Response from "../../../Server/Utils/Response";
8
+ import { mockRouter } from "./Helpers";
9
+ import { beforeEach, describe, expect, it, jest } from "@jest/globals";
10
+ import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
11
+ import ObjectID from "../../../Types/ObjectID";
12
+ import ModelPermission from "../../../Server/Types/Database/Permissions/Index";
13
+ import Probe from "../../../Models/DatabaseModels/Probe";
14
+ import { getJestSpyOn } from "../../Spy";
15
+
16
+ jest.mock("../../../Server/Utils/Express", () => {
17
+ return {
18
+ getRouter: () => {
19
+ return mockRouter;
20
+ },
21
+ };
22
+ });
23
+
24
+ jest.mock("../../../Server/Utils/Response", () => {
25
+ return {
26
+ sendEmptySuccessResponse: jest.fn(),
27
+ sendEntityResponse: jest.fn(),
28
+ sendEntityArrayResponse: jest.fn(),
29
+ sendJsonObjectResponse: jest.fn(),
30
+ };
31
+ });
32
+
33
+ /*
34
+ * PUT /<model>/:id answered 200 with an empty body no matter what happened.
35
+ *
36
+ * DatabaseService narrows the update query AFTER the caller builds it - tenant
37
+ * scope, access-control labels, owned scope - so an update can match zero rows
38
+ * and write nothing. updateOneById threw that count away and BaseAPI reported
39
+ * success regardless, so the client showed a saved edit that was never
40
+ * persisted and "disappeared" on the next page load, with no error anywhere.
41
+ */
42
+
43
+ const TEST_ID: string = "550e8400-e29b-41d4-a716-446655440009";
44
+
45
+ describe("BaseAPI.updateItem when the update matches nothing", () => {
46
+ let service: DatabaseService<Probe>;
47
+ let api: BaseAPI<Probe, DatabaseService<Probe>>;
48
+ let request: OneUptimeRequest;
49
+ let response: ExpressResponse;
50
+
51
+ beforeEach(() => {
52
+ jest.restoreAllMocks();
53
+ jest.clearAllMocks();
54
+
55
+ service = new DatabaseService<Probe>(Probe);
56
+ api = new BaseAPI<Probe, DatabaseService<Probe>>(Probe, service);
57
+
58
+ request = {
59
+ params: { id: TEST_ID },
60
+ body: { data: { name: "renamed-probe" } },
61
+ headers: {},
62
+ } as unknown as OneUptimeRequest;
63
+
64
+ response = {
65
+ status: jest.fn().mockReturnThis(),
66
+ json: jest.fn().mockReturnThis(),
67
+ send: jest.fn().mockReturnThis(),
68
+ } as unknown as ExpressResponse;
69
+ });
70
+
71
+ it("reports success when a row was actually updated", async () => {
72
+ getJestSpyOn(service, "updateOneById").mockResolvedValue(1);
73
+
74
+ await api.updateItem(request, response);
75
+
76
+ expect(Response.sendEmptySuccessResponse).toHaveBeenCalledWith(
77
+ request,
78
+ response,
79
+ );
80
+ });
81
+
82
+ it("refuses to report success when nothing was written", async () => {
83
+ getJestSpyOn(service, "updateOneById").mockResolvedValue(0);
84
+
85
+ await expect(api.updateItem(request, response)).rejects.toThrow(
86
+ NotAuthorizedException,
87
+ );
88
+
89
+ expect(Response.sendEmptySuccessResponse).not.toHaveBeenCalled();
90
+ });
91
+
92
+ it("names the resource in the error so the message is actionable", async () => {
93
+ getJestSpyOn(service, "updateOneById").mockResolvedValue(0);
94
+
95
+ await expect(api.updateItem(request, response)).rejects.toThrow(/probe/i);
96
+ });
97
+ });
98
+
99
+ describe("DatabaseService.updateOneById", () => {
100
+ it("hands back the number of rows the update matched", async () => {
101
+ const service: DatabaseService<Probe> = new DatabaseService<Probe>(Probe);
102
+
103
+ getJestSpyOn(
104
+ ModelPermission,
105
+ "checkUpdatePermissionByModel",
106
+ ).mockResolvedValue(undefined);
107
+ getJestSpyOn(service, "updateOneBy").mockResolvedValue(3);
108
+
109
+ const numberOfDocsAffected: number = await service.updateOneById({
110
+ id: new ObjectID(TEST_ID),
111
+ data: {} as any,
112
+ props: { isRoot: true },
113
+ });
114
+
115
+ expect(numberOfDocsAffected).toBe(3);
116
+ });
117
+
118
+ it("hands back zero when the permission-scoped query matched no rows", async () => {
119
+ const service: DatabaseService<Probe> = new DatabaseService<Probe>(Probe);
120
+
121
+ getJestSpyOn(
122
+ ModelPermission,
123
+ "checkUpdatePermissionByModel",
124
+ ).mockResolvedValue(undefined);
125
+ getJestSpyOn(service, "updateOneBy").mockResolvedValue(0);
126
+
127
+ const numberOfDocsAffected: number = await service.updateOneById({
128
+ id: new ObjectID(TEST_ID),
129
+ data: {} as any,
130
+ props: { isRoot: true },
131
+ });
132
+
133
+ expect(numberOfDocsAffected).toBe(0);
134
+ });
135
+ });
@@ -0,0 +1,317 @@
1
+ import { describe, expect, test, afterEach } from "@jest/globals";
2
+ import CommonAPI from "../../../Server/API/CommonAPI";
3
+ import DatabaseCommonInteractionProps from "../../../Types/BaseDatabase/DatabaseCommonInteractionProps";
4
+ import Dictionary from "../../../Types/Dictionary";
5
+ import ObjectID from "../../../Types/ObjectID";
6
+ import Permission, {
7
+ UserPermission,
8
+ UserTenantAccessPermission,
9
+ } from "../../../Types/Permission";
10
+ import BadDataException from "../../../Types/Exception/BadDataException";
11
+ import NotAuthorizedException from "../../../Types/Exception/NotAuthorizedException";
12
+ import Exception from "../../../Types/Exception/Exception";
13
+
14
+ /*
15
+ * Tests for CommonAPI.assertAuthenticatedProjectMember — the shared auth
16
+ * guard for custom endpoints that disclose project data (Teams / Slack
17
+ * channel browsing, etc.). getUserMiddleware lets unauthenticated requests
18
+ * through as "public" and takes the tenant id from a caller-supplied
19
+ * header, so this guard must reject anything that is not an authenticated
20
+ * member of the tenant project.
21
+ */
22
+
23
+ function buildTenantPermission(
24
+ projectId: ObjectID,
25
+ ): UserTenantAccessPermission {
26
+ const memberPermission: UserPermission = {
27
+ _type: "UserPermission",
28
+ permission: Permission.ProjectMember,
29
+ labelIds: [],
30
+ };
31
+
32
+ const tenantPermission: UserTenantAccessPermission = {
33
+ _type: "UserTenantAccessPermission",
34
+ projectId: projectId,
35
+ permissions: [memberPermission],
36
+ };
37
+
38
+ return tenantPermission;
39
+ }
40
+
41
+ function buildProps(overrides?: {
42
+ tenantId?: ObjectID | undefined;
43
+ userId?: ObjectID | undefined;
44
+ userTenantAccessPermission?:
45
+ | Dictionary<UserTenantAccessPermission>
46
+ | undefined;
47
+ }): DatabaseCommonInteractionProps {
48
+ const props: DatabaseCommonInteractionProps = {
49
+ tenantId: overrides?.tenantId,
50
+ userId: overrides?.userId,
51
+ userTenantAccessPermission: overrides?.userTenantAccessPermission,
52
+ };
53
+ return props;
54
+ }
55
+
56
+ /*
57
+ * The guard is synchronous, so failures are captured with try/catch (via
58
+ * this helper) or expect(() => ...).toThrow — never .rejects.
59
+ */
60
+ function captureThrown(fn: () => void): unknown {
61
+ try {
62
+ fn();
63
+ } catch (err: unknown) {
64
+ return err;
65
+ }
66
+ return undefined;
67
+ }
68
+
69
+ afterEach(() => {
70
+ jest.restoreAllMocks();
71
+ });
72
+
73
+ describe("CommonAPI.assertAuthenticatedProjectMember", () => {
74
+ describe("missing tenant id", () => {
75
+ test("throws BadDataException with 'Project ID is required' when tenantId is undefined", () => {
76
+ const props: DatabaseCommonInteractionProps = buildProps({
77
+ tenantId: undefined,
78
+ });
79
+
80
+ expect(() => {
81
+ CommonAPI.assertAuthenticatedProjectMember(props);
82
+ }).toThrow(BadDataException);
83
+ expect(() => {
84
+ CommonAPI.assertAuthenticatedProjectMember(props);
85
+ }).toThrow("Project ID is required");
86
+ });
87
+
88
+ test("missing tenantId wins over everything else — even a fully authenticated user gets BadDataException", () => {
89
+ const projectId: ObjectID = ObjectID.generate();
90
+ const props: DatabaseCommonInteractionProps = buildProps({
91
+ tenantId: undefined,
92
+ userId: ObjectID.generate(),
93
+ userTenantAccessPermission: {
94
+ [projectId.toString()]: buildTenantPermission(projectId),
95
+ },
96
+ });
97
+
98
+ const thrown: unknown = captureThrown(() => {
99
+ CommonAPI.assertAuthenticatedProjectMember(props);
100
+ });
101
+
102
+ expect(thrown).toBeInstanceOf(BadDataException);
103
+ expect(thrown).not.toBeInstanceOf(NotAuthorizedException);
104
+ expect((thrown as BadDataException).message).toBe(
105
+ "Project ID is required",
106
+ );
107
+ });
108
+
109
+ test("completely empty props object throws BadDataException", () => {
110
+ const props: DatabaseCommonInteractionProps = {};
111
+
112
+ expect(() => {
113
+ CommonAPI.assertAuthenticatedProjectMember(props);
114
+ }).toThrow(BadDataException);
115
+ });
116
+ });
117
+
118
+ describe("unauthenticated / unauthorized callers", () => {
119
+ test("tenantId present but no userId throws NotAuthorizedException", () => {
120
+ const projectId: ObjectID = ObjectID.generate();
121
+ const props: DatabaseCommonInteractionProps = buildProps({
122
+ tenantId: projectId,
123
+ userId: undefined,
124
+ userTenantAccessPermission: {
125
+ [projectId.toString()]: buildTenantPermission(projectId),
126
+ },
127
+ });
128
+
129
+ const thrown: unknown = captureThrown(() => {
130
+ CommonAPI.assertAuthenticatedProjectMember(props);
131
+ });
132
+
133
+ expect(thrown).toBeInstanceOf(NotAuthorizedException);
134
+ expect((thrown as NotAuthorizedException).message).toBe(
135
+ "You are not authorized to access this project's data.",
136
+ );
137
+ });
138
+
139
+ test("userId present but no userTenantAccessPermission map throws NotAuthorizedException", () => {
140
+ const props: DatabaseCommonInteractionProps = buildProps({
141
+ tenantId: ObjectID.generate(),
142
+ userId: ObjectID.generate(),
143
+ userTenantAccessPermission: undefined,
144
+ });
145
+
146
+ expect(() => {
147
+ CommonAPI.assertAuthenticatedProjectMember(props);
148
+ }).toThrow(NotAuthorizedException);
149
+ });
150
+
151
+ test("permission map keyed by a DIFFERENT project id throws NotAuthorizedException", () => {
152
+ const requestedProjectId: ObjectID = ObjectID.generate();
153
+ const otherProjectId: ObjectID = ObjectID.generate();
154
+ const props: DatabaseCommonInteractionProps = buildProps({
155
+ tenantId: requestedProjectId,
156
+ userId: ObjectID.generate(),
157
+ userTenantAccessPermission: {
158
+ [otherProjectId.toString()]: buildTenantPermission(otherProjectId),
159
+ },
160
+ });
161
+
162
+ const thrown: unknown = captureThrown(() => {
163
+ CommonAPI.assertAuthenticatedProjectMember(props);
164
+ });
165
+
166
+ expect(thrown).toBeInstanceOf(NotAuthorizedException);
167
+ expect((thrown as NotAuthorizedException).message).toBe(
168
+ "You are not authorized to access this project's data.",
169
+ );
170
+ });
171
+
172
+ test("empty-object permission map throws NotAuthorizedException", () => {
173
+ const props: DatabaseCommonInteractionProps = buildProps({
174
+ tenantId: ObjectID.generate(),
175
+ userId: ObjectID.generate(),
176
+ userTenantAccessPermission: {},
177
+ });
178
+
179
+ expect(() => {
180
+ CommonAPI.assertAuthenticatedProjectMember(props);
181
+ }).toThrow(NotAuthorizedException);
182
+ });
183
+
184
+ test("the not-authorized error is a NotAuthorizedException, not a BadDataException", () => {
185
+ const props: DatabaseCommonInteractionProps = buildProps({
186
+ tenantId: ObjectID.generate(),
187
+ userId: ObjectID.generate(),
188
+ userTenantAccessPermission: {},
189
+ });
190
+
191
+ const thrown: unknown = captureThrown(() => {
192
+ CommonAPI.assertAuthenticatedProjectMember(props);
193
+ });
194
+
195
+ expect(thrown).toBeInstanceOf(NotAuthorizedException);
196
+ expect(thrown).not.toBeInstanceOf(BadDataException);
197
+ expect(thrown).toBeInstanceOf(Exception);
198
+ expect(thrown).toBeInstanceOf(Error);
199
+ });
200
+
201
+ test("no userId AND no permission map throws NotAuthorizedException (public caller)", () => {
202
+ const props: DatabaseCommonInteractionProps = buildProps({
203
+ tenantId: ObjectID.generate(),
204
+ userId: undefined,
205
+ userTenantAccessPermission: undefined,
206
+ });
207
+
208
+ expect(() => {
209
+ CommonAPI.assertAuthenticatedProjectMember(props);
210
+ }).toThrow(NotAuthorizedException);
211
+ expect(() => {
212
+ CommonAPI.assertAuthenticatedProjectMember(props);
213
+ }).toThrow("You are not authorized to access this project's data.");
214
+ });
215
+ });
216
+
217
+ describe("authorized callers", () => {
218
+ test("happy path returns the exact same ObjectID instance that was passed as tenantId", () => {
219
+ const projectId: ObjectID = ObjectID.generate();
220
+ const props: DatabaseCommonInteractionProps = buildProps({
221
+ tenantId: projectId,
222
+ userId: ObjectID.generate(),
223
+ userTenantAccessPermission: {
224
+ [projectId.toString()]: buildTenantPermission(projectId),
225
+ },
226
+ });
227
+
228
+ const returned: ObjectID =
229
+ CommonAPI.assertAuthenticatedProjectMember(props);
230
+
231
+ expect(returned).toBe(projectId);
232
+ expect(returned.toString()).toBe(projectId.toString());
233
+ });
234
+
235
+ test("happy path does not throw", () => {
236
+ const projectId: ObjectID = ObjectID.generate();
237
+ const props: DatabaseCommonInteractionProps = buildProps({
238
+ tenantId: projectId,
239
+ userId: ObjectID.generate(),
240
+ userTenantAccessPermission: {
241
+ [projectId.toString()]: buildTenantPermission(projectId),
242
+ },
243
+ });
244
+
245
+ expect(() => {
246
+ CommonAPI.assertAuthenticatedProjectMember(props);
247
+ }).not.toThrow();
248
+ });
249
+
250
+ test("happy path with multiple projects in the permission map returns the requested tenant", () => {
251
+ const projectA: ObjectID = ObjectID.generate();
252
+ const projectB: ObjectID = ObjectID.generate();
253
+ const projectC: ObjectID = ObjectID.generate();
254
+ const permissionMap: Dictionary<UserTenantAccessPermission> = {
255
+ [projectA.toString()]: buildTenantPermission(projectA),
256
+ [projectB.toString()]: buildTenantPermission(projectB),
257
+ [projectC.toString()]: buildTenantPermission(projectC),
258
+ };
259
+ const props: DatabaseCommonInteractionProps = buildProps({
260
+ tenantId: projectB,
261
+ userId: ObjectID.generate(),
262
+ userTenantAccessPermission: permissionMap,
263
+ });
264
+
265
+ const returned: ObjectID =
266
+ CommonAPI.assertAuthenticatedProjectMember(props);
267
+
268
+ expect(returned).toBe(projectB);
269
+ });
270
+
271
+ test("a DIFFERENT ObjectID instance with the same string value is still authorized (lookup is by string)", () => {
272
+ const projectIdString: string = ObjectID.generate().toString();
273
+ const tenantInstance: ObjectID = new ObjectID(projectIdString);
274
+ const permissionKeyInstance: ObjectID = new ObjectID(projectIdString);
275
+ const props: DatabaseCommonInteractionProps = buildProps({
276
+ tenantId: tenantInstance,
277
+ userId: ObjectID.generate(),
278
+ userTenantAccessPermission: {
279
+ [permissionKeyInstance.toString()]: buildTenantPermission(
280
+ permissionKeyInstance,
281
+ ),
282
+ },
283
+ });
284
+
285
+ const returned: ObjectID =
286
+ CommonAPI.assertAuthenticatedProjectMember(props);
287
+
288
+ /*
289
+ * The instance returned is the tenantId from props, not the one the
290
+ * permission map was built from.
291
+ */
292
+ expect(returned).toBe(tenantInstance);
293
+ expect(returned).not.toBe(permissionKeyInstance);
294
+ expect(returned.toString()).toBe(projectIdString);
295
+ });
296
+
297
+ test("extra unrelated props (isRoot, isMasterAdmin) do not bypass the membership check", () => {
298
+ const projectId: ObjectID = ObjectID.generate();
299
+ const props: DatabaseCommonInteractionProps = {
300
+ tenantId: projectId,
301
+ userId: undefined,
302
+ userTenantAccessPermission: undefined,
303
+ isRoot: true,
304
+ isMasterAdmin: true,
305
+ };
306
+
307
+ /*
308
+ * Pin current behavior: the guard checks userId + the tenant access
309
+ * permission map ONLY — root / master-admin flags do not grant
310
+ * access to these endpoints.
311
+ */
312
+ expect(() => {
313
+ CommonAPI.assertAuthenticatedProjectMember(props);
314
+ }).toThrow(NotAuthorizedException);
315
+ });
316
+ });
317
+ });