@oneuptime/common 12.0.23 → 12.0.24

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 (294) hide show
  1. package/Models/DatabaseModels/DetectionRule.ts +55 -0
  2. package/Models/DatabaseModels/NetworkDeviceAutoImportRule.ts +27 -7
  3. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +67 -0
  4. package/Models/DatabaseModels/Project.ts +68 -0
  5. package/Models/DatabaseModels/User.ts +68 -0
  6. package/Server/API/TelemetryAPI.ts +29 -0
  7. package/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.ts +28 -0
  8. package/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.ts +49 -0
  9. package/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.ts +33 -0
  10. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
  11. package/Server/Middleware/GzipRequestBody.ts +311 -0
  12. package/Server/Middleware/MultipartFormData.ts +87 -3
  13. package/Server/Services/DetectionRuleService.ts +73 -6
  14. package/Server/Services/Index.ts +205 -0
  15. package/Server/Services/NetworkDeviceDiscoveryScanService.ts +59 -0
  16. package/Server/Services/NetworkDeviceService.ts +148 -0
  17. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
  18. package/Server/Services/OnCallDutyPolicyFeedService.ts +1 -1
  19. package/Server/Services/ProjectService.ts +29 -19
  20. package/Server/Services/SecurityEventService.ts +90 -13
  21. package/Server/Services/StatusPageResourceService.ts +223 -0
  22. package/Server/Services/TelemetryAttributeService.ts +17 -0
  23. package/Server/Services/UserService.ts +4 -6
  24. package/Server/Types/Markdown.ts +161 -67
  25. package/Server/Utils/AI/Toolbox/OnCallTools.ts +12 -4
  26. package/Server/Utils/AnalyticsDatabase/Statement.ts +13 -1
  27. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +297 -6
  28. package/Server/Utils/Attribution.ts +4 -0
  29. package/Server/Utils/Marketing/MarketingEventUtil.ts +45 -7
  30. package/Server/Utils/SecurityEvent/DetectionRuleEvaluator.ts +55 -3
  31. package/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.ts +21 -0
  32. package/Server/Utils/StartServer.ts +12 -32
  33. package/Server/Utils/Telemetry/LlmCostBudgetEvaluator.ts +101 -4
  34. package/Server/Utils/Telemetry/LlmMetricSpend.ts +130 -0
  35. package/Server/Views/Partials/AnalyticsConsent.ejs +159 -102
  36. package/Server/Views/Partials/Head.ejs +2 -1
  37. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +776 -38
  38. package/Tests/App/Dashboard/CorrelateFilterBuilder.test.tsx +359 -0
  39. package/Tests/App/Dashboard/CorrelateGraph.test.tsx +865 -0
  40. package/Tests/App/Dashboard/CorrelationGraph.test.ts +346 -0
  41. package/Tests/App/Dashboard/CriteriaFilterConditionDropdown.test.tsx +256 -0
  42. package/Tests/App/Dashboard/CriteriaFilterDefaults.test.ts +738 -0
  43. package/Tests/App/Dashboard/CriteriaFilterMonitorTypeChange.test.ts +1122 -0
  44. package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +283 -0
  45. package/Tests/App/Dashboard/ExternalStatusPageCriteriaFilter.test.ts +407 -0
  46. package/Tests/App/Dashboard/MetricSeriesInvestigateMenu.test.tsx +5 -14
  47. package/Tests/App/Dashboard/MonitorCriteriaActionToggles.test.tsx +426 -0
  48. package/Tests/App/Dashboard/MonitorStepsCriteriaAlignmentWiring.test.tsx +352 -0
  49. package/Tests/App/Dashboard/OnCallLayerCardOverrides.test.tsx +278 -0
  50. package/Tests/App/Dashboard/OnCallLayerShiftPreviewOverrides.test.ts +237 -0
  51. package/Tests/App/Dashboard/OnCallReadinessSurfaces.test.tsx +2 -2
  52. package/Tests/App/Dashboard/OnCallSchedulePreviewOverrides.test.tsx +460 -0
  53. package/Tests/App/Dashboard/SecurityEventAttributeUtil.test.ts +171 -0
  54. package/Tests/App/Dashboard/SecurityEventCorrelation.test.ts +857 -0
  55. package/Tests/App/Dashboard/SecurityEventDetailObservables.test.tsx +194 -0
  56. package/Tests/App/Dashboard/SecurityEventsDetectionRulesPage.test.tsx +124 -0
  57. package/Tests/App/Dashboard/SecurityEventsMonitorStepForm.test.tsx +79 -19
  58. package/Tests/App/Dashboard/SecurityEventsTableColumns.test.tsx +382 -0
  59. package/Tests/App/StatusPage/BulkAddStatusPageMonitors.test.ts +216 -0
  60. package/Tests/App/StatusPage/BulkAddStatusPageMonitorsModal.test.tsx +590 -11
  61. package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +757 -0
  62. package/Tests/Models/DiscoveryScanNameColumn.test.ts +293 -0
  63. package/Tests/Server/API/SecurityEventAttributesAPI.test.ts +564 -0
  64. package/Tests/Server/Infrastructure/Postgres/SchemaMigrationsOrdering.test.ts +120 -4
  65. package/Tests/Server/Middleware/GzipRequestBody.test.ts +725 -0
  66. package/Tests/Server/Middleware/MultipartFormData.test.ts +298 -0
  67. package/Tests/Server/Services/AddDetectionRuleDistinctCountColumnsMigration.test.ts +311 -0
  68. package/Tests/Server/Services/DetectionRuleService.test.ts +134 -2
  69. package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +7 -1
  70. package/Tests/Server/Services/FeedRetentionConsistency.test.ts +131 -0
  71. package/Tests/Server/Services/NetworkDeviceDiscoveryScanService.test.ts +194 -0
  72. package/Tests/Server/Services/NetworkDeviceMonitorStatusStamp.test.ts +800 -0
  73. package/Tests/Server/Services/OnCallScheduleAttachRosterRefresh.test.ts +244 -0
  74. package/Tests/Server/Services/SecurityEventService.test.ts +376 -0
  75. package/Tests/Server/Services/StatusPageResourceDuplicates.test.ts +703 -0
  76. package/Tests/Server/Services/TelemetryAttributeServiceSecurityEvent.test.ts +256 -0
  77. package/Tests/Server/Types/Workflow/Components/ComponentRegistryParity.test.ts +417 -0
  78. package/Tests/Server/Utils/AnalyticsDatabase/StatementGeneratorWhereOperators.test.ts +677 -0
  79. package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +43 -0
  80. package/Tests/Server/Utils/Monitor/Criteria/ProfileMonitorCriteria.test.ts +15 -11
  81. package/Tests/Server/Utils/SecurityEvent/DetectionRuleEvaluator.test.ts +369 -8
  82. package/Tests/Server/Utils/SecurityEvent/SigmaClickhouseCompiler.test.ts +35 -0
  83. package/Tests/Server/Utils/Telemetry/LlmCostBudgetEvaluator.test.ts +368 -4
  84. package/Tests/Server/Utils/Telemetry/LlmMetricSpend.test.ts +245 -0
  85. package/Tests/Server/Views/AnalyticsConsentPartial.test.ts +48 -5
  86. package/Tests/Types/Code/YamlSyntax.test.ts +396 -0
  87. package/Tests/Types/Monitor/MetricAndProfileDefaultCriteria.test.ts +957 -0
  88. package/Tests/Types/Monitor/MonitorCriteriaActionValidation.test.ts +598 -0
  89. package/Tests/Types/Monitor/MonitorCriteriaInstance.test.ts +49 -0
  90. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +6 -6
  91. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.test.ts +4 -4
  92. package/Tests/Types/Monitor/Recommendation/MonitorRecommendationUtil.test.ts +4 -3
  93. package/Tests/Types/Telemetry/LlmMetricConventions.test.ts +193 -0
  94. package/Tests/UI/Components/AiInvestigationSettingsCard.test.tsx +0 -12
  95. package/Tests/UI/Components/CardModelDetailEdit.test.tsx +1 -17
  96. package/Tests/UI/Components/CodeEditor.test.tsx +474 -10
  97. package/Tests/UI/Components/Detail/YamlField.test.tsx +105 -0
  98. package/Tests/UI/Components/EntityDropdownLabelsBulkAdd.test.tsx +319 -0
  99. package/Tests/UI/Components/Forms/FormFieldYaml.test.tsx +231 -0
  100. package/Tests/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.test.ts +1 -0
  101. package/Tests/UI/Components/Forms/ValidationYAML.test.ts +234 -0
  102. package/Tests/UI/Components/ModelTable/AttributeColumns.test.tsx +573 -0
  103. package/Tests/UI/Components/ModelTable/BaseModelTableAttributeColumns.test.tsx +824 -0
  104. package/Tests/UI/Components/ModelTable/ColumnCustomizationModalAddableColumns.test.tsx +707 -0
  105. package/Tests/UI/Components/SideOver.test.tsx +1 -1
  106. package/Tests/UI/Components/StatusPage/ResourceGridSelection.test.tsx +294 -0
  107. package/Tests/UI/Components/StatusPage/ResourceListSelection.test.tsx +436 -0
  108. package/Tests/UI/Components/TableBulkCsvExport.test.tsx +6 -2
  109. package/Tests/UI/Components/Toggle.test.tsx +25 -0
  110. package/Tests/UI/Components/YamlEditor.test.tsx +847 -0
  111. package/Tests/Utils/NetworkDevice/DeviceReachabilityUtil.test.ts +252 -0
  112. package/Tests/Utils/NetworkDiscovery/DiscoveredHostUtil.test.ts +221 -0
  113. package/Tests/Utils/NetworkDiscovery/ScanNameUtil.test.ts +312 -0
  114. package/Tests/Utils/StatusPage/ResourceExplorer.test.ts +468 -0
  115. package/Tests/Utils/Telemetry/LlmMetricQuery.test.ts +472 -0
  116. package/Types/Code/YamlSyntax.ts +396 -0
  117. package/Types/Exception/ExceptionCode.ts +1 -0
  118. package/Types/Exception/PayloadTooLargeException.ts +8 -0
  119. package/Types/Marketing/Attribution.ts +10 -0
  120. package/Types/Marketing/MarketingEvent.ts +4 -0
  121. package/Types/Monitor/MonitorCriteriaInstance.ts +116 -56
  122. package/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.ts +2 -1
  123. package/Types/SecurityEvent/DetectionFindingConstants.ts +11 -0
  124. package/Types/Telemetry/LlmMetricConventions.ts +124 -0
  125. package/UI/Components/AutocompleteTextInput/AutocompleteTextInput.tsx +3 -0
  126. package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +10 -1
  127. package/UI/Components/CodeEditor/CodeEditor.tsx +241 -79
  128. package/UI/Components/CodeEditor/YamlEditor.tsx +369 -0
  129. package/UI/Components/Detail/Detail.tsx +10 -0
  130. package/UI/Components/EntityDropdown/EntityDropdown.tsx +33 -0
  131. package/UI/Components/Forms/Fields/FormField.tsx +31 -0
  132. package/UI/Components/Forms/Types/Field.ts +8 -0
  133. package/UI/Components/Forms/Types/FormFieldSchemaType.ts +1 -0
  134. package/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.ts +2 -0
  135. package/UI/Components/Forms/Validation.ts +46 -0
  136. package/UI/Components/Input/Input.tsx +3 -0
  137. package/UI/Components/ModelTable/AttributeColumns.tsx +321 -0
  138. package/UI/Components/ModelTable/BaseModelTable.tsx +263 -14
  139. package/UI/Components/ModelTable/Column.ts +8 -0
  140. package/UI/Components/ModelTable/ColumnCustomizationModal.tsx +310 -0
  141. package/UI/Components/ModelTable/ColumnPreference.ts +13 -1
  142. package/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.tsx +2 -2
  143. package/UI/Components/SideOver/SideOver.tsx +5 -5
  144. package/UI/Components/StatusPage/ResourceGrid.tsx +82 -2
  145. package/UI/Components/StatusPage/ResourceList.tsx +197 -38
  146. package/UI/Components/Toggle/Toggle.tsx +9 -1
  147. package/UI/Components/Types/FieldType.ts +1 -0
  148. package/Utils/NetworkDevice/DeviceReachabilityUtil.ts +73 -1
  149. package/Utils/NetworkDiscovery/ScanNameUtil.ts +181 -0
  150. package/Utils/StatusPage/ResourceExplorer.ts +258 -0
  151. package/Utils/Telemetry/LlmMetricQuery.ts +204 -0
  152. package/build/dist/Models/DatabaseModels/DetectionRule.js +57 -0
  153. package/build/dist/Models/DatabaseModels/DetectionRule.js.map +1 -1
  154. package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js +27 -7
  155. package/build/dist/Models/DatabaseModels/NetworkDeviceAutoImportRule.js.map +1 -1
  156. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +69 -0
  157. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  158. package/build/dist/Models/DatabaseModels/Project.js +76 -0
  159. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  160. package/build/dist/Models/DatabaseModels/User.js +76 -0
  161. package/build/dist/Models/DatabaseModels/User.js.map +1 -1
  162. package/build/dist/Server/API/TelemetryAPI.js +17 -0
  163. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  164. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js +21 -0
  165. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789400000000-AddNameToNetworkDeviceDiscoveryScan.js.map +1 -0
  166. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js +42 -0
  167. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789500000000-AddCampaignUtmFields.js.map +1 -0
  168. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js +22 -0
  169. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns.js.map +1 -0
  170. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
  171. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  172. package/build/dist/Server/Middleware/GzipRequestBody.js +224 -0
  173. package/build/dist/Server/Middleware/GzipRequestBody.js.map +1 -0
  174. package/build/dist/Server/Middleware/MultipartFormData.js +71 -2
  175. package/build/dist/Server/Middleware/MultipartFormData.js.map +1 -1
  176. package/build/dist/Server/Services/DetectionRuleService.js +49 -4
  177. package/build/dist/Server/Services/DetectionRuleService.js.map +1 -1
  178. package/build/dist/Server/Services/Index.js +204 -0
  179. package/build/dist/Server/Services/Index.js.map +1 -1
  180. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js +50 -0
  181. package/build/dist/Server/Services/NetworkDeviceDiscoveryScanService.js.map +1 -1
  182. package/build/dist/Server/Services/NetworkDeviceService.js +124 -0
  183. package/build/dist/Server/Services/NetworkDeviceService.js.map +1 -1
  184. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +68 -13
  185. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  186. package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js +1 -1
  187. package/build/dist/Server/Services/OnCallDutyPolicyFeedService.js.map +1 -1
  188. package/build/dist/Server/Services/ProjectService.js +25 -45
  189. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  190. package/build/dist/Server/Services/SecurityEventService.js +57 -5
  191. package/build/dist/Server/Services/SecurityEventService.js.map +1 -1
  192. package/build/dist/Server/Services/StatusPageResourceService.js +140 -0
  193. package/build/dist/Server/Services/StatusPageResourceService.js.map +1 -1
  194. package/build/dist/Server/Services/TelemetryAttributeService.js +17 -0
  195. package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
  196. package/build/dist/Server/Services/UserService.js +2 -12
  197. package/build/dist/Server/Services/UserService.js.map +1 -1
  198. package/build/dist/Server/Types/Markdown.js +147 -61
  199. package/build/dist/Server/Types/Markdown.js.map +1 -1
  200. package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js +12 -4
  201. package/build/dist/Server/Utils/AI/Toolbox/OnCallTools.js.map +1 -1
  202. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +12 -1
  203. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
  204. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +246 -9
  205. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  206. package/build/dist/Server/Utils/Attribution.js.map +1 -1
  207. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +27 -7
  208. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -1
  209. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js +42 -7
  210. package/build/dist/Server/Utils/SecurityEvent/DetectionRuleEvaluator.js.map +1 -1
  211. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js +18 -0
  212. package/build/dist/Server/Utils/SecurityEvent/Sigma/SigmaClickhouseCompiler.js.map +1 -1
  213. package/build/dist/Server/Utils/StartServer.js +12 -21
  214. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  215. package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js +76 -5
  216. package/build/dist/Server/Utils/Telemetry/LlmCostBudgetEvaluator.js.map +1 -1
  217. package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js +102 -0
  218. package/build/dist/Server/Utils/Telemetry/LlmMetricSpend.js.map +1 -0
  219. package/build/dist/Types/Code/YamlSyntax.js +212 -0
  220. package/build/dist/Types/Code/YamlSyntax.js.map +1 -0
  221. package/build/dist/Types/Exception/ExceptionCode.js +1 -0
  222. package/build/dist/Types/Exception/ExceptionCode.js.map +1 -1
  223. package/build/dist/Types/Exception/PayloadTooLargeException.js +8 -0
  224. package/build/dist/Types/Exception/PayloadTooLargeException.js.map +1 -0
  225. package/build/dist/Types/Marketing/Attribution.js +10 -0
  226. package/build/dist/Types/Marketing/Attribution.js.map +1 -1
  227. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +112 -51
  228. package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
  229. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js +2 -1
  230. package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationSeverityMapper.js.map +1 -1
  231. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js +9 -0
  232. package/build/dist/Types/SecurityEvent/DetectionFindingConstants.js.map +1 -1
  233. package/build/dist/Types/Telemetry/LlmMetricConventions.js +98 -0
  234. package/build/dist/Types/Telemetry/LlmMetricConventions.js.map +1 -0
  235. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js +1 -1
  236. package/build/dist/UI/Components/AutocompleteTextInput/AutocompleteTextInput.js.map +1 -1
  237. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +5 -2
  238. package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
  239. package/build/dist/UI/Components/CodeEditor/CodeEditor.js +206 -76
  240. package/build/dist/UI/Components/CodeEditor/CodeEditor.js.map +1 -1
  241. package/build/dist/UI/Components/CodeEditor/YamlEditor.js +201 -0
  242. package/build/dist/UI/Components/CodeEditor/YamlEditor.js.map +1 -0
  243. package/build/dist/UI/Components/Detail/Detail.js +9 -0
  244. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  245. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js +16 -1
  246. package/build/dist/UI/Components/EntityDropdown/EntityDropdown.js.map +1 -1
  247. package/build/dist/UI/Components/Forms/Fields/FormField.js +14 -1
  248. package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
  249. package/build/dist/UI/Components/Forms/Types/Field.js.map +1 -1
  250. package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js +1 -0
  251. package/build/dist/UI/Components/Forms/Types/FormFieldSchemaType.js.map +1 -1
  252. package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js +2 -0
  253. package/build/dist/UI/Components/Forms/Utils/FormFieldSchemaTypeUtil.js.map +1 -1
  254. package/build/dist/UI/Components/Forms/Validation.js +27 -0
  255. package/build/dist/UI/Components/Forms/Validation.js.map +1 -1
  256. package/build/dist/UI/Components/Input/Input.js +1 -1
  257. package/build/dist/UI/Components/Input/Input.js.map +1 -1
  258. package/build/dist/UI/Components/ModelTable/AttributeColumns.js +159 -0
  259. package/build/dist/UI/Components/ModelTable/AttributeColumns.js.map +1 -0
  260. package/build/dist/UI/Components/ModelTable/BaseModelTable.js +179 -17
  261. package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
  262. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js +133 -2
  263. package/build/dist/UI/Components/ModelTable/ColumnCustomizationModal.js.map +1 -1
  264. package/build/dist/UI/Components/ModelTable/ColumnPreference.js +7 -1
  265. package/build/dist/UI/Components/ModelTable/ColumnPreference.js.map +1 -1
  266. package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesModal.js +2 -2
  267. package/build/dist/UI/Components/SideOver/SideOver.js +5 -5
  268. package/build/dist/UI/Components/StatusPage/ResourceGrid.js +28 -2
  269. package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -1
  270. package/build/dist/UI/Components/StatusPage/ResourceList.js +87 -20
  271. package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -1
  272. package/build/dist/UI/Components/Toggle/Toggle.js +9 -1
  273. package/build/dist/UI/Components/Toggle/Toggle.js.map +1 -1
  274. package/build/dist/UI/Components/Types/FieldType.js +1 -0
  275. package/build/dist/UI/Components/Types/FieldType.js.map +1 -1
  276. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js +37 -1
  277. package/build/dist/Utils/NetworkDevice/DeviceReachabilityUtil.js.map +1 -1
  278. package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js +120 -0
  279. package/build/dist/Utils/NetworkDiscovery/ScanNameUtil.js.map +1 -0
  280. package/build/dist/Utils/StatusPage/ResourceExplorer.js +171 -0
  281. package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -1
  282. package/build/dist/Utils/Telemetry/LlmMetricQuery.js +119 -0
  283. package/build/dist/Utils/Telemetry/LlmMetricQuery.js.map +1 -0
  284. package/jest.config.json +2 -0
  285. package/package.json +1 -1
  286. package/tsconfig.json +2 -1
  287. package/Tests/UI/Components/Toast.test.tsx +0 -197
  288. package/Tests/UI/Components/ToastStacking.test.tsx +0 -198
  289. package/UI/Components/Toast/Toast.tsx +0 -189
  290. package/UI/Components/Toast/ToastInit.tsx +0 -110
  291. package/build/dist/UI/Components/Toast/Toast.js +0 -123
  292. package/build/dist/UI/Components/Toast/Toast.js.map +0 -1
  293. package/build/dist/UI/Components/Toast/ToastInit.js +0 -53
  294. package/build/dist/UI/Components/Toast/ToastInit.js.map +0 -1
@@ -0,0 +1,298 @@
1
+ import { describe, expect, jest, test } from "@jest/globals";
2
+ import timers from "timers";
3
+
4
+ /*
5
+ * multer calls `setImmediate` internally (make-middleware.js and
6
+ * remove-uploaded-files.js), and Common's jest environment is jsdom, which
7
+ * does not expose it. A `@jest-environment node` docblock is not an option
8
+ * here - Common's shared jest.setup.ts touches `window`, which is why
9
+ * EsbuildConfig.test.ts spawns a subprocess instead - so lend jsdom the
10
+ * real one from Node.
11
+ */
12
+ if (
13
+ typeof (globalThis as unknown as { setImmediate?: unknown }).setImmediate !==
14
+ "function"
15
+ ) {
16
+ (globalThis as unknown as { setImmediate: unknown }).setImmediate =
17
+ timers.setImmediate;
18
+ }
19
+
20
+ /*
21
+ * The limits under test are 25 and 50 MiB, so the fixtures that probe them
22
+ * are 25 and 50 MiB of real multipart body parsed by the real busboy.
23
+ * That does not fit in jest's 5s default.
24
+ */
25
+ jest.setTimeout(180_000);
26
+
27
+ /*
28
+ * The multipart reader used by Pyroscope ingest and the SendGrid inbound
29
+ * email webhook.
30
+ *
31
+ * Both routes mount it BEFORE their auth check - Pyroscope before
32
+ * isAuthorizedServiceMiddleware, SendGrid before the webhook secret is
33
+ * verified - and it uses multer's memoryStorage, so every part it accepts
34
+ * is buffered in the process on behalf of a caller who has not proved
35
+ * anything yet.
36
+ *
37
+ * multer's defaults leave almost all of that unbounded: fileSize, files,
38
+ * fields and parts are all Infinity out of the box, and only fieldSize
39
+ * has a default at all. These tests pin the limits and the status code a
40
+ * breach produces, because the failure mode of getting this wrong is
41
+ * silent - an unbounded upload looks exactly like a working one until the
42
+ * pod dies.
43
+ */
44
+
45
+ import MultipartFormDataMiddleware, {
46
+ MAX_MULTIPART_FIELDS,
47
+ MAX_MULTIPART_FIELD_BYTES,
48
+ MAX_MULTIPART_FILES,
49
+ MAX_MULTIPART_FILE_BYTES,
50
+ } from "../../../Server/Middleware/MultipartFormData";
51
+ import {
52
+ ExpressRequest,
53
+ ExpressResponse,
54
+ NextFunction,
55
+ } from "../../../Server/Utils/Express";
56
+ import Exception from "../../../Types/Exception/Exception";
57
+ import ExceptionCode from "../../../Types/Exception/ExceptionCode";
58
+ import { Readable } from "stream";
59
+
60
+ const BOUNDARY: string = "----oneuptimetestboundary";
61
+
62
+ interface MultipartPart {
63
+ name: string;
64
+ value: Buffer;
65
+ filename?: string;
66
+ }
67
+
68
+ function buildMultipartBody(parts: Array<MultipartPart>): Buffer {
69
+ const chunks: Array<Buffer> = [];
70
+
71
+ for (const part of parts) {
72
+ const disposition: string = part.filename
73
+ ? `form-data; name="${part.name}"; filename="${part.filename}"`
74
+ : `form-data; name="${part.name}"`;
75
+
76
+ const header: string =
77
+ `--${BOUNDARY}\r\n` +
78
+ `Content-Disposition: ${disposition}\r\n` +
79
+ (part.filename ? "Content-Type: application/octet-stream\r\n" : "") +
80
+ "\r\n";
81
+
82
+ chunks.push(Buffer.from(header));
83
+ chunks.push(part.value);
84
+ chunks.push(Buffer.from("\r\n"));
85
+ }
86
+
87
+ chunks.push(Buffer.from(`--${BOUNDARY}--\r\n`));
88
+
89
+ return Buffer.concat(chunks as unknown as Array<Uint8Array>);
90
+ }
91
+
92
+ interface Outcome {
93
+ req: ExpressRequest;
94
+ error: unknown;
95
+ nextCalls: number;
96
+ }
97
+
98
+ /*
99
+ * Drive the middleware with a real multipart body over a real Readable, so
100
+ * busboy does the parsing it does in production rather than being faked.
101
+ */
102
+ function run(parts: Array<MultipartPart>): Promise<Outcome> {
103
+ const body: Buffer = buildMultipartBody(parts);
104
+
105
+ const req: Readable & {
106
+ headers: Record<string, string>;
107
+ body?: unknown;
108
+ files?: unknown;
109
+ } = Readable.from([body]) as never;
110
+
111
+ req.headers = {
112
+ "content-type": `multipart/form-data; boundary=${BOUNDARY}`,
113
+ "content-length": String(body.length),
114
+ };
115
+
116
+ const res: ExpressResponse = {
117
+ status: (): unknown => {
118
+ return { send: (): void => {}, json: (): void => {} };
119
+ },
120
+ headersSent: false,
121
+ } as unknown as ExpressResponse;
122
+
123
+ return new Promise<Outcome>((resolve: (value: Outcome) => void): void => {
124
+ let nextCalls: number = 0;
125
+
126
+ MultipartFormDataMiddleware(req as unknown as ExpressRequest, res, ((
127
+ err?: unknown,
128
+ ): void => {
129
+ nextCalls++;
130
+ resolve({
131
+ req: req as unknown as ExpressRequest,
132
+ error: err,
133
+ nextCalls: nextCalls,
134
+ });
135
+ }) as NextFunction);
136
+ });
137
+ }
138
+
139
+ function files(outcome: Outcome): Array<{ fieldname: string; buffer: Buffer }> {
140
+ return (outcome.req.files || []) as Array<{
141
+ fieldname: string;
142
+ buffer: Buffer;
143
+ }>;
144
+ }
145
+
146
+ describe("MultipartFormData - the limits are set where they were reasoned about", () => {
147
+ test("sizes match the app-wide 50 MiB body-parser number", () => {
148
+ expect(MAX_MULTIPART_FILE_BYTES).toBe(50 * 1024 * 1024);
149
+ expect(MAX_MULTIPART_FIELD_BYTES).toBe(25 * 1024 * 1024);
150
+ });
151
+
152
+ test("counts are bounded at all, which is the part multer leaves at Infinity", () => {
153
+ expect(MAX_MULTIPART_FILES).toBe(50);
154
+ expect(MAX_MULTIPART_FIELDS).toBe(200);
155
+ expect(Number.isFinite(MAX_MULTIPART_FILES)).toBe(true);
156
+ expect(Number.isFinite(MAX_MULTIPART_FIELDS)).toBe(true);
157
+ });
158
+ });
159
+
160
+ describe("MultipartFormData - normal bodies still parse", () => {
161
+ test("a single file arrives as a Buffer on req.files", async () => {
162
+ const outcome: Outcome = await run([
163
+ { name: "profile", value: Buffer.from("pprof-bytes"), filename: "p.pb" },
164
+ ]);
165
+
166
+ expect(outcome.error).toBeUndefined();
167
+ expect(files(outcome)).toHaveLength(1);
168
+ expect(files(outcome)[0]!.fieldname).toBe("profile");
169
+ expect(files(outcome)[0]!.buffer.toString()).toBe("pprof-bytes");
170
+ });
171
+
172
+ test("fields and files together, the SendGrid shape", async () => {
173
+ const outcome: Outcome = await run([
174
+ { name: "from", value: Buffer.from("someone@example.com") },
175
+ { name: "subject", value: Buffer.from("hello") },
176
+ { name: "attachment1", value: Buffer.from("PDF"), filename: "a.pdf" },
177
+ ]);
178
+
179
+ expect(outcome.error).toBeUndefined();
180
+ expect((outcome.req.body as Record<string, string>)["from"]).toBe(
181
+ "someone@example.com",
182
+ );
183
+ expect(files(outcome)).toHaveLength(1);
184
+ });
185
+
186
+ test("a body with no parts at all is fine", async () => {
187
+ const outcome: Outcome = await run([]);
188
+
189
+ expect(outcome.error).toBeUndefined();
190
+ expect(files(outcome)).toHaveLength(0);
191
+ });
192
+
193
+ test("a file right at the count limit is accepted", async () => {
194
+ const outcome: Outcome = await run(
195
+ Array.from({ length: MAX_MULTIPART_FILES }, (_v: unknown, i: number) => {
196
+ return {
197
+ name: `f${i}`,
198
+ value: Buffer.from("x"),
199
+ filename: `f${i}.bin`,
200
+ };
201
+ }),
202
+ );
203
+
204
+ expect(outcome.error).toBeUndefined();
205
+ expect(files(outcome)).toHaveLength(MAX_MULTIPART_FILES);
206
+ });
207
+
208
+ test("fields right at the count limit are accepted", async () => {
209
+ const outcome: Outcome = await run(
210
+ Array.from({ length: MAX_MULTIPART_FIELDS }, (_v: unknown, i: number) => {
211
+ return { name: `k${i}`, value: Buffer.from("v") };
212
+ }),
213
+ );
214
+
215
+ expect(outcome.error).toBeUndefined();
216
+ expect(
217
+ Object.keys(outcome.req.body as Record<string, unknown>),
218
+ ).toHaveLength(MAX_MULTIPART_FIELDS);
219
+ });
220
+ });
221
+
222
+ describe("MultipartFormData - breaches answer 413, not 500", () => {
223
+ test("one file over the size limit", async () => {
224
+ const outcome: Outcome = await run([
225
+ {
226
+ name: "big",
227
+ value: Buffer.alloc(MAX_MULTIPART_FILE_BYTES + 1),
228
+ filename: "big.bin",
229
+ },
230
+ ]);
231
+
232
+ expect(outcome.error).toBeInstanceOf(Exception);
233
+ expect((outcome.error as Exception).code).toBe(
234
+ ExceptionCode.PayloadTooLargeException,
235
+ );
236
+ expect((outcome.error as Exception).code).toBe(413);
237
+ expect((outcome.error as Exception).message).toContain("LIMIT_FILE_SIZE");
238
+ });
239
+
240
+ test("one file too many", async () => {
241
+ const outcome: Outcome = await run(
242
+ Array.from(
243
+ { length: MAX_MULTIPART_FILES + 1 },
244
+ (_v: unknown, i: number) => {
245
+ return {
246
+ name: `f${i}`,
247
+ value: Buffer.from("x"),
248
+ filename: `f${i}.bin`,
249
+ };
250
+ },
251
+ ),
252
+ );
253
+
254
+ expect((outcome.error as Exception).code).toBe(
255
+ ExceptionCode.PayloadTooLargeException,
256
+ );
257
+ expect((outcome.error as Exception).message).toContain("LIMIT_FILE_COUNT");
258
+ });
259
+
260
+ test("one field too many - thousands of tiny parts is the cheap version of this attack", async () => {
261
+ const outcome: Outcome = await run(
262
+ Array.from(
263
+ { length: MAX_MULTIPART_FIELDS + 1 },
264
+ (_v: unknown, i: number) => {
265
+ return { name: `k${i}`, value: Buffer.from("v") };
266
+ },
267
+ ),
268
+ );
269
+
270
+ expect((outcome.error as Exception).code).toBe(
271
+ ExceptionCode.PayloadTooLargeException,
272
+ );
273
+ expect((outcome.error as Exception).message).toContain("LIMIT_FIELD_COUNT");
274
+ });
275
+
276
+ test("one field value over the size limit", async () => {
277
+ const outcome: Outcome = await run([
278
+ { name: "html", value: Buffer.alloc(MAX_MULTIPART_FIELD_BYTES + 1) },
279
+ ]);
280
+
281
+ expect((outcome.error as Exception).code).toBe(
282
+ ExceptionCode.PayloadTooLargeException,
283
+ );
284
+ expect((outcome.error as Exception).message).toContain("LIMIT_FIELD_VALUE");
285
+ });
286
+
287
+ test("next() is called exactly once on a breach", async () => {
288
+ const outcome: Outcome = await run([
289
+ {
290
+ name: "big",
291
+ value: Buffer.alloc(MAX_MULTIPART_FILE_BYTES + 1),
292
+ filename: "big.bin",
293
+ },
294
+ ]);
295
+
296
+ expect(outcome.nextCalls).toBe(1);
297
+ });
298
+ });
@@ -0,0 +1,311 @@
1
+ import { AddDetectionRuleDistinctCountColumns1789512000000 } from "../../../Server/Infrastructure/Postgres/SchemaMigrations/1789512000000-AddDetectionRuleDistinctCountColumns";
2
+ import SchemaMigrations from "../../../Server/Infrastructure/Postgres/SchemaMigrations/Index";
3
+ import DetectionRule from "../../../Models/DatabaseModels/DetectionRule";
4
+ import ColumnLength from "../../../Types/Database/ColumnLength";
5
+ import ColumnType from "../../../Types/Database/ColumnType";
6
+ import { TableColumnMetadata } from "../../../Types/Database/TableColumn";
7
+ import TableColumnType from "../../../Types/Database/TableColumnType";
8
+ import {
9
+ MigrationInterface,
10
+ QueryRunner,
11
+ getMetadataArgsStorage,
12
+ } from "typeorm";
13
+ import type { ColumnMetadataArgs } from "typeorm/metadata-args/ColumnMetadataArgs";
14
+ import fs from "fs";
15
+ import path from "path";
16
+ import { describe, expect, test } from "@jest/globals";
17
+
18
+ /*
19
+ * The migration behind detection-rule correlation semantics (issue #3398).
20
+ *
21
+ * DetectionRule gains distinctCountField (what a rule counts: unique
22
+ * values of a field instead of raw matching events) and
23
+ * matchCountThreshold (how many of that count a group needs in one
24
+ * evaluation window before the rule fires). Three things have to hold or
25
+ * a deployed instance breaks on upgrade:
26
+ *
27
+ * 1. up() adds exactly those two columns — distinctCountField nullable
28
+ * with NO default (NULL means "count raw events", the behavior every
29
+ * existing rule was saved with), matchCountThreshold NOT NULL
30
+ * DEFAULT '1' so every existing row keeps fire-on-any-match without
31
+ * a backfill;
32
+ * 2. down() removes exactly what up() added;
33
+ * 3. the migration is registered in SchemaMigrations/Index.ts — an
34
+ * unregistered migration never runs on boot, so the evaluator's
35
+ * SELECT of the two columns would 500 on every evaluation cycle.
36
+ *
37
+ * Pure SQL-contract assertions against a fake QueryRunner. No Postgres.
38
+ * The registry-wide ordering guard lives in
39
+ * SchemaMigrationsOrdering.test.ts and is not repeated here; what is
40
+ * asserted here is about THIS migration and stays true forever.
41
+ */
42
+
43
+ const MIGRATION_DIRECTORY: string = path.join(
44
+ __dirname,
45
+ "../../../Server/Infrastructure/Postgres/SchemaMigrations",
46
+ );
47
+
48
+ const MIGRATION_TIMESTAMP: string = "1789512000000";
49
+
50
+ const MIGRATION_NAME: string = `AddDetectionRuleDistinctCountColumns${MIGRATION_TIMESTAMP}`;
51
+
52
+ type MakeQueryRunnerResult = {
53
+ runner: QueryRunner;
54
+ statements: Array<string>;
55
+ };
56
+
57
+ type MakeQueryRunnerFunction = () => MakeQueryRunnerResult;
58
+
59
+ const makeQueryRunner: MakeQueryRunnerFunction = (): MakeQueryRunnerResult => {
60
+ const statements: Array<string> = [];
61
+
62
+ const query: (...args: Array<unknown>) => Promise<undefined> = (
63
+ ...args: Array<unknown>
64
+ ): Promise<undefined> => {
65
+ statements.push(String(args[0]));
66
+ return Promise.resolve(undefined);
67
+ };
68
+
69
+ return {
70
+ runner: { query } as unknown as QueryRunner,
71
+ statements,
72
+ };
73
+ };
74
+
75
+ describe("AddDetectionRuleDistinctCountColumns1789512000000 — SQL contract", () => {
76
+ test("up() adds exactly the two columns with their exact types and defaults", async () => {
77
+ const { runner, statements } = makeQueryRunner();
78
+
79
+ await new AddDetectionRuleDistinctCountColumns1789512000000().up(runner);
80
+
81
+ expect(statements).toEqual([
82
+ `ALTER TABLE "DetectionRule" ADD "distinctCountField" character varying(100)`,
83
+ `ALTER TABLE "DetectionRule" ADD "matchCountThreshold" integer NOT NULL DEFAULT '1'`,
84
+ ]);
85
+ });
86
+
87
+ /*
88
+ * NULL is a meaning on distinctCountField: it is what the evaluator
89
+ * reads as "threshold the raw match count", i.e. the semantics every
90
+ * rule saved before this migration was created with. A default would
91
+ * silently switch existing rules to distinct counting.
92
+ */
93
+ test("distinctCountField is nullable with no default", async () => {
94
+ const { runner, statements } = makeQueryRunner();
95
+
96
+ await new AddDetectionRuleDistinctCountColumns1789512000000().up(runner);
97
+
98
+ const addDistinctCountField: string = statements.find((sql: string) => {
99
+ return sql.includes(`ADD "distinctCountField"`);
100
+ })!;
101
+
102
+ expect(addDistinctCountField).not.toContain("NOT NULL");
103
+ expect(addDistinctCountField).not.toContain("DEFAULT");
104
+ });
105
+
106
+ /*
107
+ * The opposite for matchCountThreshold: the DB default IS the
108
+ * compatibility story. Every pre-existing row gets threshold 1 —
109
+ * fire-on-any-match — the instant the column appears, with no backfill
110
+ * statement to forget.
111
+ */
112
+ test("matchCountThreshold is NOT NULL with DEFAULT '1'", async () => {
113
+ const { runner, statements } = makeQueryRunner();
114
+
115
+ await new AddDetectionRuleDistinctCountColumns1789512000000().up(runner);
116
+
117
+ const addMatchCountThreshold: string = statements.find((sql: string) => {
118
+ return sql.includes(`ADD "matchCountThreshold"`);
119
+ })!;
120
+
121
+ expect(addMatchCountThreshold).toContain("integer NOT NULL DEFAULT '1'");
122
+ });
123
+
124
+ test("down() drops exactly what up() added, in reverse order", async () => {
125
+ const { runner, statements } = makeQueryRunner();
126
+
127
+ await new AddDetectionRuleDistinctCountColumns1789512000000().down(runner);
128
+
129
+ expect(statements).toEqual([
130
+ `ALTER TABLE "DetectionRule" DROP COLUMN "matchCountThreshold"`,
131
+ `ALTER TABLE "DetectionRule" DROP COLUMN "distinctCountField"`,
132
+ ]);
133
+ });
134
+
135
+ /*
136
+ * Not just "same count": the exact column names, derived from the two
137
+ * statement sets rather than restated, so adding a column to up()
138
+ * without adding it to down() fails here.
139
+ */
140
+ test("up() and down() name the same columns", async () => {
141
+ const { runner: upRunner, statements: upStatements } = makeQueryRunner();
142
+ const { runner: downRunner, statements: downStatements } =
143
+ makeQueryRunner();
144
+
145
+ await new AddDetectionRuleDistinctCountColumns1789512000000().up(upRunner);
146
+ await new AddDetectionRuleDistinctCountColumns1789512000000().down(
147
+ downRunner,
148
+ );
149
+
150
+ const columnsIn: (
151
+ statements: Array<string>,
152
+ keyword: string,
153
+ ) => Array<string> = (
154
+ statements: Array<string>,
155
+ keyword: string,
156
+ ): Array<string> => {
157
+ return statements
158
+ .filter((sql: string) => {
159
+ return sql.includes(keyword);
160
+ })
161
+ .map((sql: string) => {
162
+ return sql.split(`${keyword} "`)[1]!.split('"')[0]!;
163
+ })
164
+ .sort();
165
+ };
166
+
167
+ expect(columnsIn(downStatements, "DROP COLUMN")).toEqual(
168
+ columnsIn(upStatements, "ADD"),
169
+ );
170
+ });
171
+ });
172
+
173
+ describe("registration", () => {
174
+ /*
175
+ * The step AGENTS.md calls out and the one that fails silently: a
176
+ * migration that is not in this array never runs, so the columns are
177
+ * missing in a real deployment while the evaluator's rule SELECT and
178
+ * the dashboard form both reference them.
179
+ */
180
+ test("the migration is registered so it runs on boot", () => {
181
+ const names: Array<string> = SchemaMigrations.map(
182
+ (migration: new () => MigrationInterface): string => {
183
+ return migration.name;
184
+ },
185
+ );
186
+
187
+ expect(names).toContain(MIGRATION_NAME);
188
+ });
189
+
190
+ test("it is registered exactly once", () => {
191
+ const occurrences: number = SchemaMigrations.filter(
192
+ (migration: new () => MigrationInterface): boolean => {
193
+ return migration.name === MIGRATION_NAME;
194
+ },
195
+ ).length;
196
+
197
+ expect(occurrences).toBe(1);
198
+ });
199
+
200
+ /*
201
+ * TypeORM records applied migrations by the `name` property, not the
202
+ * file name. A mismatch re-runs the migration on every boot.
203
+ */
204
+ test("its declared name matches its class name", () => {
205
+ expect(new AddDetectionRuleDistinctCountColumns1789512000000().name).toBe(
206
+ MIGRATION_NAME,
207
+ );
208
+ expect(AddDetectionRuleDistinctCountColumns1789512000000.name).toBe(
209
+ MIGRATION_NAME,
210
+ );
211
+ });
212
+ });
213
+
214
+ describe("identity on disk", () => {
215
+ /*
216
+ * The timestamp in the class name is the one on disk. A class renamed
217
+ * without renaming its file — or two migrations landing on the same
218
+ * timestamp — makes the registered ordering describe something that is
219
+ * not what actually runs.
220
+ */
221
+ test("exactly one file on disk carries its timestamp, and it is this one", () => {
222
+ const matching: Array<string> = fs
223
+ .readdirSync(MIGRATION_DIRECTORY)
224
+ .filter((file: string): boolean => {
225
+ return file.startsWith(`${MIGRATION_TIMESTAMP}-`);
226
+ });
227
+
228
+ expect(matching).toEqual([
229
+ `${MIGRATION_TIMESTAMP}-AddDetectionRuleDistinctCountColumns.ts`,
230
+ ]);
231
+ });
232
+ });
233
+
234
+ /*
235
+ * The model side of the same contract. TypeORM generates the schema from
236
+ * these decorators, so a column that exists in the migration but not on
237
+ * the model (or vice versa, or with different nullability/defaults) is
238
+ * exactly the drift the Postgres Schema Drift workflow fails on.
239
+ */
240
+ describe("the DetectionRule model carries the columns the migration adds", () => {
241
+ const columnArgs: (propertyName: string) => ColumnMetadataArgs = (
242
+ propertyName: string,
243
+ ): ColumnMetadataArgs => {
244
+ const args: ColumnMetadataArgs | undefined = getMetadataArgsStorage()
245
+ .columns.filter((candidate: ColumnMetadataArgs) => {
246
+ return candidate.target === DetectionRule;
247
+ })
248
+ .find((candidate: ColumnMetadataArgs) => {
249
+ return candidate.propertyName === propertyName;
250
+ });
251
+
252
+ if (!args) {
253
+ throw new Error(
254
+ `DetectionRule.${propertyName} has no TypeORM @Column metadata`,
255
+ );
256
+ }
257
+
258
+ return args;
259
+ };
260
+
261
+ test("distinctCountField is a nullable ShortText varchar(100), matching the migration", () => {
262
+ const args: ColumnMetadataArgs = columnArgs("distinctCountField");
263
+
264
+ expect(args.options.type).toBe(ColumnType.ShortText);
265
+ expect(args.options.length).toBe(ColumnLength.ShortText);
266
+ // character varying(100) in the migration.
267
+ expect(ColumnLength.ShortText).toBe(100);
268
+ expect(args.options.nullable).toBe(true);
269
+ expect(args.options.default).toBeUndefined();
270
+ });
271
+
272
+ test("matchCountThreshold is a NOT NULL number defaulting to 1, matching the migration", () => {
273
+ const args: ColumnMetadataArgs = columnArgs("matchCountThreshold");
274
+
275
+ expect(args.options.type).toBe(ColumnType.Number);
276
+ expect(args.options.nullable).toBe(false);
277
+ expect(args.options.default).toBe(1);
278
+ });
279
+
280
+ test("distinctCountField's TableColumn is an optional ShortText", () => {
281
+ const metadata: TableColumnMetadata =
282
+ new DetectionRule().getTableColumnMetadata("distinctCountField");
283
+
284
+ expect(metadata.type).toBe(TableColumnType.ShortText);
285
+ expect(metadata.required).toBe(false);
286
+ });
287
+
288
+ /*
289
+ * isDefaultValueColumn is the API-side half of the DB default: without
290
+ * it, checkRequiredFields 400s every create payload written before the
291
+ * column existed and the DEFAULT '1' can never apply.
292
+ */
293
+ test("matchCountThreshold's TableColumn is required with default 1 as a default-value column", () => {
294
+ const metadata: TableColumnMetadata =
295
+ new DetectionRule().getTableColumnMetadata("matchCountThreshold");
296
+
297
+ expect(metadata.type).toBe(TableColumnType.Number);
298
+ expect(metadata.required).toBe(true);
299
+ expect(metadata.defaultValue).toBe(1);
300
+ expect(metadata.isDefaultValueColumn).toBe(true);
301
+ });
302
+
303
+ test("both columns default to undefined on a fresh model instance", () => {
304
+ const rule: DetectionRule = new DetectionRule();
305
+
306
+ expect("distinctCountField" in rule).toBe(true);
307
+ expect("matchCountThreshold" in rule).toBe(true);
308
+ expect(rule.distinctCountField).toBeUndefined();
309
+ expect(rule.matchCountThreshold).toBeUndefined();
310
+ });
311
+ });