@oneuptime/common 11.3.26 → 11.4.0

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 (298) hide show
  1. package/Models/DatabaseModels/AIRun.ts +50 -0
  2. package/Models/DatabaseModels/IncidentEpisodeRoleMember.ts +1 -1
  3. package/Models/DatabaseModels/IncidentMember.ts +1 -1
  4. package/Models/DatabaseModels/IncomingCallPolicy.ts +13 -0
  5. package/Models/DatabaseModels/Index.ts +3 -0
  6. package/Models/DatabaseModels/MigrationFailure.ts +170 -0
  7. package/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.ts +51 -2
  8. package/Models/DatabaseModels/OnCallDutyPolicySchedule.ts +44 -0
  9. package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.ts +6 -0
  10. package/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.ts +6 -0
  11. package/Models/DatabaseModels/OnCallDutyPolicyTimeLog.ts +1 -1
  12. package/Models/DatabaseModels/OnCallDutyPolicyUserOverride.ts +2 -2
  13. package/Models/DatabaseModels/Project.ts +60 -0
  14. package/Server/API/AIInvestigationAPI.ts +148 -0
  15. package/Server/API/AlertAPI.ts +1 -1
  16. package/Server/API/IncidentAPI.ts +2 -2
  17. package/Server/API/IncidentEpisodeAPI.ts +1 -1
  18. package/Server/API/LlmProviderAPI.ts +1 -0
  19. package/Server/API/ScheduledMaintenanceAPI.ts +1 -1
  20. package/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.ts +25 -0
  21. package/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.ts +81 -0
  22. package/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.ts +25 -0
  23. package/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.ts +35 -0
  24. package/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.ts +19 -0
  25. package/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.ts +33 -0
  26. package/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.ts +19 -0
  27. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +14 -0
  28. package/Server/Infrastructure/PostgresDatabase.ts +11 -0
  29. package/Server/Services/AIService.ts +74 -2
  30. package/Server/Services/AlertService.ts +25 -0
  31. package/Server/Services/AnalyticsDatabaseService.ts +10 -1
  32. package/Server/Services/DatabaseService.ts +10 -1
  33. package/Server/Services/IncidentService.ts +26 -1
  34. package/Server/Services/IncidentStateTimelineService.ts +19 -0
  35. package/Server/Services/IncomingCallPolicyEscalationRuleService.ts +75 -2
  36. package/Server/Services/IncomingCallPolicyService.ts +42 -1
  37. package/Server/Services/Index.ts +2 -0
  38. package/Server/Services/MigrationFailureService.ts +9 -0
  39. package/Server/Services/MonitorProbeService.ts +33 -7
  40. package/Server/Services/MonitorService.ts +19 -7
  41. package/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.ts +65 -0
  42. package/Server/Services/OnCallDutyPolicyEscalationRuleService.ts +185 -41
  43. package/Server/Services/OnCallDutyPolicyEscalationRuleUserService.ts +23 -15
  44. package/Server/Services/OnCallDutyPolicyExecutionLogService.ts +39 -0
  45. package/Server/Services/OnCallDutyPolicyScheduleLayerService.ts +148 -0
  46. package/Server/Services/OnCallDutyPolicyScheduleLayerUserService.ts +11 -2
  47. package/Server/Services/OnCallDutyPolicyScheduleService.ts +502 -26
  48. package/Server/Services/OnCallDutyPolicyTimeLogService.ts +49 -7
  49. package/Server/Services/OnCallDutyPolicyUserOverrideService.ts +218 -4
  50. package/Server/Services/ProjectCallSMSConfigService.ts +82 -1
  51. package/Server/Services/TeamMemberService.ts +6 -0
  52. package/Server/Services/UserNotificationRuleService.ts +13 -42
  53. package/Server/Services/UserOnCallLogService.ts +90 -0
  54. package/Server/Services/UserOnCallLogTimelineService.ts +43 -1
  55. package/Server/Types/Database/QueryUtil.ts +57 -0
  56. package/Server/Utils/AI/AlertAIContextBuilder.ts +6 -1
  57. package/Server/Utils/AI/Chat/ChatAgentRunner.ts +14 -2
  58. package/Server/Utils/AI/Chat/ObservabilityAssistant.ts +119 -11
  59. package/Server/Utils/AI/IncidentAIContextBuilder.ts +18 -3
  60. package/Server/Utils/AI/IncidentEpisodeAIContextBuilder.ts +6 -1
  61. package/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.ts +12 -2
  62. package/Server/Utils/AI/Sentinel/AlertInvestigationRunner.ts +141 -0
  63. package/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.ts +178 -0
  64. package/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.ts +105 -0
  65. package/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.ts +405 -0
  66. package/Server/Utils/AI/Sentinel/SentinelMemory.ts +167 -0
  67. package/Server/Utils/AI/Toolbox/IncidentTools.ts +183 -1
  68. package/Server/Utils/AI/Toolbox/Index.ts +15 -1
  69. package/Server/Utils/AI/Toolbox/RecentChangesTools.ts +208 -0
  70. package/Server/Utils/AI/Toolbox/SentinelActionTools.ts +577 -0
  71. package/Server/Utils/AI/Toolbox/Serializer.ts +42 -0
  72. package/Server/Utils/Database/MigrationFailureLog.ts +240 -0
  73. package/Server/Utils/IncomingCallPhoneNumber.ts +41 -0
  74. package/Server/Utils/LLM/LLMService.ts +110 -11
  75. package/Server/Utils/Telemetry/IoTSnapshotScan.ts +12 -9
  76. package/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.ts +12 -9
  77. package/Tests/Server/API/BaseAPI.test.ts +1 -0
  78. package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +0 -8
  79. package/Tests/Server/Services/OnCallDutyPolicyScheduleLayerUserReorder.test.ts +233 -0
  80. package/Tests/Server/Services/OnCallDutyPolicyTimeLogServiceScoping.test.ts +242 -0
  81. package/Tests/Server/Services/OnCallDutyPolicyUserOverrideEdit.test.ts +198 -0
  82. package/Tests/Server/Services/UserOnCallLogClaimNotificationRule.test.ts +495 -0
  83. package/Tests/Server/Types/Database/QueryUtil.test.ts +74 -0
  84. package/Tests/Server/Utils/AI/LLMServiceToolCalling.test.ts +78 -0
  85. package/Tests/Server/Utils/AI/ToolResultSerializer.test.ts +68 -0
  86. package/Tests/Types/DateExhaustiveTimezone.test.ts +700 -0
  87. package/Tests/Types/DateTimezoneWallClock.test.ts +99 -0
  88. package/Tests/Types/Events/Recurring.test.ts +22 -9
  89. package/Tests/Types/OnCallDutyPolicy/LayerUtilAuditFixes.test.ts +291 -0
  90. package/Tests/Types/OnCallDutyPolicy/LayerUtilAuditFixesRound2.test.ts +398 -0
  91. package/Tests/Types/OnCallDutyPolicy/LayerUtilDSTWeekendGap.test.ts +114 -0
  92. package/Tests/Types/OnCallDutyPolicy/LayerUtilDSTWeeklyDayShift.test.ts +76 -0
  93. package/Tests/Types/OnCallDutyPolicy/LayerUtilDailyMutation.test.ts +128 -0
  94. package/Tests/Types/OnCallDutyPolicy/LayerUtilDailyProbe.test.ts +203 -0
  95. package/Tests/Types/OnCallDutyPolicy/LayerUtilDiffFuzz.test.ts +322 -0
  96. package/Tests/Types/OnCallDutyPolicy/LayerUtilEdgeCases.test.ts +280 -0
  97. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveCurrentUser.test.ts +863 -0
  98. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveDaily.test.ts +900 -0
  99. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveMultiLayer.test.ts +1104 -0
  100. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveRotation.test.ts +957 -0
  101. package/Tests/Types/OnCallDutyPolicy/LayerUtilExhaustiveWeekly.test.ts +1095 -0
  102. package/Tests/Types/OnCallDutyPolicy/LayerUtilInvariants.test.ts +232 -0
  103. package/Tests/Types/OnCallDutyPolicy/LayerUtilMergeAudit.test.ts +227 -0
  104. package/Tests/Types/OnCallDutyPolicy/LayerUtilMonthYearAudit.test.ts +255 -0
  105. package/Tests/Types/OnCallDutyPolicy/LayerUtilMultiLayerAudit.test.ts +388 -0
  106. package/Tests/Types/OnCallDutyPolicy/LayerUtilMultiLayerEdge.test.ts +300 -0
  107. package/Tests/Types/OnCallDutyPolicy/LayerUtilOvernightDSTAudit.test.ts +158 -0
  108. package/Tests/Types/OnCallDutyPolicy/LayerUtilRestrictedGapFix.test.ts +253 -0
  109. package/Tests/Types/OnCallDutyPolicy/LayerUtilRestrictedGapRepro.test.ts +157 -0
  110. package/Tests/Types/OnCallDutyPolicy/LayerUtilRotationFixes.test.ts +414 -0
  111. package/Tests/Types/OnCallDutyPolicy/LayerUtilTimezone.test.ts +243 -0
  112. package/Tests/Types/OnCallDutyPolicy/LayerUtilWeekendGapRepro.test.ts +112 -0
  113. package/Tests/Types/OnCallDutyPolicy/OverrideLensAudit.test.ts +117 -0
  114. package/Tests/Types/OnCallDutyPolicy/RestrictionTimes.test.ts +8 -6
  115. package/Tests/Types/OnCallDutyPolicy/RestrictionTimesDefaultDayAudit.test.ts +112 -0
  116. package/Tests/Types/OnCallDutyPolicy/RestrictionTimesExhaustive.test.ts +821 -0
  117. package/Tests/Types/OnCallDutyPolicy/RestrictionTimesMutationInvariant.test.ts +886 -0
  118. package/Tests/Types/OnCallDutyPolicy/ScheduleShiftUtil.test.ts +260 -0
  119. package/Tests/Types/OnCallDutyPolicy/UserOverrideUtil.test.ts +255 -0
  120. package/Tests/Types/OnCallDutyPolicy/UserOverrideUtilExhaustive.test.ts +1126 -0
  121. package/Types/Date.ts +246 -15
  122. package/Types/OnCallDutyPolicy/Layer.ts +790 -260
  123. package/Types/OnCallDutyPolicy/RestrictionTimes.ts +39 -13
  124. package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +257 -0
  125. package/Types/OnCallDutyPolicy/UserOverrideUtil.ts +21 -5
  126. package/UI/Components/Calendar/Calendar.tsx +8 -2
  127. package/UI/Components/Charts/ChartGroup/ChartGroup.tsx +5 -5
  128. package/UI/Components/Events/RecurringFieldElement.tsx +16 -0
  129. package/UI/Components/ModelFormModal/ModelFormModal.tsx +8 -0
  130. package/UI/Components/RadioButtons/BasicRadioButtons.tsx +1 -1
  131. package/build/dist/Models/DatabaseModels/AIRun.js +52 -0
  132. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  133. package/build/dist/Models/DatabaseModels/IncidentEpisodeRoleMember.js +1 -1
  134. package/build/dist/Models/DatabaseModels/IncidentEpisodeRoleMember.js.map +1 -1
  135. package/build/dist/Models/DatabaseModels/IncidentMember.js +1 -1
  136. package/build/dist/Models/DatabaseModels/IncidentMember.js.map +1 -1
  137. package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js +10 -0
  138. package/build/dist/Models/DatabaseModels/IncomingCallPolicy.js.map +1 -1
  139. package/build/dist/Models/DatabaseModels/Index.js +2 -0
  140. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  141. package/build/dist/Models/DatabaseModels/MigrationFailure.js +196 -0
  142. package/build/dist/Models/DatabaseModels/MigrationFailure.js.map +1 -0
  143. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js +52 -2
  144. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyExecutionLog.js.map +1 -1
  145. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js +45 -0
  146. package/build/dist/Models/DatabaseModels/OnCallDutyPolicySchedule.js.map +1 -1
  147. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js +6 -0
  148. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayer.js.map +1 -1
  149. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.js +6 -0
  150. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyScheduleLayerUser.js.map +1 -1
  151. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js +1 -1
  152. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js.map +1 -1
  153. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js +2 -2
  154. package/build/dist/Models/DatabaseModels/OnCallDutyPolicyUserOverride.js.map +1 -1
  155. package/build/dist/Models/DatabaseModels/Project.js +62 -0
  156. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  157. package/build/dist/Server/API/AIInvestigationAPI.js +107 -0
  158. package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -0
  159. package/build/dist/Server/API/AlertAPI.js +1 -1
  160. package/build/dist/Server/API/IncidentAPI.js +2 -2
  161. package/build/dist/Server/API/IncidentEpisodeAPI.js +1 -1
  162. package/build/dist/Server/API/LlmProviderAPI.js +1 -0
  163. package/build/dist/Server/API/LlmProviderAPI.js.map +1 -1
  164. package/build/dist/Server/API/ScheduledMaintenanceAPI.js +1 -1
  165. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.js +18 -0
  166. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783470000000-AddIncomingCallPolicyRoutingPhoneNumberUnique.js.map +1 -0
  167. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.js +38 -0
  168. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783510935686-FixNotNullForeignKeysOnDelete.js.map +1 -0
  169. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.js +16 -0
  170. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783515836148-MigrationName.js.map +1 -0
  171. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.js +18 -0
  172. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783523076215-AddMigrationFailureTable.js.map +1 -0
  173. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.js +12 -0
  174. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783598145111-AddEnableAutomaticIncidentInvestigationToProject.js.map +1 -0
  175. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.js +18 -0
  176. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783615884106-AddInvestigationSubjectToAIRun.js.map +1 -0
  177. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.js +12 -0
  178. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1783632447975-AddEnableAutomaticAlertInvestigationToProject.js.map +1 -0
  179. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +14 -0
  180. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  181. package/build/dist/Server/Infrastructure/PostgresDatabase.js +9 -0
  182. package/build/dist/Server/Infrastructure/PostgresDatabase.js.map +1 -1
  183. package/build/dist/Server/Services/AIService.js +53 -2
  184. package/build/dist/Server/Services/AIService.js.map +1 -1
  185. package/build/dist/Server/Services/AlertService.js +24 -0
  186. package/build/dist/Server/Services/AlertService.js.map +1 -1
  187. package/build/dist/Server/Services/AnalyticsDatabaseService.js +10 -1
  188. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  189. package/build/dist/Server/Services/DatabaseService.js +10 -1
  190. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  191. package/build/dist/Server/Services/IncidentService.js +25 -1
  192. package/build/dist/Server/Services/IncidentService.js.map +1 -1
  193. package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
  194. package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
  195. package/build/dist/Server/Services/IncomingCallPolicyEscalationRuleService.js +55 -2
  196. package/build/dist/Server/Services/IncomingCallPolicyEscalationRuleService.js.map +1 -1
  197. package/build/dist/Server/Services/IncomingCallPolicyService.js +40 -0
  198. package/build/dist/Server/Services/IncomingCallPolicyService.js.map +1 -1
  199. package/build/dist/Server/Services/Index.js +2 -0
  200. package/build/dist/Server/Services/Index.js.map +1 -1
  201. package/build/dist/Server/Services/MigrationFailureService.js +9 -0
  202. package/build/dist/Server/Services/MigrationFailureService.js.map +1 -0
  203. package/build/dist/Server/Services/MonitorProbeService.js +33 -7
  204. package/build/dist/Server/Services/MonitorProbeService.js.map +1 -1
  205. package/build/dist/Server/Services/MonitorService.js +12 -5
  206. package/build/dist/Server/Services/MonitorService.js.map +1 -1
  207. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js +81 -22
  208. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleScheduleService.js.map +1 -1
  209. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js +134 -31
  210. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleService.js.map +1 -1
  211. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js +21 -13
  212. package/build/dist/Server/Services/OnCallDutyPolicyEscalationRuleUserService.js.map +1 -1
  213. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js +36 -0
  214. package/build/dist/Server/Services/OnCallDutyPolicyExecutionLogService.js.map +1 -1
  215. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js +121 -0
  216. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerService.js.map +1 -1
  217. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js +11 -2
  218. package/build/dist/Server/Services/OnCallDutyPolicyScheduleLayerUserService.js.map +1 -1
  219. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js +409 -36
  220. package/build/dist/Server/Services/OnCallDutyPolicyScheduleService.js.map +1 -1
  221. package/build/dist/Server/Services/OnCallDutyPolicyTimeLogService.js +52 -8
  222. package/build/dist/Server/Services/OnCallDutyPolicyTimeLogService.js.map +1 -1
  223. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js +170 -3
  224. package/build/dist/Server/Services/OnCallDutyPolicyUserOverrideService.js.map +1 -1
  225. package/build/dist/Server/Services/ProjectCallSMSConfigService.js +72 -0
  226. package/build/dist/Server/Services/ProjectCallSMSConfigService.js.map +1 -1
  227. package/build/dist/Server/Services/TeamMemberService.js +6 -0
  228. package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
  229. package/build/dist/Server/Services/UserNotificationRuleService.js +12 -30
  230. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  231. package/build/dist/Server/Services/UserOnCallLogService.js +75 -0
  232. package/build/dist/Server/Services/UserOnCallLogService.js.map +1 -1
  233. package/build/dist/Server/Services/UserOnCallLogTimelineService.js +31 -1
  234. package/build/dist/Server/Services/UserOnCallLogTimelineService.js.map +1 -1
  235. package/build/dist/Server/Types/Database/QueryUtil.js +45 -0
  236. package/build/dist/Server/Types/Database/QueryUtil.js.map +1 -1
  237. package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js +6 -1
  238. package/build/dist/Server/Utils/AI/AlertAIContextBuilder.js.map +1 -1
  239. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +13 -2
  240. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
  241. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js +59 -11
  242. package/build/dist/Server/Utils/AI/Chat/ObservabilityAssistant.js.map +1 -1
  243. package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js +18 -3
  244. package/build/dist/Server/Utils/AI/IncidentAIContextBuilder.js.map +1 -1
  245. package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js +6 -1
  246. package/build/dist/Server/Utils/AI/IncidentEpisodeAIContextBuilder.js.map +1 -1
  247. package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js +12 -2
  248. package/build/dist/Server/Utils/AI/ScheduledMaintenanceAIContextBuilder.js.map +1 -1
  249. package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js +119 -0
  250. package/build/dist/Server/Utils/AI/Sentinel/AlertInvestigationRunner.js.map +1 -0
  251. package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js +147 -0
  252. package/build/dist/Server/Utils/AI/Sentinel/IncidentInvestigationRunner.js.map +1 -0
  253. package/build/dist/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.js +93 -0
  254. package/build/dist/Server/Utils/AI/Sentinel/IncidentPostmortemRunner.js.map +1 -0
  255. package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js +311 -0
  256. package/build/dist/Server/Utils/AI/Sentinel/SentinelInvestigationEngine.js.map +1 -0
  257. package/build/dist/Server/Utils/AI/Sentinel/SentinelMemory.js +143 -0
  258. package/build/dist/Server/Utils/AI/Sentinel/SentinelMemory.js.map +1 -0
  259. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js +152 -1
  260. package/build/dist/Server/Utils/AI/Toolbox/IncidentTools.js.map +1 -1
  261. package/build/dist/Server/Utils/AI/Toolbox/Index.js +10 -1
  262. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
  263. package/build/dist/Server/Utils/AI/Toolbox/RecentChangesTools.js +165 -0
  264. package/build/dist/Server/Utils/AI/Toolbox/RecentChangesTools.js.map +1 -0
  265. package/build/dist/Server/Utils/AI/Toolbox/SentinelActionTools.js +451 -0
  266. package/build/dist/Server/Utils/AI/Toolbox/SentinelActionTools.js.map +1 -0
  267. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js +40 -0
  268. package/build/dist/Server/Utils/AI/Toolbox/Serializer.js.map +1 -1
  269. package/build/dist/Server/Utils/Database/MigrationFailureLog.js +174 -0
  270. package/build/dist/Server/Utils/Database/MigrationFailureLog.js.map +1 -0
  271. package/build/dist/Server/Utils/IncomingCallPhoneNumber.js +30 -0
  272. package/build/dist/Server/Utils/IncomingCallPhoneNumber.js.map +1 -0
  273. package/build/dist/Server/Utils/LLM/LLMService.js +81 -5
  274. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  275. package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js +11 -8
  276. package/build/dist/Server/Utils/Telemetry/IoTSnapshotScan.js.map +1 -1
  277. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js +11 -8
  278. package/build/dist/Server/Utils/Telemetry/ProxmoxCephSnapshotScan.js.map +1 -1
  279. package/build/dist/Types/Date.js +185 -15
  280. package/build/dist/Types/Date.js.map +1 -1
  281. package/build/dist/Types/OnCallDutyPolicy/Layer.js +602 -171
  282. package/build/dist/Types/OnCallDutyPolicy/Layer.js.map +1 -1
  283. package/build/dist/Types/OnCallDutyPolicy/RestrictionTimes.js +27 -13
  284. package/build/dist/Types/OnCallDutyPolicy/RestrictionTimes.js.map +1 -1
  285. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +159 -0
  286. package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -0
  287. package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js +20 -5
  288. package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js.map +1 -1
  289. package/build/dist/UI/Components/Calendar/Calendar.js +2 -2
  290. package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
  291. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js +2 -2
  292. package/build/dist/UI/Components/Charts/ChartGroup/ChartGroup.js.map +1 -1
  293. package/build/dist/UI/Components/Events/RecurringFieldElement.js +13 -0
  294. package/build/dist/UI/Components/Events/RecurringFieldElement.js.map +1 -1
  295. package/build/dist/UI/Components/ModelFormModal/ModelFormModal.js.map +1 -1
  296. package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js +1 -1
  297. package/build/dist/UI/Components/RadioButtons/BasicRadioButtons.js.map +1 -1
  298. package/package.json +1 -1
@@ -0,0 +1,821 @@
1
+ /**
2
+ * EXHAUSTIVE suite for the RestrictionTimes type.
3
+ *
4
+ * Covers: getDefault / getDefaultRestrictonTimeData (+ per-call independence),
5
+ * getDefaultWeeklyRestrictionTIme, addDefaultDailyRestriction /
6
+ * addDefaultWeeklyRestriction / removeAllRestrictions state machine,
7
+ * toJSON structural correctness (DateTime wrappers, order preservation),
8
+ * toJSON/fromJSON round-trips for None / Daily / Weekly incl. MULTIPLE weekly
9
+ * windows (raw round-trip vs the realistic deserialize-at-the-model-boundary
10
+ * round-trip that restores real Date instances), the F20 fix (absent
11
+ * weeklyRestrictionTimes yields an ARRAY, never {}, and iterating it never
12
+ * throws), fromJSON error handling, and toDatabase/fromDatabase.
13
+ *
14
+ * These lock in the CURRENT (fixed) behavior. A couple of residual quirks are
15
+ * asserted as "current behavior" and reported separately.
16
+ */
17
+ import OneUptimeDate from "../../../Types/Date";
18
+ import DayOfWeek from "../../../Types/Day/DayOfWeek";
19
+ import BadDataException from "../../../Types/Exception/BadDataException";
20
+ import { JSONObject, ObjectType } from "../../../Types/JSON";
21
+ import JSONFunctions from "../../../Types/JSONFunctions";
22
+ import RestrictionTimes, {
23
+ RestrictionTimesData,
24
+ RestrictionType,
25
+ WeeklyResctriction,
26
+ } from "../../../Types/OnCallDutyPolicy/RestrictionTimes";
27
+ import StartAndEndTime from "../../../Types/Time/StartAndEndTime";
28
+
29
+ // ---- helpers ---------------------------------------------------------------
30
+
31
+ function valueOf(json: JSONObject): JSONObject {
32
+ return json["value"] as JSONObject;
33
+ }
34
+
35
+ // A raw round-trip: toJSON then straight back through fromJSON (no deserialize).
36
+ function roundTripRaw(r: RestrictionTimes): RestrictionTimes {
37
+ return RestrictionTimes.fromJSON(r.toJSON());
38
+ }
39
+
40
+ /*
41
+ * The realistic model-boundary round-trip: serialize -> persist as pure JSON
42
+ * (JSONB) -> deserialize (restores Date instances from the DateTime wrappers)
43
+ * -> fromJSON. This is what actually happens loading a layer from the database.
44
+ */
45
+ function roundTripThroughDb(r: RestrictionTimes): RestrictionTimes {
46
+ const persisted: JSONObject = JSON.parse(
47
+ JSON.stringify(r.toJSON()),
48
+ ) as JSONObject;
49
+ const deserialized: JSONObject = JSONFunctions.deserialize(persisted);
50
+ return RestrictionTimes.fromJSON(deserialized);
51
+ }
52
+
53
+ function weeklyWindow(data: {
54
+ startDay: DayOfWeek;
55
+ endDay: DayOfWeek;
56
+ startIso: string;
57
+ endIso: string;
58
+ }): WeeklyResctriction {
59
+ return {
60
+ startDay: data.startDay,
61
+ endDay: data.endDay,
62
+ startTime: OneUptimeDate.fromString(data.startIso),
63
+ endTime: OneUptimeDate.fromString(data.endIso),
64
+ };
65
+ }
66
+
67
+ // A wrapper object as produced by JSONFunctions.serialize for a Date value.
68
+ function expectDateTimeWrapper(v: unknown): void {
69
+ const wrapper: JSONObject = v as JSONObject;
70
+ expect(wrapper["_type"]).toBe(ObjectType.DateTime);
71
+ expect(typeof wrapper["value"]).toBe("string");
72
+ }
73
+
74
+ // Typed accessor for the protected static (de)serialization overrides.
75
+ type DbAccessor = {
76
+ toDatabase(value: unknown): JSONObject | null;
77
+ fromDatabase(value: JSONObject | null): RestrictionTimes | null;
78
+ };
79
+ const dbAccessor: DbAccessor = RestrictionTimes as unknown as DbAccessor;
80
+
81
+ // ---- tests -----------------------------------------------------------------
82
+
83
+ describe("RestrictionTimes (exhaustive)", () => {
84
+ describe("getDefaultRestrictonTimeData", () => {
85
+ test("shape is None / null / empty-array", () => {
86
+ const data: RestrictionTimesData =
87
+ RestrictionTimes.getDefaultRestrictonTimeData();
88
+
89
+ expect(data.restictionType).toBe(RestrictionType.None);
90
+ expect(data.dayRestrictionTimes).toBeNull();
91
+ expect(Array.isArray(data.weeklyRestrictionTimes)).toBe(true);
92
+ expect(data.weeklyRestrictionTimes).toEqual([]);
93
+ });
94
+
95
+ test("returns an independent object graph on every call", () => {
96
+ const a: RestrictionTimesData =
97
+ RestrictionTimes.getDefaultRestrictonTimeData();
98
+ const b: RestrictionTimesData =
99
+ RestrictionTimes.getDefaultRestrictonTimeData();
100
+
101
+ expect(a).not.toBe(b);
102
+ expect(a.weeklyRestrictionTimes).not.toBe(b.weeklyRestrictionTimes);
103
+
104
+ a.weeklyRestrictionTimes.push(
105
+ RestrictionTimes.getDefaultWeeklyRestrictionTIme(),
106
+ );
107
+ a.restictionType = RestrictionType.Daily;
108
+
109
+ expect(b.weeklyRestrictionTimes).toEqual([]);
110
+ expect(b.restictionType).toBe(RestrictionType.None);
111
+ });
112
+ });
113
+
114
+ describe("getDefault / constructor", () => {
115
+ test("getDefault yields an unrestricted instance", () => {
116
+ const r: RestrictionTimes = RestrictionTimes.getDefault();
117
+
118
+ expect(r).toBeInstanceOf(RestrictionTimes);
119
+ expect(r.restictionType).toBe(RestrictionType.None);
120
+ expect(r.dayRestrictionTimes).toBeNull();
121
+ expect(r.weeklyRestrictionTimes).toEqual([]);
122
+ });
123
+
124
+ test("new RestrictionTimes() matches getDefault state", () => {
125
+ const ctor: RestrictionTimes = new RestrictionTimes();
126
+ const def: RestrictionTimes = RestrictionTimes.getDefault();
127
+
128
+ expect(ctor.restictionType).toBe(def.restictionType);
129
+ expect(ctor.dayRestrictionTimes).toBe(def.dayRestrictionTimes);
130
+ expect(ctor.weeklyRestrictionTimes).toEqual(def.weeklyRestrictionTimes);
131
+ });
132
+
133
+ test("each instance owns independent internal data", () => {
134
+ const a: RestrictionTimes = RestrictionTimes.getDefault();
135
+ const b: RestrictionTimes = RestrictionTimes.getDefault();
136
+
137
+ expect(a).not.toBe(b);
138
+
139
+ a.addDefaultWeeklyRestriction();
140
+
141
+ expect(a.restictionType).toBe(RestrictionType.Weekly);
142
+ expect(a.weeklyRestrictionTimes.length).toBe(1);
143
+ // b must be untouched.
144
+ expect(b.restictionType).toBe(RestrictionType.None);
145
+ expect(b.weeklyRestrictionTimes).toEqual([]);
146
+ });
147
+
148
+ test("mutating the array returned by the getter is isolated per instance", () => {
149
+ const a: RestrictionTimes = RestrictionTimes.getDefault();
150
+ const b: RestrictionTimes = RestrictionTimes.getDefault();
151
+
152
+ a.weeklyRestrictionTimes.push(
153
+ RestrictionTimes.getDefaultWeeklyRestrictionTIme(),
154
+ );
155
+
156
+ expect(a.weeklyRestrictionTimes.length).toBe(1);
157
+ expect(b.weeklyRestrictionTimes.length).toBe(0);
158
+ });
159
+ });
160
+
161
+ describe("getDefaultWeeklyRestrictionTIme", () => {
162
+ test("spans Sunday -> Monday with 00:00:00 -> 01:00:00 local times", () => {
163
+ const w: WeeklyResctriction =
164
+ RestrictionTimes.getDefaultWeeklyRestrictionTIme();
165
+
166
+ expect(w.startDay).toBe(DayOfWeek.Sunday);
167
+ expect(w.endDay).toBe(DayOfWeek.Monday);
168
+ expect(w.startTime).toBeInstanceOf(Date);
169
+ expect(w.endTime).toBeInstanceOf(Date);
170
+
171
+ // Hours are set in LOCAL time and are therefore TZ-independent getters.
172
+ expect(w.startTime.getHours()).toBe(0);
173
+ expect(w.startTime.getMinutes()).toBe(0);
174
+ expect(w.startTime.getSeconds()).toBe(0);
175
+ expect(w.endTime.getHours()).toBe(1);
176
+ expect(w.endTime.getMinutes()).toBe(0);
177
+ expect(w.endTime.getSeconds()).toBe(0);
178
+ });
179
+
180
+ test("returns a fresh window (and fresh Date objects) each call", () => {
181
+ const a: WeeklyResctriction =
182
+ RestrictionTimes.getDefaultWeeklyRestrictionTIme();
183
+ const b: WeeklyResctriction =
184
+ RestrictionTimes.getDefaultWeeklyRestrictionTIme();
185
+
186
+ expect(a).not.toBe(b);
187
+ expect(a.startTime).not.toBe(b.startTime);
188
+ expect(a.endTime).not.toBe(b.endTime);
189
+ });
190
+ });
191
+
192
+ describe("addDefaultDailyRestriction", () => {
193
+ test("switches to Daily with 00:00 -> 01:00 day times and empty weekly", () => {
194
+ const r: RestrictionTimes = new RestrictionTimes();
195
+ r.addDefaultDailyRestriction();
196
+
197
+ expect(r.restictionType).toBe(RestrictionType.Daily);
198
+ expect(r.dayRestrictionTimes).not.toBeNull();
199
+ const day: StartAndEndTime = r.dayRestrictionTimes as StartAndEndTime;
200
+ expect(day.startTime).toBeInstanceOf(Date);
201
+ expect(day.endTime).toBeInstanceOf(Date);
202
+ expect((day.startTime as Date).getHours()).toBe(0);
203
+ expect((day.startTime as Date).getMinutes()).toBe(0);
204
+ expect((day.startTime as Date).getSeconds()).toBe(0);
205
+ expect((day.endTime as Date).getHours()).toBe(1);
206
+ expect(r.weeklyRestrictionTimes).toEqual([]);
207
+ });
208
+
209
+ test("clears a pre-existing weekly configuration", () => {
210
+ const r: RestrictionTimes = new RestrictionTimes();
211
+ r.addDefaultWeeklyRestriction();
212
+ expect(r.weeklyRestrictionTimes.length).toBe(1);
213
+
214
+ r.addDefaultDailyRestriction();
215
+
216
+ expect(r.restictionType).toBe(RestrictionType.Daily);
217
+ expect(r.weeklyRestrictionTimes).toEqual([]);
218
+ expect(r.dayRestrictionTimes).not.toBeNull();
219
+ });
220
+
221
+ test("is idempotent and produces fresh Date objects on repeat calls", () => {
222
+ const r: RestrictionTimes = new RestrictionTimes();
223
+ r.addDefaultDailyRestriction();
224
+ const first: StartAndEndTime = r.dayRestrictionTimes as StartAndEndTime;
225
+
226
+ r.addDefaultDailyRestriction();
227
+ const second: StartAndEndTime = r.dayRestrictionTimes as StartAndEndTime;
228
+
229
+ expect(r.restictionType).toBe(RestrictionType.Daily);
230
+ expect(second).not.toBe(first);
231
+ expect((second.startTime as Date).getHours()).toBe(0);
232
+ expect((second.endTime as Date).getHours()).toBe(1);
233
+ });
234
+ });
235
+
236
+ describe("addDefaultWeeklyRestriction", () => {
237
+ test("switches to Weekly with exactly one Sunday->Monday window, no day time", () => {
238
+ const r: RestrictionTimes = new RestrictionTimes();
239
+ r.addDefaultWeeklyRestriction();
240
+
241
+ expect(r.restictionType).toBe(RestrictionType.Weekly);
242
+ expect(r.dayRestrictionTimes).toBeNull();
243
+ expect(r.weeklyRestrictionTimes.length).toBe(1);
244
+ const w: WeeklyResctriction = r.weeklyRestrictionTimes[0]!;
245
+ expect(w.startDay).toBe(DayOfWeek.Sunday);
246
+ expect(w.endDay).toBe(DayOfWeek.Monday);
247
+ expect(w.startTime).toBeInstanceOf(Date);
248
+ expect(w.endTime).toBeInstanceOf(Date);
249
+ });
250
+
251
+ test("clears a pre-existing daily configuration", () => {
252
+ const r: RestrictionTimes = new RestrictionTimes();
253
+ r.addDefaultDailyRestriction();
254
+ expect(r.dayRestrictionTimes).not.toBeNull();
255
+
256
+ r.addDefaultWeeklyRestriction();
257
+
258
+ expect(r.restictionType).toBe(RestrictionType.Weekly);
259
+ expect(r.dayRestrictionTimes).toBeNull();
260
+ expect(r.weeklyRestrictionTimes.length).toBe(1);
261
+ });
262
+
263
+ test("repeated calls replace rather than append (always exactly one window)", () => {
264
+ const r: RestrictionTimes = new RestrictionTimes();
265
+ r.addDefaultWeeklyRestriction();
266
+ r.addDefaultWeeklyRestriction();
267
+ r.addDefaultWeeklyRestriction();
268
+
269
+ expect(r.weeklyRestrictionTimes.length).toBe(1);
270
+ });
271
+ });
272
+
273
+ describe("removeAllRestrictions", () => {
274
+ test("resets a Weekly instance to None", () => {
275
+ const r: RestrictionTimes = new RestrictionTimes();
276
+ r.addDefaultWeeklyRestriction();
277
+
278
+ r.removeAllRestrictions();
279
+
280
+ expect(r.restictionType).toBe(RestrictionType.None);
281
+ expect(r.dayRestrictionTimes).toBeNull();
282
+ expect(r.weeklyRestrictionTimes).toEqual([]);
283
+ });
284
+
285
+ test("resets a Daily instance to None", () => {
286
+ const r: RestrictionTimes = new RestrictionTimes();
287
+ r.addDefaultDailyRestriction();
288
+
289
+ r.removeAllRestrictions();
290
+
291
+ expect(r.restictionType).toBe(RestrictionType.None);
292
+ expect(r.dayRestrictionTimes).toBeNull();
293
+ expect(r.weeklyRestrictionTimes).toEqual([]);
294
+ });
295
+
296
+ test("is a no-op on an already-unrestricted instance", () => {
297
+ const r: RestrictionTimes = new RestrictionTimes();
298
+
299
+ r.removeAllRestrictions();
300
+
301
+ expect(r.restictionType).toBe(RestrictionType.None);
302
+ expect(r.dayRestrictionTimes).toBeNull();
303
+ expect(r.weeklyRestrictionTimes).toEqual([]);
304
+ });
305
+
306
+ test("clears a manually configured multi-window weekly list", () => {
307
+ const r: RestrictionTimes = new RestrictionTimes();
308
+ r.restictionType = RestrictionType.Weekly;
309
+ r.weeklyRestrictionTimes = [
310
+ weeklyWindow({
311
+ startDay: DayOfWeek.Monday,
312
+ endDay: DayOfWeek.Tuesday,
313
+ startIso: "2025-01-06T09:00:00.000Z",
314
+ endIso: "2025-01-06T17:00:00.000Z",
315
+ }),
316
+ weeklyWindow({
317
+ startDay: DayOfWeek.Saturday,
318
+ endDay: DayOfWeek.Sunday,
319
+ startIso: "2025-01-11T00:00:00.000Z",
320
+ endIso: "2025-01-12T00:00:00.000Z",
321
+ }),
322
+ ];
323
+
324
+ r.removeAllRestrictions();
325
+
326
+ expect(r.restictionType).toBe(RestrictionType.None);
327
+ expect(r.weeklyRestrictionTimes).toEqual([]);
328
+ });
329
+ });
330
+
331
+ describe("toJSON structural correctness", () => {
332
+ test("None serializes with null day time and empty weekly array", () => {
333
+ const json: JSONObject = new RestrictionTimes().toJSON();
334
+
335
+ expect(json["_type"]).toBe(ObjectType.RestrictionTimes);
336
+ const v: JSONObject = valueOf(json);
337
+ expect(v["restictionType"]).toBe(RestrictionType.None);
338
+ expect(v["dayRestrictionTimes"]).toBeNull();
339
+ expect(v["weeklyRestrictionTimes"]).toEqual([]);
340
+ });
341
+
342
+ test("Daily serializes day times as DateTime wrappers", () => {
343
+ const r: RestrictionTimes = new RestrictionTimes();
344
+ r.addDefaultDailyRestriction();
345
+
346
+ const v: JSONObject = valueOf(r.toJSON());
347
+ expect(v["restictionType"]).toBe(RestrictionType.Daily);
348
+ const day: JSONObject = v["dayRestrictionTimes"] as JSONObject;
349
+ expectDateTimeWrapper(day["startTime"]);
350
+ expectDateTimeWrapper(day["endTime"]);
351
+ expect(v["weeklyRestrictionTimes"]).toEqual([]);
352
+ });
353
+
354
+ test("Weekly serializes each window's days as strings and times as wrappers", () => {
355
+ const r: RestrictionTimes = new RestrictionTimes();
356
+ r.addDefaultWeeklyRestriction();
357
+
358
+ const v: JSONObject = valueOf(r.toJSON());
359
+ expect(v["restictionType"]).toBe(RestrictionType.Weekly);
360
+ expect(v["dayRestrictionTimes"]).toBeNull();
361
+ const list: Array<JSONObject> = v[
362
+ "weeklyRestrictionTimes"
363
+ ] as Array<JSONObject>;
364
+ expect(list.length).toBe(1);
365
+ expect(list[0]!["startDay"]).toBe(DayOfWeek.Sunday);
366
+ expect(list[0]!["endDay"]).toBe(DayOfWeek.Monday);
367
+ expectDateTimeWrapper(list[0]!["startTime"]);
368
+ expectDateTimeWrapper(list[0]!["endTime"]);
369
+ });
370
+
371
+ test("multiple weekly windows preserve count, order and day labels", () => {
372
+ const r: RestrictionTimes = new RestrictionTimes();
373
+ r.restictionType = RestrictionType.Weekly;
374
+ r.weeklyRestrictionTimes = [
375
+ weeklyWindow({
376
+ startDay: DayOfWeek.Monday,
377
+ endDay: DayOfWeek.Wednesday,
378
+ startIso: "2025-01-06T09:00:00.000Z",
379
+ endIso: "2025-01-08T17:00:00.000Z",
380
+ }),
381
+ weeklyWindow({
382
+ startDay: DayOfWeek.Thursday,
383
+ endDay: DayOfWeek.Friday,
384
+ startIso: "2025-01-09T00:00:00.000Z",
385
+ endIso: "2025-01-10T12:30:45.000Z",
386
+ }),
387
+ weeklyWindow({
388
+ startDay: DayOfWeek.Saturday,
389
+ endDay: DayOfWeek.Sunday,
390
+ startIso: "2025-01-11T00:00:00.000Z",
391
+ endIso: "2025-01-12T23:59:59.000Z",
392
+ }),
393
+ ];
394
+
395
+ const list: Array<JSONObject> = valueOf(r.toJSON())[
396
+ "weeklyRestrictionTimes"
397
+ ] as Array<JSONObject>;
398
+
399
+ expect(list.length).toBe(3);
400
+ expect(
401
+ list.map((w: JSONObject) => {
402
+ return w["startDay"];
403
+ }),
404
+ ).toEqual([DayOfWeek.Monday, DayOfWeek.Thursday, DayOfWeek.Saturday]);
405
+ expect(
406
+ list.map((w: JSONObject) => {
407
+ return w["endDay"];
408
+ }),
409
+ ).toEqual([DayOfWeek.Wednesday, DayOfWeek.Friday, DayOfWeek.Sunday]);
410
+ list.forEach((w: JSONObject) => {
411
+ expectDateTimeWrapper(w["startTime"]);
412
+ expectDateTimeWrapper(w["endTime"]);
413
+ });
414
+ });
415
+
416
+ test("toJSON output is pure JSON (survives JSON.stringify round-trip)", () => {
417
+ const r: RestrictionTimes = new RestrictionTimes();
418
+ r.addDefaultDailyRestriction();
419
+ const json: JSONObject = r.toJSON();
420
+
421
+ expect(() => {
422
+ JSON.parse(JSON.stringify(json));
423
+ }).not.toThrow();
424
+ expect(JSON.parse(JSON.stringify(json))).toEqual(json);
425
+ });
426
+
427
+ test("toJSON does not mutate the instance", () => {
428
+ const r: RestrictionTimes = new RestrictionTimes();
429
+ r.addDefaultWeeklyRestriction();
430
+ const before: WeeklyResctriction = r.weeklyRestrictionTimes[0]!;
431
+
432
+ r.toJSON();
433
+
434
+ expect(r.restictionType).toBe(RestrictionType.Weekly);
435
+ expect(r.weeklyRestrictionTimes[0]).toBe(before);
436
+ expect(r.weeklyRestrictionTimes[0]!.startTime).toBeInstanceOf(Date);
437
+ });
438
+ });
439
+
440
+ describe("fromJSON: identity and passthrough", () => {
441
+ test("returns the same instance when handed a RestrictionTimes", () => {
442
+ const r: RestrictionTimes = new RestrictionTimes();
443
+ expect(RestrictionTimes.fromJSON(r)).toBe(r);
444
+ });
445
+
446
+ test("reads restriction type / day times / weekly list from value", () => {
447
+ const restored: RestrictionTimes = RestrictionTimes.fromJSON({
448
+ _type: ObjectType.RestrictionTimes,
449
+ value: {
450
+ restictionType: RestrictionType.Weekly,
451
+ dayRestrictionTimes: null,
452
+ weeklyRestrictionTimes: [],
453
+ },
454
+ });
455
+ expect(restored).toBeInstanceOf(RestrictionTimes);
456
+ expect(restored.restictionType).toBe(RestrictionType.Weekly);
457
+ expect(restored.dayRestrictionTimes).toBeNull();
458
+ expect(restored.weeklyRestrictionTimes).toEqual([]);
459
+ });
460
+ });
461
+
462
+ describe("fromJSON: F20 weeklyRestrictionTimes normalization", () => {
463
+ test("absent weeklyRestrictionTimes yields an ARRAY, never {}", () => {
464
+ const restored: RestrictionTimes = RestrictionTimes.fromJSON({
465
+ _type: ObjectType.RestrictionTimes,
466
+ value: {
467
+ restictionType: RestrictionType.None,
468
+ dayRestrictionTimes: null,
469
+ },
470
+ });
471
+
472
+ expect(Array.isArray(restored.weeklyRestrictionTimes)).toBe(true);
473
+ expect(restored.weeklyRestrictionTimes).toEqual([]);
474
+ });
475
+
476
+ test("null weeklyRestrictionTimes falls back to an empty array", () => {
477
+ const restored: RestrictionTimes = RestrictionTimes.fromJSON({
478
+ _type: ObjectType.RestrictionTimes,
479
+ value: {
480
+ restictionType: RestrictionType.None,
481
+ dayRestrictionTimes: null,
482
+ weeklyRestrictionTimes: null,
483
+ },
484
+ });
485
+
486
+ expect(Array.isArray(restored.weeklyRestrictionTimes)).toBe(true);
487
+ expect(restored.weeklyRestrictionTimes).toEqual([]);
488
+ });
489
+
490
+ test("iterating the fallback array does NOT throw (the F20 crash guard)", () => {
491
+ const restored: RestrictionTimes = RestrictionTimes.fromJSON({
492
+ _type: ObjectType.RestrictionTimes,
493
+ value: { restictionType: RestrictionType.None },
494
+ });
495
+
496
+ expect(() => {
497
+ // Exactly the loop shape Layer.getEventsByWeeklyRestriction runs.
498
+ for (const _w of restored.weeklyRestrictionTimes) {
499
+ void _w;
500
+ }
501
+ const copy: Array<WeeklyResctriction> = [
502
+ ...restored.weeklyRestrictionTimes,
503
+ ];
504
+ restored.weeklyRestrictionTimes.map((w: WeeklyResctriction) => {
505
+ return w;
506
+ });
507
+ void copy;
508
+ }).not.toThrow();
509
+ expect(restored.weeklyRestrictionTimes.length).toBe(0);
510
+ });
511
+
512
+ test("a provided weekly list is preserved verbatim (raw, no deserialize)", () => {
513
+ const window: WeeklyResctriction = weeklyWindow({
514
+ startDay: DayOfWeek.Tuesday,
515
+ endDay: DayOfWeek.Thursday,
516
+ startIso: "2025-02-04T08:00:00.000Z",
517
+ endIso: "2025-02-06T20:00:00.000Z",
518
+ });
519
+ const restored: RestrictionTimes = RestrictionTimes.fromJSON({
520
+ _type: ObjectType.RestrictionTimes,
521
+ value: {
522
+ restictionType: RestrictionType.Weekly,
523
+ dayRestrictionTimes: null,
524
+ weeklyRestrictionTimes: [window as unknown as JSONObject],
525
+ },
526
+ });
527
+
528
+ expect(restored.weeklyRestrictionTimes.length).toBe(1);
529
+ expect(restored.weeklyRestrictionTimes[0]!.startDay).toBe(
530
+ DayOfWeek.Tuesday,
531
+ );
532
+ expect(restored.weeklyRestrictionTimes[0]!.endDay).toBe(
533
+ DayOfWeek.Thursday,
534
+ );
535
+ });
536
+
537
+ test(
538
+ "NOTE current behavior: an explicit {} weekly value is NOT normalized " +
539
+ "to [] (|| [] only rescues falsy values); see reported bug",
540
+ () => {
541
+ const restored: RestrictionTimes = RestrictionTimes.fromJSON({
542
+ _type: ObjectType.RestrictionTimes,
543
+ value: {
544
+ restictionType: RestrictionType.None,
545
+ dayRestrictionTimes: null,
546
+ weeklyRestrictionTimes: {} as unknown as JSONObject,
547
+ },
548
+ });
549
+
550
+ // Current behavior: stays a non-array {} because {} is truthy.
551
+ expect(Array.isArray(restored.weeklyRestrictionTimes)).toBe(false);
552
+ },
553
+ );
554
+ });
555
+
556
+ describe("fromJSON: missing / omitted day time", () => {
557
+ test("None round-trip keeps dayRestrictionTimes null", () => {
558
+ const restored: RestrictionTimes = roundTripRaw(new RestrictionTimes());
559
+ expect(restored.dayRestrictionTimes).toBeNull();
560
+ });
561
+
562
+ test("omitting dayRestrictionTimes leaves it undefined (current behavior)", () => {
563
+ const restored: RestrictionTimes = RestrictionTimes.fromJSON({
564
+ _type: ObjectType.RestrictionTimes,
565
+ value: { restictionType: RestrictionType.None },
566
+ });
567
+ // NOTE current behavior: absent -> undefined (not coerced to null).
568
+ expect(restored.dayRestrictionTimes).toBeUndefined();
569
+ });
570
+ });
571
+
572
+ describe("fromJSON: validation / error handling", () => {
573
+ test.each([
574
+ ["null", null],
575
+ ["undefined", undefined],
576
+ ["empty object (no _type)", {}],
577
+ ["wrong _type", { _type: ObjectType.Recurring, value: {} }],
578
+ [
579
+ "correct _type but missing value",
580
+ { _type: ObjectType.RestrictionTimes },
581
+ ],
582
+ [
583
+ "correct _type but null value",
584
+ { _type: ObjectType.RestrictionTimes, value: null },
585
+ ],
586
+ ])("throws BadDataException for %s", (_label: string, input: unknown) => {
587
+ expect(() => {
588
+ RestrictionTimes.fromJSON(input as unknown as JSONObject);
589
+ }).toThrowError(BadDataException);
590
+ });
591
+
592
+ test("a present-but-empty value object does NOT throw (only truthiness checked)", () => {
593
+ const restored: RestrictionTimes = RestrictionTimes.fromJSON({
594
+ _type: ObjectType.RestrictionTimes,
595
+ value: {},
596
+ });
597
+ expect(restored).toBeInstanceOf(RestrictionTimes);
598
+ expect(restored.restictionType).toBeUndefined();
599
+ expect(restored.weeklyRestrictionTimes).toEqual([]);
600
+ });
601
+
602
+ test(
603
+ "NOTE current behavior: an invalid restictionType string is passed " +
604
+ "through unvalidated",
605
+ () => {
606
+ const restored: RestrictionTimes = RestrictionTimes.fromJSON({
607
+ _type: ObjectType.RestrictionTimes,
608
+ value: {
609
+ restictionType: "TotallyInvalid",
610
+ dayRestrictionTimes: null,
611
+ weeklyRestrictionTimes: [],
612
+ },
613
+ });
614
+ expect(restored.restictionType).toBe(
615
+ "TotallyInvalid" as unknown as RestrictionType,
616
+ );
617
+ },
618
+ );
619
+ });
620
+
621
+ describe("round-trip: None / Daily / Weekly", () => {
622
+ test("None survives a raw round-trip", () => {
623
+ const restored: RestrictionTimes = roundTripRaw(new RestrictionTimes());
624
+ expect(restored.restictionType).toBe(RestrictionType.None);
625
+ expect(restored.dayRestrictionTimes).toBeNull();
626
+ expect(restored.weeklyRestrictionTimes).toEqual([]);
627
+ });
628
+
629
+ test(
630
+ "NOTE current behavior: a raw (no-deserialize) round-trip does NOT " +
631
+ "restore Date instances for day times",
632
+ () => {
633
+ const r: RestrictionTimes = new RestrictionTimes();
634
+ r.addDefaultDailyRestriction();
635
+ const restored: RestrictionTimes = roundTripRaw(r);
636
+
637
+ expect(restored.restictionType).toBe(RestrictionType.Daily);
638
+ expect(restored.dayRestrictionTimes).not.toBeNull();
639
+ // The times come back as {_type:"DateTime", value} wrappers, not Dates.
640
+ const start: unknown = (restored.dayRestrictionTimes as StartAndEndTime)
641
+ .startTime;
642
+ expect(start instanceof Date).toBe(false);
643
+ expectDateTimeWrapper(start);
644
+ },
645
+ );
646
+
647
+ test("Daily survives the realistic DB round-trip with Date times intact", () => {
648
+ const r: RestrictionTimes = new RestrictionTimes();
649
+ r.dayRestrictionTimes = {
650
+ startTime: OneUptimeDate.fromString("2025-03-10T09:15:30.000Z"),
651
+ endTime: OneUptimeDate.fromString("2025-03-10T17:45:00.000Z"),
652
+ };
653
+ r.restictionType = RestrictionType.Daily;
654
+ const startMs: number = (
655
+ r.dayRestrictionTimes.startTime as Date
656
+ ).getTime();
657
+ const endMs: number = (r.dayRestrictionTimes.endTime as Date).getTime();
658
+
659
+ const restored: RestrictionTimes = roundTripThroughDb(r);
660
+
661
+ expect(restored.restictionType).toBe(RestrictionType.Daily);
662
+ const day: StartAndEndTime =
663
+ restored.dayRestrictionTimes as StartAndEndTime;
664
+ expect(day.startTime).toBeInstanceOf(Date);
665
+ expect(day.endTime).toBeInstanceOf(Date);
666
+ // getTime() is absolute and thus TZ-independent -> exact preservation.
667
+ expect((day.startTime as Date).getTime()).toBe(startMs);
668
+ expect((day.endTime as Date).getTime()).toBe(endMs);
669
+ });
670
+
671
+ test("single Weekly window survives the DB round-trip", () => {
672
+ const r: RestrictionTimes = new RestrictionTimes();
673
+ r.addDefaultWeeklyRestriction();
674
+ const origStartMs: number =
675
+ r.weeklyRestrictionTimes[0]!.startTime.getTime();
676
+ const origEndMs: number = r.weeklyRestrictionTimes[0]!.endTime.getTime();
677
+
678
+ const restored: RestrictionTimes = roundTripThroughDb(r);
679
+
680
+ expect(restored.restictionType).toBe(RestrictionType.Weekly);
681
+ expect(restored.weeklyRestrictionTimes.length).toBe(1);
682
+ const w: WeeklyResctriction = restored.weeklyRestrictionTimes[0]!;
683
+ expect(w.startDay).toBe(DayOfWeek.Sunday);
684
+ expect(w.endDay).toBe(DayOfWeek.Monday);
685
+ expect(w.startTime).toBeInstanceOf(Date);
686
+ expect(w.endTime).toBeInstanceOf(Date);
687
+ expect(w.startTime.getTime()).toBe(origStartMs);
688
+ expect(w.endTime.getTime()).toBe(origEndMs);
689
+ });
690
+
691
+ test("MULTIPLE Weekly windows survive the DB round-trip in order", () => {
692
+ const windows: Array<WeeklyResctriction> = [
693
+ weeklyWindow({
694
+ startDay: DayOfWeek.Monday,
695
+ endDay: DayOfWeek.Wednesday,
696
+ startIso: "2025-01-06T09:00:00.000Z",
697
+ endIso: "2025-01-08T17:00:00.000Z",
698
+ }),
699
+ weeklyWindow({
700
+ startDay: DayOfWeek.Thursday,
701
+ endDay: DayOfWeek.Friday,
702
+ startIso: "2025-01-09T00:00:00.000Z",
703
+ endIso: "2025-01-10T12:30:45.000Z",
704
+ }),
705
+ weeklyWindow({
706
+ startDay: DayOfWeek.Saturday,
707
+ endDay: DayOfWeek.Sunday,
708
+ startIso: "2025-01-11T06:00:00.000Z",
709
+ endIso: "2025-01-12T23:59:59.000Z",
710
+ }),
711
+ ];
712
+ const r: RestrictionTimes = new RestrictionTimes();
713
+ r.restictionType = RestrictionType.Weekly;
714
+ r.weeklyRestrictionTimes = windows;
715
+
716
+ const restored: RestrictionTimes = roundTripThroughDb(r);
717
+
718
+ expect(restored.weeklyRestrictionTimes.length).toBe(3);
719
+ restored.weeklyRestrictionTimes.forEach(
720
+ (w: WeeklyResctriction, i: number) => {
721
+ expect(w.startDay).toBe(windows[i]!.startDay);
722
+ expect(w.endDay).toBe(windows[i]!.endDay);
723
+ expect(w.startTime).toBeInstanceOf(Date);
724
+ expect(w.endTime).toBeInstanceOf(Date);
725
+ expect(w.startTime.getTime()).toBe(windows[i]!.startTime.getTime());
726
+ expect(w.endTime.getTime()).toBe(windows[i]!.endTime.getTime());
727
+ },
728
+ );
729
+ });
730
+
731
+ test("restriction type round-trips for every RestrictionType value", () => {
732
+ const types: Array<RestrictionType> = [
733
+ RestrictionType.None,
734
+ RestrictionType.Daily,
735
+ RestrictionType.Weekly,
736
+ ];
737
+ types.forEach((t: RestrictionType) => {
738
+ const r: RestrictionTimes = new RestrictionTimes();
739
+ r.restictionType = t;
740
+ expect(roundTripRaw(r).restictionType).toBe(t);
741
+ expect(roundTripThroughDb(r).restictionType).toBe(t);
742
+ });
743
+ });
744
+ });
745
+
746
+ describe("toDatabase / fromDatabase", () => {
747
+ test("toDatabase(instance) equals its toJSON()", () => {
748
+ const r: RestrictionTimes = new RestrictionTimes();
749
+ r.addDefaultWeeklyRestriction();
750
+
751
+ const db: JSONObject | null = dbAccessor.toDatabase(r);
752
+
753
+ expect(db).not.toBeNull();
754
+ expect(db!["_type"]).toBe(ObjectType.RestrictionTimes);
755
+ expect(db).toEqual(r.toJSON());
756
+ });
757
+
758
+ test("toDatabase(plain value object) serializes without instanceof", () => {
759
+ const plain: JSONObject = {
760
+ _type: ObjectType.RestrictionTimes,
761
+ value: {
762
+ restictionType: RestrictionType.None,
763
+ dayRestrictionTimes: null,
764
+ weeklyRestrictionTimes: [],
765
+ },
766
+ };
767
+
768
+ const db: JSONObject | null = dbAccessor.toDatabase(plain);
769
+
770
+ expect(db).not.toBeNull();
771
+ expect(db!["_type"]).toBe(ObjectType.RestrictionTimes);
772
+ });
773
+
774
+ test("toDatabase(null) and toDatabase(undefined) return null", () => {
775
+ expect(dbAccessor.toDatabase(null)).toBeNull();
776
+ expect(dbAccessor.toDatabase(undefined)).toBeNull();
777
+ });
778
+
779
+ test("fromDatabase rebuilds an instance from stored JSON", () => {
780
+ const r: RestrictionTimes = new RestrictionTimes();
781
+ r.addDefaultWeeklyRestriction();
782
+
783
+ const restored: RestrictionTimes | null = dbAccessor.fromDatabase(
784
+ r.toJSON(),
785
+ );
786
+
787
+ expect(restored).toBeInstanceOf(RestrictionTimes);
788
+ expect(restored!.restictionType).toBe(RestrictionType.Weekly);
789
+ expect(restored!.weeklyRestrictionTimes.length).toBe(1);
790
+ });
791
+
792
+ test("fromDatabase(null) returns null", () => {
793
+ expect(dbAccessor.fromDatabase(null)).toBeNull();
794
+ });
795
+
796
+ test("toDatabase -> deserialize -> fromDatabase restores Date times (Daily)", () => {
797
+ const r: RestrictionTimes = new RestrictionTimes();
798
+ r.restictionType = RestrictionType.Daily;
799
+ r.dayRestrictionTimes = {
800
+ startTime: OneUptimeDate.fromString("2025-06-01T00:00:00.000Z"),
801
+ endTime: OneUptimeDate.fromString("2025-06-01T08:30:00.000Z"),
802
+ };
803
+ const startMs: number = (
804
+ r.dayRestrictionTimes.startTime as Date
805
+ ).getTime();
806
+
807
+ const stored: JSONObject = JSON.parse(
808
+ JSON.stringify(dbAccessor.toDatabase(r)),
809
+ ) as JSONObject;
810
+ const restored: RestrictionTimes | null = dbAccessor.fromDatabase(
811
+ JSONFunctions.deserialize(stored),
812
+ );
813
+
814
+ expect(restored).toBeInstanceOf(RestrictionTimes);
815
+ const day: StartAndEndTime = restored!
816
+ .dayRestrictionTimes as StartAndEndTime;
817
+ expect(day.startTime).toBeInstanceOf(Date);
818
+ expect((day.startTime as Date).getTime()).toBe(startMs);
819
+ });
820
+ });
821
+ });