@oneuptime/common 12.0.5 → 12.0.6
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.
- package/Models/DatabaseModels/AIRun.ts +33 -0
- package/Models/DatabaseModels/DataSource.ts +744 -0
- package/Models/DatabaseModels/Index.ts +3 -0
- package/Models/DatabaseModels/ProjectCallSMSConfig.ts +10 -2
- package/Models/DatabaseModels/User.ts +2 -0
- package/Models/DatabaseModels/UserOnCallLog.ts +2 -0
- package/Models/DatabaseModels/UserOnCallLogTimeline.ts +2 -0
- package/Models/DatabaseModels/UserSession.ts +2 -0
- package/Models/DatabaseModels/UserTotpAuth.ts +6 -4
- package/Models/DatabaseModels/UserWebAuthn.ts +5 -3
- package/Server/API/AIInsightAPI.ts +47 -4
- package/Server/API/AIInvestigationAPI.ts +25 -0
- package/Server/API/DashboardAPI.ts +176 -492
- package/Server/API/DataSourceAPI.ts +268 -0
- package/Server/API/StatusPageAPI.ts +11 -1
- package/Server/API/UserAPI.ts +119 -0
- package/Server/Infrastructure/ClickhouseConfig.ts +59 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.ts +39 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.ts +24 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.ts +76 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +6 -0
- package/Server/Infrastructure/Queue.ts +26 -0
- package/Server/Infrastructure/QueueWorker.ts +17 -0
- package/Server/Middleware/TelemetryIngest.ts +8 -3
- package/Server/Services/AIInsightService.ts +132 -12
- package/Server/Services/AIRunService.ts +34 -0
- package/Server/Services/AIService.ts +17 -2
- package/Server/Services/AlertService.ts +38 -32
- package/Server/Services/AnalyticsDatabaseService.ts +8 -4
- package/Server/Services/DataSourceService.ts +200 -0
- package/Server/Services/HostService.ts +92 -0
- package/Server/Services/IncidentService.ts +45 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/MonitorService.ts +49 -0
- package/Server/Services/MonitorStatusService.ts +166 -0
- package/Server/Services/OpenTelemetryIngestService.ts +255 -184
- package/Server/Services/RumSessionPinService.ts +7 -21
- package/Server/Services/StatusPageSubscriberService.ts +13 -0
- package/Server/Services/UserService.ts +280 -9
- package/Server/Static/Vendor/README.md +62 -0
- package/Server/Static/Vendor/fonts/InterVariable.woff2 +0 -0
- package/Server/Static/Vendor/highlight/highlight.min.js +1244 -0
- package/Server/Static/Vendor/highlight/languages/apache.min.js +15 -0
- package/Server/Static/Vendor/highlight/languages/bash.min.js +21 -0
- package/Server/Static/Vendor/highlight/languages/c.min.js +41 -0
- package/Server/Static/Vendor/highlight/languages/cpp.min.js +47 -0
- package/Server/Static/Vendor/highlight/languages/csharp.min.js +49 -0
- package/Server/Static/Vendor/highlight/languages/css.min.js +31 -0
- package/Server/Static/Vendor/highlight/languages/diff.min.js +9 -0
- package/Server/Static/Vendor/highlight/languages/dns.min.js +11 -0
- package/Server/Static/Vendor/highlight/languages/dockerfile.min.js +8 -0
- package/Server/Static/Vendor/highlight/languages/go.min.js +20 -0
- package/Server/Static/Vendor/highlight/languages/graphql.min.js +12 -0
- package/Server/Static/Vendor/highlight/languages/http.min.js +14 -0
- package/Server/Static/Vendor/highlight/languages/ini.min.js +15 -0
- package/Server/Static/Vendor/highlight/languages/java.min.js +38 -0
- package/Server/Static/Vendor/highlight/languages/javascript.min.js +81 -0
- package/Server/Static/Vendor/highlight/languages/json.min.js +8 -0
- package/Server/Static/Vendor/highlight/languages/kotlin.min.js +46 -0
- package/Server/Static/Vendor/highlight/languages/lua.min.js +15 -0
- package/Server/Static/Vendor/highlight/languages/makefile.min.js +14 -0
- package/Server/Static/Vendor/highlight/languages/nginx.min.js +21 -0
- package/Server/Static/Vendor/highlight/languages/perl.min.js +41 -0
- package/Server/Static/Vendor/highlight/languages/php.min.js +59 -0
- package/Server/Static/Vendor/highlight/languages/powershell.min.js +39 -0
- package/Server/Static/Vendor/highlight/languages/python.min.js +42 -0
- package/Server/Static/Vendor/highlight/languages/r.min.js +26 -0
- package/Server/Static/Vendor/highlight/languages/ruby.min.js +54 -0
- package/Server/Static/Vendor/highlight/languages/rust.min.js +27 -0
- package/Server/Static/Vendor/highlight/languages/scala.min.js +28 -0
- package/Server/Static/Vendor/highlight/languages/sql.min.js +21 -0
- package/Server/Static/Vendor/highlight/languages/swift.min.js +68 -0
- package/Server/Static/Vendor/highlight/languages/typescript.min.js +99 -0
- package/Server/Static/Vendor/highlight/languages/xml.min.js +29 -0
- package/Server/Static/Vendor/highlight/languages/yaml.min.js +28 -0
- package/Server/Static/Vendor/highlight/styles/github-dark.min.css +10 -0
- package/Server/Static/Vendor/highlight/styles/vs2015.min.css +1 -0
- package/Server/Static/Vendor/tailwind/tailwind-3.4.5.js +21028 -0
- package/Server/Types/Database/Permissions/BasePermission.ts +7 -0
- package/Server/Types/Database/Permissions/DeletePermission.ts +2 -0
- package/Server/Types/Database/Permissions/ReadPermission.ts +3 -0
- package/Server/Types/Database/Permissions/TenantPermission.ts +101 -16
- package/Server/Utils/AI/CodeFix/FixRunBudget.ts +2 -2
- package/Server/Utils/AI/SRE/AIInvestigationEngine.ts +90 -11
- package/Server/Utils/AI/SRE/AlertInvestigationRunner.ts +1 -0
- package/Server/Utils/AI/SRE/IncidentInvestigationRunner.ts +1 -0
- package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.ts +125 -0
- package/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.ts +25 -15
- package/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.ts +256 -57
- package/Server/Utils/AI/SRE/Insights/InsightStore.ts +76 -18
- package/Server/Utils/AI/SRE/InvestigationTldr.ts +352 -0
- package/Server/Utils/AnalyticsDatabase/InsertDedupContext.ts +7 -2
- package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +1793 -0
- package/Server/Utils/Dashboard/PublicDashboardViewConfig.ts +65 -0
- package/Server/Utils/DataSource/ConnectorRegistry.ts +53 -0
- package/Server/Utils/DataSource/Connectors/ClickHouseConnector.ts +338 -0
- package/Server/Utils/DataSource/Connectors/ElasticsearchConnector.ts +717 -0
- package/Server/Utils/DataSource/Connectors/LokiConnector.ts +606 -0
- package/Server/Utils/DataSource/Connectors/MySqlConnector.ts +307 -0
- package/Server/Utils/DataSource/Connectors/PostgresConnector.ts +260 -0
- package/Server/Utils/DataSource/Connectors/PrometheusConnector.ts +555 -0
- package/Server/Utils/DataSource/Connectors/RestApiConnector.ts +597 -0
- package/Server/Utils/DataSource/Connectors/SqlServerConnector.ts +318 -0
- package/Server/Utils/DataSource/EgressGuard.ts +446 -0
- package/Server/Utils/DataSource/HttpFetch.ts +221 -0
- package/Server/Utils/DataSource/SqlQueryGuard.ts +383 -0
- package/Server/Utils/DataSource/SqlResultShaper.ts +372 -0
- package/Server/Utils/DataSource/Types.ts +93 -0
- package/Server/Utils/Database/PostgresErrorTranslator.ts +172 -17
- package/Server/Utils/InProcessMemo.ts +141 -0
- package/Server/Utils/InstanceHealth/TelemetryIngestion.ts +628 -0
- package/Server/Utils/Monitor/MonitorMetricUtil.ts +58 -0
- package/Server/Utils/Monitor/MonitorResource.ts +14 -0
- package/Server/Utils/Response.ts +12 -1
- package/Server/Utils/SSRFProtection.ts +237 -0
- package/Server/Utils/StartServer.ts +8 -0
- package/Server/Utils/StatusPageSubscriberWebhook.ts +30 -0
- package/Server/Utils/Telemetry/CaptureSpan.ts +30 -18
- package/Server/Utils/Telemetry/IngestionTimestamp.ts +75 -0
- package/Server/Utils/Telemetry/ResourceHeartbeat.ts +99 -7
- package/Server/Utils/Telemetry/Telemetry.ts +112 -1
- package/Server/Utils/Telemetry.ts +104 -3
- package/Server/Utils/VendorAssets.ts +165 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +28 -5
- package/Server/Utils/Workspace/Slack/Slack.ts +5 -0
- package/Server/Views/Partials/Head.ejs +21 -7
- package/Tests/App/AdminDashboard/AdminProjectsIdFilter.test.tsx +207 -0
- package/Tests/App/AdminDashboard/AdminUsersIdFilter.test.tsx +216 -0
- package/Tests/App/Dashboard/AIInsightViewActions.test.tsx +422 -0
- package/Tests/App/Dashboard/AddWidgetModal.test.tsx +10 -3
- package/Tests/App/Dashboard/AlertsSideMenu.test.tsx +387 -0
- package/Tests/App/Dashboard/ChatActivityFeed.test.tsx +199 -0
- package/Tests/App/Dashboard/DataSourceChartSyntheticConfig.test.tsx +736 -0
- package/Tests/App/Dashboard/DataSourceQueryEditor.test.tsx +868 -0
- package/Tests/App/Dashboard/DataSourceWidgetCatalog.test.ts +440 -0
- package/Tests/App/Dashboard/DataSourceWidgetFetching.test.tsx +2109 -0
- package/Tests/App/Dashboard/DataSourceWidgetViews.test.tsx +726 -0
- package/Tests/App/Dashboard/IncidentsSideMenu.test.tsx +447 -0
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +514 -3
- package/Tests/App/Dashboard/ProductMenuBreadcrumbs.test.tsx +391 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceSideMenu.test.tsx +326 -0
- package/Tests/App/Dashboard/SideMenuHarness.tsx +187 -0
- package/Tests/App/Dashboard/WidgetCatalog.test.ts +6 -1
- package/Tests/App/StatusPage/StatusPageResourceExplorer.test.tsx +1858 -0
- package/Tests/Models/DatabaseModels/DataSourceModelSecrets.test.ts +70 -0
- package/Tests/Server/API/AIInvestigationAPI.test.ts +187 -0
- package/Tests/Server/API/DashboardPublicDataSourceStrip.test.ts +686 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +585 -823
- package/Tests/Server/API/DataSourceAPIRoutes.test.ts +486 -0
- package/Tests/Server/API/StatusPageSubscriberOwnership.test.ts +291 -0
- package/Tests/Server/API/UserAuthenticationAPI.test.ts +641 -0
- package/Tests/Server/Infrastructure/ClickhouseKeepAliveOptions.test.ts +441 -0
- package/Tests/Server/Middleware/TelemetryIngestTokenLog.test.ts +140 -0
- package/Tests/Server/Services/AIInsightService.test.ts +266 -11
- package/Tests/Server/Services/AIRunAnalysisTldrPersistence.test.ts +160 -0
- package/Tests/Server/Services/AIServiceDailyBudget.test.ts +4 -0
- package/Tests/Server/Services/AlertMetricResourceAttributes.test.ts +352 -0
- package/Tests/Server/Services/DataSourceServiceSettings.test.ts +244 -0
- package/Tests/Server/Services/HostServiceFindOrCreateMemo.test.ts +397 -0
- package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +41 -2
- package/Tests/Server/Services/IncidentMetricResourceAttributes.test.ts +408 -0
- package/Tests/Server/Services/MonitorStatusServiceDeleteReferenceCleanup.test.ts +351 -0
- package/Tests/Server/Services/MonitorStatusServiceValidation.test.ts +171 -0
- package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +398 -0
- package/Tests/Server/Services/OpenTelemetryServiceResolutionCache.test.ts +19 -0
- package/Tests/Server/Services/ProjectCallSMSConfigCredentialUniqueness.test.ts +305 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +36 -1
- package/Tests/Server/Services/RumSessionPinUniqueViolationRecovery.test.ts +163 -0
- package/Tests/Server/Services/UserAuthenticationService.test.ts +973 -0
- package/Tests/Server/Types/Database/ObjectIdColumnQuery.test.ts +262 -0
- package/Tests/Server/Types/Database/Permissions/TenantPermission.test.ts +358 -0
- package/Tests/Server/Utils/AI/FixRunBudget.test.ts +2 -2
- package/Tests/Server/Utils/AI/Insights/ExceptionSpikeDetector.test.ts +6 -1
- package/Tests/Server/Utils/AI/Insights/InsightStore.test.ts +121 -4
- package/Tests/Server/Utils/AI/Insights/NewExceptionDetector.test.ts +361 -10
- package/Tests/Server/Utils/AI/InvestigationSettlement.test.ts +292 -0
- package/Tests/Server/Utils/AI/InvestigationTldr.test.ts +623 -0
- package/Tests/Server/Utils/AI/InvestigationTldrWiring.test.ts +98 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +857 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardViewConfig.test.ts +480 -0
- package/Tests/Server/Utils/DataSource/ClickHouseConnector.test.ts +759 -0
- package/Tests/Server/Utils/DataSource/ConnectorRegistry.test.ts +37 -0
- package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +645 -0
- package/Tests/Server/Utils/DataSource/ElasticsearchConnector.test.ts +1206 -0
- package/Tests/Server/Utils/DataSource/HttpFetch.test.ts +616 -0
- package/Tests/Server/Utils/DataSource/LokiConnector.test.ts +970 -0
- package/Tests/Server/Utils/DataSource/MySqlConnector.test.ts +935 -0
- package/Tests/Server/Utils/DataSource/PostgresConnector.test.ts +854 -0
- package/Tests/Server/Utils/DataSource/PrometheusConnector.test.ts +881 -0
- package/Tests/Server/Utils/DataSource/RestApiConnector.test.ts +1020 -0
- package/Tests/Server/Utils/DataSource/SqlQueryGuard.test.ts +425 -0
- package/Tests/Server/Utils/DataSource/SqlResultShaper.test.ts +555 -0
- package/Tests/Server/Utils/DataSource/SqlServerConnector.test.ts +934 -0
- package/Tests/Server/Utils/Database/PostgresErrorTranslator.test.ts +228 -7
- package/Tests/Server/Utils/InProcessMemo.test.ts +292 -0
- package/Tests/Server/Utils/InstanceHealth/TelemetryIngestion.test.ts +960 -0
- package/Tests/Server/Utils/MicrosoftTeamsWebhookUrlValidation.test.ts +103 -0
- package/Tests/Server/Utils/Monitor/MonitorMetricResourceAttributes.test.ts +336 -0
- package/Tests/Server/Utils/OfflineAssetHygiene.test.ts +521 -0
- package/Tests/Server/Utils/ResponseRenderAnalytics.test.ts +143 -0
- package/Tests/Server/Utils/SSRFProtection.test.ts +193 -0
- package/Tests/Server/Utils/StatusPageSubscriberWebhook.test.ts +101 -0
- package/Tests/Server/Utils/TailwindBuildCopy.test.ts +225 -0
- package/Tests/Server/Utils/Telemetry/ResourceHeartbeatL1Memo.test.ts +355 -0
- package/Tests/Server/Utils/Telemetry/TelemetryFanInWriter.test.ts +128 -0
- package/Tests/Server/Utils/Telemetry/TelemetryUtilAttributeKeys.test.ts +381 -0
- package/Tests/Server/Utils/Telemetry.test.ts +87 -0
- package/Tests/Server/Utils/VendorAssetIntegrity.test.ts +343 -0
- package/Tests/Server/Utils/VendorAssets.test.ts +671 -0
- package/Tests/Server/Views/OfflineRender.test.ts +297 -0
- package/Tests/Types/Dashboard/DataSourceComponentTypes.test.ts +618 -0
- package/Tests/Types/DateClickhouseFormat.test.ts +192 -0
- package/Tests/Types/Password.test.ts +363 -0
- package/Tests/UI/Components/ConfirmModal.test.tsx +7 -0
- package/Tests/UI/Components/ErrorBoundary.test.tsx +53 -0
- package/Tests/UI/Components/Modal.test.tsx +281 -1
- package/Tests/UI/Components/ObjectIdFilterPipeline.test.tsx +396 -0
- package/Tests/UI/Components/StatusPage/ResourceGroupNavigator.test.tsx +1147 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +116 -1
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +2 -0
- package/Tests/Utils/Analytics.test.ts +23 -0
- package/Tests/Utils/Dashboard/Components/DashboardDataSourceComponents.test.ts +640 -0
- package/Tests/Utils/DataSource/DataSourceQueryText.test.ts +138 -0
- package/Tests/Utils/DataSource/DataSourceValueReducer.test.ts +526 -0
- package/Tests/Utils/Metrics/MetricResourceAttributeUtil.test.ts +691 -0
- package/Tests/Utils/StatusPage/GroupHierarchyView.test.ts +1452 -0
- package/Tests/Utils/StatusPage/ResourceExplorer.test.ts +931 -0
- package/Tests/Utils/Telemetry/CaptureSpanExportGating.test.ts +288 -0
- package/Tests/Utils/Telemetry/IngestionTimestamp.test.ts +170 -0
- package/Types/AI/AIInsightEvidence.ts +6 -0
- package/Types/Analytics/RevenueEvent.ts +31 -0
- package/Types/Dashboard/DashboardComponentType.ts +31 -0
- package/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.ts +27 -0
- package/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.ts +32 -0
- package/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.ts +30 -0
- package/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.ts +55 -0
- package/Types/DataSource/DataSourceLimits.ts +62 -0
- package/Types/DataSource/DataSourceQueryConfig.ts +41 -0
- package/Types/DataSource/DataSourceTableResult.ts +38 -0
- package/Types/DataSource/DataSourceType.ts +213 -0
- package/Types/Date.ts +43 -3
- package/Types/Password.ts +64 -0
- package/Types/Permission.ts +46 -0
- package/Types/UserAuthenticationStatus.ts +34 -0
- package/UI/Components/Button/Button.tsx +1 -1
- package/UI/Components/ErrorBoundary.tsx +459 -108
- package/UI/Components/Icon/Icon.tsx +10 -9
- package/UI/Components/Modal/ConfirmModal.tsx +6 -1
- package/UI/Components/Modal/Modal.tsx +267 -35
- package/UI/Components/Modal/ModalFooter.tsx +6 -1
- package/UI/Components/MoreMenu/MoreMenu.tsx +19 -4
- package/UI/Components/MoreMenu/MoreMenuItem.tsx +6 -1
- package/UI/Components/StatusPage/ResourceGrid.tsx +314 -0
- package/UI/Components/StatusPage/ResourceGroupNavigator.tsx +808 -0
- package/UI/Components/StatusPage/ResourceList.tsx +570 -0
- package/UI/Styles/Theme.css +18 -0
- package/UI/Utils/API/RequestOptions.ts +7 -0
- package/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +1 -0
- package/UI/Utils/ModelAPI/ModelAPI.ts +1 -0
- package/UI/Utils/TestDataSource.ts +61 -0
- package/UI/esbuild-config.js +45 -0
- package/Utils/Analytics.ts +15 -0
- package/Utils/Dashboard/Components/DashboardDataSourceChartComponent.ts +88 -0
- package/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.ts +123 -0
- package/Utils/Dashboard/Components/DashboardDataSourceShared.ts +16 -0
- package/Utils/Dashboard/Components/DashboardDataSourceTableComponent.ts +72 -0
- package/Utils/Dashboard/Components/DashboardDataSourceValueComponent.ts +126 -0
- package/Utils/Dashboard/Components/Index.ts +28 -0
- package/Utils/DataSource/DataSourceQueryText.ts +94 -0
- package/Utils/DataSource/DataSourceValueReducer.ts +156 -0
- package/Utils/Metrics/MetricResourceAttributeUtil.ts +380 -0
- package/Utils/NetworkDiscovery/ScanTargetUtil.ts +8 -6
- package/Utils/StatusPage/GroupHierarchyView.ts +931 -0
- package/Utils/StatusPage/ResourceExplorer.ts +853 -0
- package/build/dist/Models/DatabaseModels/AIRun.js +34 -0
- package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DataSource.js +774 -0
- package/build/dist/Models/DatabaseModels/DataSource.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.js +14 -4
- package/build/dist/Models/DatabaseModels/ProjectCallSMSConfig.js.map +1 -1
- package/build/dist/Models/DatabaseModels/User.js +2 -0
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLog.js +2 -0
- package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +2 -0
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserSession.js +2 -0
- package/build/dist/Models/DatabaseModels/UserSession.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserTotpAuth.js +6 -4
- package/build/dist/Models/DatabaseModels/UserTotpAuth.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserWebAuthn.js +5 -3
- package/build/dist/Models/DatabaseModels/UserWebAuthn.js.map +1 -1
- package/build/dist/Server/API/AIInsightAPI.js +34 -4
- package/build/dist/Server/API/AIInsightAPI.js.map +1 -1
- package/build/dist/Server/API/AIInvestigationAPI.js +23 -0
- package/build/dist/Server/API/AIInvestigationAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +119 -433
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/DataSourceAPI.js +173 -0
- package/build/dist/Server/API/DataSourceAPI.js.map +1 -0
- package/build/dist/Server/API/StatusPageAPI.js +11 -1
- package/build/dist/Server/API/StatusPageAPI.js.map +1 -1
- package/build/dist/Server/API/UserAPI.js +94 -2
- package/build/dist/Server/API/UserAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js +49 -0
- package/build/dist/Server/Infrastructure/ClickhouseConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.js +20 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786303472848-AddDataSourceTable.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.js +19 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786600000000-AddInvestigationAnalysisTldr.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.js +64 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786700000000-DropProjectCallSMSConfigCredentialUniques.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Queue.js +27 -0
- package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
- package/build/dist/Server/Infrastructure/QueueWorker.js +13 -0
- package/build/dist/Server/Infrastructure/QueueWorker.js.map +1 -1
- package/build/dist/Server/Middleware/TelemetryIngest.js +8 -3
- package/build/dist/Server/Middleware/TelemetryIngest.js.map +1 -1
- package/build/dist/Server/Services/AIInsightService.js +110 -10
- package/build/dist/Server/Services/AIInsightService.js.map +1 -1
- package/build/dist/Server/Services/AIRunService.js +34 -0
- package/build/dist/Server/Services/AIRunService.js.map +1 -1
- package/build/dist/Server/Services/AIService.js +16 -2
- package/build/dist/Server/Services/AIService.js.map +1 -1
- package/build/dist/Server/Services/AlertService.js +28 -35
- package/build/dist/Server/Services/AlertService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +8 -4
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DataSourceService.js +159 -0
- package/build/dist/Server/Services/DataSourceService.js.map +1 -0
- package/build/dist/Server/Services/HostService.js +74 -0
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/IncidentService.js +35 -24
- package/build/dist/Server/Services/IncidentService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +45 -0
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusService.js +129 -0
- package/build/dist/Server/Services/MonitorStatusService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +222 -159
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/RumSessionPinService.js +7 -15
- package/build/dist/Server/Services/RumSessionPinService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageSubscriberService.js +10 -0
- package/build/dist/Server/Services/StatusPageSubscriberService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +241 -10
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/BasePermission.js +6 -0
- package/build/dist/Server/Types/Database/Permissions/BasePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/DeletePermission.js +1 -0
- package/build/dist/Server/Types/Database/Permissions/DeletePermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js +2 -0
- package/build/dist/Server/Types/Database/Permissions/ReadPermission.js.map +1 -1
- package/build/dist/Server/Types/Database/Permissions/TenantPermission.js +68 -12
- package/build/dist/Server/Types/Database/Permissions/TenantPermission.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js +2 -2
- package/build/dist/Server/Utils/AI/CodeFix/FixRunBudget.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js +68 -8
- package/build/dist/Server/Utils/AI/SRE/AIInvestigationEngine.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js +1 -0
- package/build/dist/Server/Utils/AI/SRE/AlertInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js +1 -0
- package/build/dist/Server/Utils/AI/SRE/IncidentInvestigationRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.js +97 -0
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionIdentity.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js +17 -15
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/ExceptionSpikeDetector.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js +178 -54
- package/build/dist/Server/Utils/AI/SRE/Insights/Detectors/NewExceptionDetector.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js +65 -16
- package/build/dist/Server/Utils/AI/SRE/Insights/InsightStore.js.map +1 -1
- package/build/dist/Server/Utils/AI/SRE/InvestigationTldr.js +303 -0
- package/build/dist/Server/Utils/AI/SRE/InvestigationTldr.js.map +1 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js.map +1 -1
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +1261 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardViewConfig.js +47 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardViewConfig.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/ConnectorRegistry.js +42 -0
- package/build/dist/Server/Utils/DataSource/ConnectorRegistry.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/ClickHouseConnector.js +187 -0
- package/build/dist/Server/Utils/DataSource/Connectors/ClickHouseConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/ElasticsearchConnector.js +493 -0
- package/build/dist/Server/Utils/DataSource/Connectors/ElasticsearchConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/LokiConnector.js +399 -0
- package/build/dist/Server/Utils/DataSource/Connectors/LokiConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/MySqlConnector.js +195 -0
- package/build/dist/Server/Utils/DataSource/Connectors/MySqlConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/PostgresConnector.js +149 -0
- package/build/dist/Server/Utils/DataSource/Connectors/PostgresConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/PrometheusConnector.js +362 -0
- package/build/dist/Server/Utils/DataSource/Connectors/PrometheusConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/RestApiConnector.js +342 -0
- package/build/dist/Server/Utils/DataSource/Connectors/RestApiConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Connectors/SqlServerConnector.js +186 -0
- package/build/dist/Server/Utils/DataSource/Connectors/SqlServerConnector.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/EgressGuard.js +305 -0
- package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/HttpFetch.js +134 -0
- package/build/dist/Server/Utils/DataSource/HttpFetch.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/SqlQueryGuard.js +331 -0
- package/build/dist/Server/Utils/DataSource/SqlQueryGuard.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/SqlResultShaper.js +280 -0
- package/build/dist/Server/Utils/DataSource/SqlResultShaper.js.map +1 -0
- package/build/dist/Server/Utils/DataSource/Types.js +2 -0
- package/build/dist/Server/Utils/DataSource/Types.js.map +1 -0
- package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js +103 -9
- package/build/dist/Server/Utils/Database/PostgresErrorTranslator.js.map +1 -1
- package/build/dist/Server/Utils/InProcessMemo.js +113 -0
- package/build/dist/Server/Utils/InProcessMemo.js.map +1 -0
- package/build/dist/Server/Utils/InstanceHealth/TelemetryIngestion.js +406 -0
- package/build/dist/Server/Utils/InstanceHealth/TelemetryIngestion.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js +34 -0
- package/build/dist/Server/Utils/Monitor/MonitorMetricUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +14 -0
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Response.js +9 -1
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/SSRFProtection.js +192 -0
- package/build/dist/Server/Utils/SSRFProtection.js.map +1 -0
- package/build/dist/Server/Utils/StartServer.js +7 -0
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js +24 -0
- package/build/dist/Server/Utils/StatusPageSubscriberWebhook.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/CaptureSpan.js +25 -11
- package/build/dist/Server/Utils/Telemetry/CaptureSpan.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/IngestionTimestamp.js +32 -0
- package/build/dist/Server/Utils/Telemetry/IngestionTimestamp.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +92 -5
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +92 -6
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry.js +93 -6
- package/build/dist/Server/Utils/Telemetry.js.map +1 -1
- package/build/dist/Server/Utils/VendorAssets.js +121 -0
- package/build/dist/Server/Utils/VendorAssets.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +25 -4
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js +5 -0
- package/build/dist/Server/Utils/Workspace/Slack/Slack.js.map +1 -1
- package/build/dist/Types/Analytics/RevenueEvent.js +26 -0
- package/build/dist/Types/Analytics/RevenueEvent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js +27 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceChartComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceGaugeComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceTableComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.js +16 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardDataSourceValueComponent.js.map +1 -0
- package/build/dist/Types/DataSource/DataSourceLimits.js +47 -0
- package/build/dist/Types/DataSource/DataSourceLimits.js.map +1 -0
- package/build/dist/Types/DataSource/DataSourceQueryConfig.js +10 -0
- package/build/dist/Types/DataSource/DataSourceQueryConfig.js.map +1 -0
- package/build/dist/Types/DataSource/DataSourceTableResult.js +14 -0
- package/build/dist/Types/DataSource/DataSourceTableResult.js.map +1 -0
- package/build/dist/Types/DataSource/DataSourceType.js +183 -0
- package/build/dist/Types/DataSource/DataSourceType.js.map +1 -0
- package/build/dist/Types/Date.js +38 -3
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/Password.js +56 -0
- package/build/dist/Types/Password.js.map +1 -0
- package/build/dist/Types/Permission.js +42 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/UserAuthenticationStatus.js +2 -0
- package/build/dist/Types/UserAuthenticationStatus.js.map +1 -0
- package/build/dist/UI/Components/ErrorBoundary.js +388 -72
- package/build/dist/UI/Components/ErrorBoundary.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +10 -4
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/Modal/ConfirmModal.js +1 -1
- package/build/dist/UI/Components/Modal/ConfirmModal.js.map +1 -1
- package/build/dist/UI/Components/Modal/Modal.js +178 -19
- package/build/dist/UI/Components/Modal/Modal.js.map +1 -1
- package/build/dist/UI/Components/Modal/ModalFooter.js +3 -1
- package/build/dist/UI/Components/Modal/ModalFooter.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js +9 -6
- package/build/dist/UI/Components/MoreMenu/MoreMenu.js.map +1 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js +7 -1
- package/build/dist/UI/Components/MoreMenu/MoreMenuItem.js.map +1 -1
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js +119 -0
- package/build/dist/UI/Components/StatusPage/ResourceGrid.js.map +1 -0
- package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js +354 -0
- package/build/dist/UI/Components/StatusPage/ResourceGroupNavigator.js.map +1 -0
- package/build/dist/UI/Components/StatusPage/ResourceList.js +203 -0
- package/build/dist/UI/Components/StatusPage/ResourceList.js.map +1 -0
- package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js +1 -6
- package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +3 -8
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/TestDataSource.js +39 -0
- package/build/dist/UI/Utils/TestDataSource.js.map +1 -0
- package/build/dist/Utils/Analytics.js +4 -0
- package/build/dist/Utils/Analytics.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceChartComponent.js +79 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceChartComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.js +111 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceGaugeComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceShared.js +15 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceShared.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceTableComponent.js +62 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceTableComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceValueComponent.js +112 -0
- package/build/dist/Utils/Dashboard/Components/DashboardDataSourceValueComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/Index.js +16 -0
- package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
- package/build/dist/Utils/DataSource/DataSourceQueryText.js +64 -0
- package/build/dist/Utils/DataSource/DataSourceQueryText.js.map +1 -0
- package/build/dist/Utils/DataSource/DataSourceValueReducer.js +124 -0
- package/build/dist/Utils/DataSource/DataSourceValueReducer.js.map +1 -0
- package/build/dist/Utils/Metrics/MetricResourceAttributeUtil.js +281 -0
- package/build/dist/Utils/Metrics/MetricResourceAttributeUtil.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js +8 -6
- package/build/dist/Utils/NetworkDiscovery/ScanTargetUtil.js.map +1 -1
- package/build/dist/Utils/StatusPage/GroupHierarchyView.js +560 -0
- package/build/dist/Utils/StatusPage/GroupHierarchyView.js.map +1 -0
- package/build/dist/Utils/StatusPage/ResourceExplorer.js +519 -0
- package/build/dist/Utils/StatusPage/ResourceExplorer.js.map +1 -0
- package/jest.config.json +1 -0
- package/package.json +5 -1
- package/tsconfig.json +12 -10
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import OneUptimeDate from "../../Types/Date";
|
|
2
|
+
import moment from "moment-timezone";
|
|
3
|
+
import { afterAll, describe, expect, test } from "@jest/globals";
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* Run the whole suite in a non-UTC, DST-observing zone. Under TZ=UTC the
|
|
7
|
+
* local and UTC Date getters agree, so a getUTC* -> get* regression in the
|
|
8
|
+
* hand-rolled formatter would be invisible to every parity assertion below.
|
|
9
|
+
* America/New_York makes any local-getter mutation diverge from the
|
|
10
|
+
* moment-UTC oracle deterministically (including across DST transitions in
|
|
11
|
+
* the fuzz sweep). Node re-reads process.env.TZ on Date operations; the
|
|
12
|
+
* original value is restored afterAll so co-resident suites in the same
|
|
13
|
+
* worker keep the process default.
|
|
14
|
+
*/
|
|
15
|
+
const ORIGINAL_TZ: string | undefined = process.env["TZ"];
|
|
16
|
+
process.env["TZ"] = "America/New_York";
|
|
17
|
+
|
|
18
|
+
afterAll(() => {
|
|
19
|
+
if (ORIGINAL_TZ === undefined) {
|
|
20
|
+
delete process.env["TZ"];
|
|
21
|
+
} else {
|
|
22
|
+
process.env["TZ"] = ORIGINAL_TZ;
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
* Contract under test — the ClickHouse DateTime formatters that stamp every
|
|
28
|
+
* ingested telemetry row (createdAt, time, retentionDate, startTime/endTime).
|
|
29
|
+
*
|
|
30
|
+
* These used to be moment(...).utc().format("YYYY-MM-DD HH:mm:ss"), which
|
|
31
|
+
* allocates a moment wrapper and parses a format-token string PER CALL — and
|
|
32
|
+
* they are called several times per log record / span / metric datapoint.
|
|
33
|
+
* They are now a hand-rolled UTC formatter. The replacement must be
|
|
34
|
+
* byte-identical to what moment produced, because these strings are written
|
|
35
|
+
* straight into ClickHouse DateTime columns and compared/parsed downstream.
|
|
36
|
+
*
|
|
37
|
+
* The parity tests below therefore compare the new implementation against a
|
|
38
|
+
* live moment call, not against hardcoded strings, across edge cases and a
|
|
39
|
+
* deterministic fuzz sweep.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
function momentReference(date: Date): string {
|
|
43
|
+
return moment(date).utc().format("YYYY-MM-DD HH:mm:ss");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
describe("OneUptimeDate.toClickhouseDateTime", () => {
|
|
47
|
+
const edgeCases: Array<{ name: string; date: Date }> = [
|
|
48
|
+
{ name: "unix epoch", date: new Date(0) },
|
|
49
|
+
{ name: "one ms before epoch", date: new Date(-1) },
|
|
50
|
+
{ name: "one second before epoch", date: new Date(-1000) },
|
|
51
|
+
{
|
|
52
|
+
name: "pre-1970 date",
|
|
53
|
+
date: new Date(Date.UTC(1955, 10, 5, 6, 15, 30, 123)),
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: "leap day",
|
|
57
|
+
date: new Date(Date.UTC(2024, 1, 29, 23, 59, 59, 999)),
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "year rollover instant",
|
|
61
|
+
date: new Date(Date.UTC(2025, 11, 31, 23, 59, 59, 999)),
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: "first instant of a year",
|
|
65
|
+
date: new Date(Date.UTC(2026, 0, 1, 0, 0, 0, 0)),
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "single-digit month/day/hour/minute/second",
|
|
69
|
+
date: new Date(Date.UTC(2026, 2, 4, 5, 6, 7, 8)),
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: "three-digit year pads to 4 digits",
|
|
73
|
+
date: new Date(Date.UTC(999, 0, 2, 3, 4, 5)),
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "far future",
|
|
77
|
+
date: new Date(Date.UTC(2999, 11, 31, 12, 0, 0)),
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
for (const { name, date } of edgeCases) {
|
|
82
|
+
test(`matches the moment implementation it replaced: ${name}`, () => {
|
|
83
|
+
expect(OneUptimeDate.toClickhouseDateTime(date)).toBe(
|
|
84
|
+
momentReference(date),
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
test("fuzz sweep: parity with moment across ±100 years", () => {
|
|
90
|
+
/*
|
|
91
|
+
* Deterministic LCG so a failure is reproducible. 2000 samples spread
|
|
92
|
+
* across ~200 years of epoch millis, including sub-second components.
|
|
93
|
+
*/
|
|
94
|
+
let seed: number = 42;
|
|
95
|
+
const nextRandom: () => number = (): number => {
|
|
96
|
+
seed = (seed * 1103515245 + 12345) % 2147483648;
|
|
97
|
+
return seed / 2147483648;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const hundredYearsMs: number = 100 * 365.25 * 24 * 3600 * 1000;
|
|
101
|
+
|
|
102
|
+
for (let i: number = 0; i < 2000; i++) {
|
|
103
|
+
const ms: number = Math.floor((nextRandom() * 2 - 1) * hundredYearsMs);
|
|
104
|
+
const date: Date = new Date(ms);
|
|
105
|
+
expect(OneUptimeDate.toClickhouseDateTime(date)).toBe(
|
|
106
|
+
momentReference(date),
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("accepts ISO string input like the moment version did", () => {
|
|
112
|
+
const iso: string = "2026-08-10T13:45:59.500Z";
|
|
113
|
+
expect(OneUptimeDate.toClickhouseDateTime(iso)).toBe(
|
|
114
|
+
momentReference(new Date(iso)),
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("keeps moment's 'Invalid date' sentinel for NaN dates", () => {
|
|
119
|
+
const invalid: Date = new Date(NaN);
|
|
120
|
+
expect(OneUptimeDate.toClickhouseDateTime(invalid)).toBe(
|
|
121
|
+
momentReference(invalid),
|
|
122
|
+
);
|
|
123
|
+
expect(OneUptimeDate.toClickhouseDateTime(invalid)).toBe("Invalid date");
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("output is always UTC regardless of process timezone", () => {
|
|
127
|
+
// 2026-06-15T00:30:00Z is a different calendar day in UTC-negative zones.
|
|
128
|
+
const date: Date = new Date(Date.UTC(2026, 5, 15, 0, 30, 0));
|
|
129
|
+
expect(OneUptimeDate.toClickhouseDateTime(date)).toBe(
|
|
130
|
+
"2026-06-15 00:30:00",
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe("OneUptimeDate.toClickhouseDateTime64", () => {
|
|
136
|
+
/*
|
|
137
|
+
* Nano timestamps arrive as JS numbers, so a modern epoch date times 1e6
|
|
138
|
+
* exceeds Number.MAX_SAFE_INTEGER and the sub-second digits are already
|
|
139
|
+
* imprecise before this function sees them (pre-existing caller
|
|
140
|
+
* behavior). Exercise the extraction arithmetic with early-epoch dates
|
|
141
|
+
* whose nano values are exactly representable.
|
|
142
|
+
*/
|
|
143
|
+
test("extracts sub-second nanos from the unix-nano timestamp", () => {
|
|
144
|
+
const date: Date = new Date(Date.UTC(1970, 0, 20, 12, 0, 5, 0));
|
|
145
|
+
// 123456789 sub-second nanoseconds — exactly representable here.
|
|
146
|
+
const nano: number = date.getTime() * 1_000_000 + 123_456_789;
|
|
147
|
+
expect(OneUptimeDate.toClickhouseDateTime64(date, nano)).toBe(
|
|
148
|
+
"1970-01-20 12:00:05.123456789",
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("pads short sub-second nano fractions to 9 digits", () => {
|
|
153
|
+
const date: Date = new Date(Date.UTC(1970, 0, 20, 12, 0, 5, 0));
|
|
154
|
+
const nano: number = date.getTime() * 1_000_000 + 7;
|
|
155
|
+
expect(OneUptimeDate.toClickhouseDateTime64(date, nano)).toBe(
|
|
156
|
+
"1970-01-20 12:00:05.000000007",
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("falls back to Date milliseconds when nano timestamp is absent", () => {
|
|
161
|
+
const date: Date = new Date(Date.UTC(2026, 7, 10, 12, 0, 5, 123));
|
|
162
|
+
expect(OneUptimeDate.toClickhouseDateTime64(date)).toBe(
|
|
163
|
+
"2026-08-10 12:00:05.123000000",
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("falls back to Date milliseconds when nano timestamp is zero", () => {
|
|
168
|
+
// The implementation treats only nanoTimestamp > 0 as authoritative.
|
|
169
|
+
const date: Date = new Date(Date.UTC(2026, 7, 10, 12, 0, 5, 42));
|
|
170
|
+
expect(OneUptimeDate.toClickhouseDateTime64(date, 0)).toBe(
|
|
171
|
+
"2026-08-10 12:00:05.042000000",
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("base portion matches the moment implementation it replaced", () => {
|
|
176
|
+
const date: Date = new Date(Date.UTC(1999, 11, 31, 23, 59, 59, 999));
|
|
177
|
+
const formatted: string = OneUptimeDate.toClickhouseDateTime64(date);
|
|
178
|
+
expect(formatted.slice(0, 19)).toBe(momentReference(date));
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe("OneUptimeDate.getCurrentDate", () => {
|
|
183
|
+
test("returns the current wall-clock time as a plain Date", () => {
|
|
184
|
+
const before: number = Date.now();
|
|
185
|
+
const current: Date = OneUptimeDate.getCurrentDate();
|
|
186
|
+
const after: number = Date.now();
|
|
187
|
+
|
|
188
|
+
expect(current).toBeInstanceOf(Date);
|
|
189
|
+
expect(current.getTime()).toBeGreaterThanOrEqual(before);
|
|
190
|
+
expect(current.getTime()).toBeLessThanOrEqual(after);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MAXIMUM_PASSWORD_LENGTH,
|
|
3
|
+
MINIMUM_PASSWORD_LENGTH,
|
|
4
|
+
getPasswordValidationError,
|
|
5
|
+
} from "../../Types/Password";
|
|
6
|
+
import { describe, expect, test } from "@jest/globals";
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* The password policy, pinned.
|
|
12
|
+
*
|
|
13
|
+
* Before this module existed the only thing enforcing a minimum password
|
|
14
|
+
* length in OneUptime was `minLength: 6` typed inline into each browser form.
|
|
15
|
+
* A password that arrives from anywhere other than that form — the master-admin
|
|
16
|
+
* "set this user's password" endpoint being the obvious one — skipped the check
|
|
17
|
+
* entirely. So the value of this module is not that it is clever; it is that
|
|
18
|
+
* exactly one place decides what "acceptable" means, and both the form and the
|
|
19
|
+
* server ask it.
|
|
20
|
+
*
|
|
21
|
+
* That makes two classes of silent breakage worth guarding:
|
|
22
|
+
*
|
|
23
|
+
* 1. The RULES drifting. Every branch here is a rejection an operator or a
|
|
24
|
+
* user will read, and the order the branches run in is load-bearing (a
|
|
25
|
+
* six-space password is long enough and still not a password). Loosening
|
|
26
|
+
* one by accident is invisible until a bad password is already hashed and
|
|
27
|
+
* stored.
|
|
28
|
+
* 2. The rules drifting APART. If the Admin Dashboard form goes back to
|
|
29
|
+
* hardcoding a number, then retuning the policy here silently stops
|
|
30
|
+
* retuning the form, and the browser starts accepting what the server
|
|
31
|
+
* rejects — which surfaces to the operator as an opaque failed request.
|
|
32
|
+
* The last describe block below reads that form's source to pin it.
|
|
33
|
+
*
|
|
34
|
+
* The length cases are derived from the constants rather than written as 6 and
|
|
35
|
+
* 100, so that raising the minimum moves these tests with the policy instead of
|
|
36
|
+
* turning them into a wall of failures that has to be hand-edited (and might be
|
|
37
|
+
* hand-edited wrongly).
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
describe("getPasswordValidationError rejects a missing password", () => {
|
|
41
|
+
/*
|
|
42
|
+
* undefined, null and "" all mean "the operator submitted the form without
|
|
43
|
+
* typing anything", and each arrives from a different layer: undefined from a
|
|
44
|
+
* JSON body with no `password` key at all, null from a client that sent one
|
|
45
|
+
* explicitly, "" from an empty text input. They have to be one message
|
|
46
|
+
* because to the person reading it they are one mistake.
|
|
47
|
+
*/
|
|
48
|
+
test("undefined, null and the empty string all report the password as required", () => {
|
|
49
|
+
const expected: string = "Password is required.";
|
|
50
|
+
|
|
51
|
+
expect(getPasswordValidationError(undefined)).toBe(expected);
|
|
52
|
+
expect(getPasswordValidationError(null)).toBe(expected);
|
|
53
|
+
expect(getPasswordValidationError("")).toBe(expected);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("getPasswordValidationError rejects a blank password", () => {
|
|
58
|
+
test("a single space and a tab/newline run are blank, not passwords", () => {
|
|
59
|
+
expect(getPasswordValidationError(" ")).toBe("Password cannot be blank.");
|
|
60
|
+
expect(getPasswordValidationError("\t\n")).toBe(
|
|
61
|
+
"Password cannot be blank.",
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/*
|
|
66
|
+
* The case the blank check exists for.
|
|
67
|
+
*
|
|
68
|
+
* Six spaces satisfies the length rule on its own — nothing below this line
|
|
69
|
+
* would reject it — so without the blank branch a user could be given a
|
|
70
|
+
* password of pure whitespace that they can neither see nor reliably retype.
|
|
71
|
+
* If the blank check were ever reordered below the length check, or dropped
|
|
72
|
+
* because "the length check covers it", this is the case that would start
|
|
73
|
+
* passing silently, and the shorter whitespace strings above would not catch
|
|
74
|
+
* it because they fail the length rule anyway.
|
|
75
|
+
*/
|
|
76
|
+
test("a whitespace-only value long enough to pass the length rule is still rejected", () => {
|
|
77
|
+
const sixSpaces: string = " ".repeat(MINIMUM_PASSWORD_LENGTH);
|
|
78
|
+
|
|
79
|
+
expect(sixSpaces.length).toBeGreaterThanOrEqual(MINIMUM_PASSWORD_LENGTH);
|
|
80
|
+
expect(getPasswordValidationError(sixSpaces)).toBe(
|
|
81
|
+
"Password cannot be blank.",
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
/*
|
|
86
|
+
* Precedence in the other direction: blankness is reported before length even
|
|
87
|
+
* when the value is also too long. "Password cannot be more than 100
|
|
88
|
+
* characters." would be a baffling thing to show someone who pressed the
|
|
89
|
+
* space bar.
|
|
90
|
+
*/
|
|
91
|
+
test("blankness is reported ahead of the maximum-length rule", () => {
|
|
92
|
+
const overlongWhitespace: string = " ".repeat(MAXIMUM_PASSWORD_LENGTH + 1);
|
|
93
|
+
|
|
94
|
+
expect(getPasswordValidationError(overlongWhitespace)).toBe(
|
|
95
|
+
"Password cannot be blank.",
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("getPasswordValidationError enforces the length bounds", () => {
|
|
101
|
+
/*
|
|
102
|
+
* The four values either side of the two boundaries. Off-by-one here is the
|
|
103
|
+
* classic way a policy quietly becomes a different policy: a `<=` where a `<`
|
|
104
|
+
* belongs locks out everyone whose existing password is exactly at the
|
|
105
|
+
* minimum, and nothing else in the system would notice.
|
|
106
|
+
*/
|
|
107
|
+
test("one character below the minimum is rejected", () => {
|
|
108
|
+
const tooShort: string = "a".repeat(MINIMUM_PASSWORD_LENGTH - 1);
|
|
109
|
+
|
|
110
|
+
expect(getPasswordValidationError(tooShort)).toBe(
|
|
111
|
+
`Password cannot be less than ${MINIMUM_PASSWORD_LENGTH} characters.`,
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("exactly the minimum is accepted", () => {
|
|
116
|
+
const atMinimum: string = "a".repeat(MINIMUM_PASSWORD_LENGTH);
|
|
117
|
+
|
|
118
|
+
expect(getPasswordValidationError(atMinimum)).toBeNull();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("exactly the maximum is accepted", () => {
|
|
122
|
+
const atMaximum: string = "a".repeat(MAXIMUM_PASSWORD_LENGTH);
|
|
123
|
+
|
|
124
|
+
expect(getPasswordValidationError(atMaximum)).toBeNull();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("one character above the maximum is rejected", () => {
|
|
128
|
+
const tooLong: string = "a".repeat(MAXIMUM_PASSWORD_LENGTH + 1);
|
|
129
|
+
|
|
130
|
+
expect(getPasswordValidationError(tooLong)).toBe(
|
|
131
|
+
`Password cannot be more than ${MAXIMUM_PASSWORD_LENGTH} characters.`,
|
|
132
|
+
);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
/*
|
|
136
|
+
* Length is counted on the RAW value, never a trimmed one.
|
|
137
|
+
*
|
|
138
|
+
* This looks like a bug the first time you read it — " ab " is only two
|
|
139
|
+
* real characters — and the temptation is to trim before measuring. Doing so
|
|
140
|
+
* would be worse than lax: whatever the user typed is what gets hashed, so
|
|
141
|
+
* trimming for the length check while hashing the untrimmed value measures
|
|
142
|
+
* one string and stores another, and trimming for BOTH stores a secret the
|
|
143
|
+
* user never typed and then fails to match it when they type it again at
|
|
144
|
+
* login. Leading and trailing spaces in a password are the user's business.
|
|
145
|
+
*/
|
|
146
|
+
test("length is measured before trimming, so padded short passwords are accepted", () => {
|
|
147
|
+
const padded: string = " abcd ";
|
|
148
|
+
|
|
149
|
+
expect(padded.length).toBe(8);
|
|
150
|
+
expect(padded.trim().length).toBe(4);
|
|
151
|
+
expect(padded.trim().length).toBeLessThan(MINIMUM_PASSWORD_LENGTH);
|
|
152
|
+
|
|
153
|
+
expect(getPasswordValidationError(padded)).toBeNull();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
/*
|
|
157
|
+
* The mirror of the above: padding does not rescue a value that is genuinely
|
|
158
|
+
* too short in raw characters either. Nothing is trimmed, in either
|
|
159
|
+
* direction.
|
|
160
|
+
*/
|
|
161
|
+
test("a padded value that is still short raw is rejected on length", () => {
|
|
162
|
+
const shortEvenPadded: string = " ab ";
|
|
163
|
+
|
|
164
|
+
expect(shortEvenPadded.length).toBeLessThan(MINIMUM_PASSWORD_LENGTH);
|
|
165
|
+
expect(getPasswordValidationError(shortEvenPadded)).toBe(
|
|
166
|
+
`Password cannot be less than ${MINIMUM_PASSWORD_LENGTH} characters.`,
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe("the rejection messages tell the operator what to do", () => {
|
|
172
|
+
/*
|
|
173
|
+
* These messages are surfaced verbatim — the browser form shows them and the
|
|
174
|
+
* server wraps them in the exception it returns. A message that says only
|
|
175
|
+
* "Password is invalid." forces the operator to guess, and guessing at
|
|
176
|
+
* somebody else's password policy is exactly the loop this feature exists to
|
|
177
|
+
* remove. So each length message must actually contain its own limit, and
|
|
178
|
+
* must not quote the other one.
|
|
179
|
+
*/
|
|
180
|
+
test("the too-short message names the minimum and not the maximum", () => {
|
|
181
|
+
const message: string | null = getPasswordValidationError(
|
|
182
|
+
"a".repeat(MINIMUM_PASSWORD_LENGTH - 1),
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
expect(message).toContain(String(MINIMUM_PASSWORD_LENGTH));
|
|
186
|
+
expect(message).not.toContain(String(MAXIMUM_PASSWORD_LENGTH));
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("the too-long message names the maximum", () => {
|
|
190
|
+
const message: string | null = getPasswordValidationError(
|
|
191
|
+
"a".repeat(MAXIMUM_PASSWORD_LENGTH + 1),
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
expect(message).toContain(String(MAXIMUM_PASSWORD_LENGTH));
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
/*
|
|
198
|
+
* Every rejection is a complete sentence a human can read, not a code. Cheap
|
|
199
|
+
* to assert, and it catches the refactor that starts returning an enum or a
|
|
200
|
+
* key while leaving the `string | null` signature in place.
|
|
201
|
+
*/
|
|
202
|
+
test("every rejection is a non-empty sentence", () => {
|
|
203
|
+
const rejected: Array<string | undefined | null> = [
|
|
204
|
+
undefined,
|
|
205
|
+
null,
|
|
206
|
+
"",
|
|
207
|
+
" ",
|
|
208
|
+
"a".repeat(MINIMUM_PASSWORD_LENGTH - 1),
|
|
209
|
+
"a".repeat(MAXIMUM_PASSWORD_LENGTH + 1),
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
rejected.forEach((value: string | undefined | null): void => {
|
|
213
|
+
const message: string | null = getPasswordValidationError(value);
|
|
214
|
+
|
|
215
|
+
expect(typeof message).toBe("string");
|
|
216
|
+
expect((message as string).length).toBeGreaterThan(0);
|
|
217
|
+
expect(message as string).toMatch(/\.$/);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe("getPasswordValidationError accepts real passwords", () => {
|
|
223
|
+
test("an ordinary passphrase returns null", () => {
|
|
224
|
+
expect(getPasswordValidationError("correct-horse-battery")).toBeNull();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
/*
|
|
228
|
+
* null specifically, not undefined or false — callers branch on it (`const
|
|
229
|
+
* error = getPasswordValidationError(p); if (error) { throw ... }`), and the
|
|
230
|
+
* server-side guard turns a non-null return straight into a BadDataException.
|
|
231
|
+
*/
|
|
232
|
+
test("acceptance is signalled with null, not undefined", () => {
|
|
233
|
+
const result: string | null = getPasswordValidationError(
|
|
234
|
+
"a-perfectly-fine-password",
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
expect(result).toBeNull();
|
|
238
|
+
expect(result).not.toBeUndefined();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("passwords full of punctuation and symbols are not second-guessed", () => {
|
|
242
|
+
const accepted: Array<string> = [
|
|
243
|
+
"!@#$%^&*()",
|
|
244
|
+
"p@ssw0rd!",
|
|
245
|
+
" spaces inside are fine ",
|
|
246
|
+
"Ünïcödé-påsswörd",
|
|
247
|
+
];
|
|
248
|
+
|
|
249
|
+
accepted.forEach((value: string): void => {
|
|
250
|
+
expect(getPasswordValidationError(value)).toBeNull();
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
/*
|
|
255
|
+
* Pinning, not endorsing: `.length` is UTF-16 code units, so a password of
|
|
256
|
+
* three astral-plane emoji counts as six and clears the minimum. That is the
|
|
257
|
+
* behaviour every other OneUptime password form has always had (they all used
|
|
258
|
+
* the browser's own minLength, which counts the same way), so this module
|
|
259
|
+
* matching it is the point. Recorded here so that if someone ever switches to
|
|
260
|
+
* grapheme counting it is a deliberate, visible change rather than a silent
|
|
261
|
+
* one.
|
|
262
|
+
*/
|
|
263
|
+
test("length counts UTF-16 code units, matching every other password form", () => {
|
|
264
|
+
const threeEmoji: string = "🔐🔐🔐";
|
|
265
|
+
|
|
266
|
+
expect(threeEmoji.length).toBe(MINIMUM_PASSWORD_LENGTH);
|
|
267
|
+
expect(getPasswordValidationError(threeEmoji)).toBeNull();
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
describe("the exported constants are usable as a policy", () => {
|
|
272
|
+
test("both bounds are plain numbers", () => {
|
|
273
|
+
expect(typeof MINIMUM_PASSWORD_LENGTH).toBe("number");
|
|
274
|
+
expect(typeof MAXIMUM_PASSWORD_LENGTH).toBe("number");
|
|
275
|
+
|
|
276
|
+
expect(Number.isInteger(MINIMUM_PASSWORD_LENGTH)).toBe(true);
|
|
277
|
+
expect(Number.isInteger(MAXIMUM_PASSWORD_LENGTH)).toBe(true);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
/*
|
|
281
|
+
* A minimum at or above the maximum would make every password unacceptable
|
|
282
|
+
* while each individual branch still looked correct in review — the kind of
|
|
283
|
+
* mistake that only shows up when nobody can set a password any more.
|
|
284
|
+
*/
|
|
285
|
+
test("the minimum is positive and strictly below the maximum", () => {
|
|
286
|
+
expect(MINIMUM_PASSWORD_LENGTH).toBeGreaterThan(0);
|
|
287
|
+
expect(MINIMUM_PASSWORD_LENGTH).toBeLessThan(MAXIMUM_PASSWORD_LENGTH);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
/*
|
|
292
|
+
* The whole reason this module sits in Common/Types rather than Common/Server:
|
|
293
|
+
* the browser can import it, so the form and the endpoint cannot disagree.
|
|
294
|
+
*
|
|
295
|
+
* That only holds while the form actually imports it. A form that hardcodes
|
|
296
|
+
* `minLength: 6` again keeps working today and starts lying the moment the
|
|
297
|
+
* policy is retuned here — the browser would go on accepting six characters
|
|
298
|
+
* that the server now rejects, and the operator would see a request fail with
|
|
299
|
+
* no field marked. Nothing in a type-check or a render test catches that, so it
|
|
300
|
+
* is asserted against the form's source text.
|
|
301
|
+
*/
|
|
302
|
+
describe("the Admin Dashboard set-password form shares this policy", () => {
|
|
303
|
+
const SET_PASSWORD_FORM: string = path.join(
|
|
304
|
+
__dirname,
|
|
305
|
+
"..",
|
|
306
|
+
"..",
|
|
307
|
+
"..",
|
|
308
|
+
"App",
|
|
309
|
+
"FeatureSet",
|
|
310
|
+
"AdminDashboard",
|
|
311
|
+
"src",
|
|
312
|
+
"Pages",
|
|
313
|
+
"Users",
|
|
314
|
+
"View",
|
|
315
|
+
"Authentication.tsx",
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
/*
|
|
319
|
+
* Comments in that file discuss the policy in prose (and the module under
|
|
320
|
+
* test is itself heavily commented), so a bare `toContain` on the raw text
|
|
321
|
+
* could be satisfied by a comment while the code hardcoded a number. Strip
|
|
322
|
+
* comments first and assert only on code. Line comments are only stripped at
|
|
323
|
+
* a line start or after whitespace, so a "//" inside a URL literal survives.
|
|
324
|
+
*/
|
|
325
|
+
const readFormCode: () => string = (): string => {
|
|
326
|
+
const raw: string = fs.readFileSync(SET_PASSWORD_FORM, "utf8");
|
|
327
|
+
|
|
328
|
+
return raw
|
|
329
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
330
|
+
.replace(/(^|\s)\/\/.*$/gm, "$1");
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
test("the form imports the minimum from this module", () => {
|
|
334
|
+
const code: string = readFormCode();
|
|
335
|
+
|
|
336
|
+
expect(code).toMatch(
|
|
337
|
+
/import\s*\{[^}]*MINIMUM_PASSWORD_LENGTH[^}]*\}\s*from\s*["']Common\/Types\/Password["']/,
|
|
338
|
+
);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("both password fields validate against the imported constant", () => {
|
|
342
|
+
const code: string = readFormCode();
|
|
343
|
+
|
|
344
|
+
const usages: number = (
|
|
345
|
+
code.match(/minLength\s*:\s*MINIMUM_PASSWORD_LENGTH/g) || []
|
|
346
|
+
).length;
|
|
347
|
+
|
|
348
|
+
// The new password field and the confirmation field.
|
|
349
|
+
expect(usages).toBe(2);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
/*
|
|
353
|
+
* The assertion that actually bites: no numeric literal may appear as a
|
|
354
|
+
* minLength anywhere in the form, whatever number it is. Written against the
|
|
355
|
+
* digit rather than against "6" so that re-hardcoding the current value under
|
|
356
|
+
* a future, different policy still fails.
|
|
357
|
+
*/
|
|
358
|
+
test("no minLength in the form is a hardcoded number", () => {
|
|
359
|
+
const code: string = readFormCode();
|
|
360
|
+
|
|
361
|
+
expect(code).not.toMatch(/minLength\s*:\s*\d/);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
@@ -3,6 +3,13 @@ import ConfirmModal, {
|
|
|
3
3
|
ComponentProps,
|
|
4
4
|
} from "../../../UI/Components/Modal/ConfirmModal";
|
|
5
5
|
import { describe, expect, it, jest, beforeEach } from "@jest/globals";
|
|
6
|
+
/*
|
|
7
|
+
* The main entry, not "/extend-expect": the latter no longer ships type
|
|
8
|
+
* declarations. Without this import the jest-dom matchers below have no types
|
|
9
|
+
* of their own, and the suite only compiled when it happened to share a
|
|
10
|
+
* program with a file that did import them.
|
|
11
|
+
*/
|
|
12
|
+
import "@testing-library/jest-dom";
|
|
6
13
|
import { fireEvent, render, screen } from "@testing-library/react";
|
|
7
14
|
import React from "react";
|
|
8
15
|
|
|
@@ -222,6 +222,18 @@ describe("ErrorBoundary", () => {
|
|
|
222
222
|
expect(screen.queryByRole("alert")).not.toBeNull();
|
|
223
223
|
});
|
|
224
224
|
|
|
225
|
+
test("leads with a plain-language heading", () => {
|
|
226
|
+
render(
|
|
227
|
+
<ErrorBoundary>
|
|
228
|
+
<ThrowOnRender />
|
|
229
|
+
</ErrorBoundary>,
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
expect(
|
|
233
|
+
screen.queryByRole("heading", { name: "Something went wrong" }),
|
|
234
|
+
).not.toBeNull();
|
|
235
|
+
});
|
|
236
|
+
|
|
225
237
|
test("shows the underlying error message for support", () => {
|
|
226
238
|
render(
|
|
227
239
|
<ErrorBoundary>
|
|
@@ -234,6 +246,26 @@ describe("ErrorBoundary", () => {
|
|
|
234
246
|
).toContain("reading 'queryConfigs'");
|
|
235
247
|
});
|
|
236
248
|
|
|
249
|
+
/*
|
|
250
|
+
* The stack-shaped message is for support, not for the person who just hit
|
|
251
|
+
* the error. It stays in the DOM (so it can be read and copied) but behind
|
|
252
|
+
* a closed disclosure so the recovery actions lead.
|
|
253
|
+
*/
|
|
254
|
+
test("keeps the raw error message behind a closed disclosure", () => {
|
|
255
|
+
render(
|
|
256
|
+
<ErrorBoundary>
|
|
257
|
+
<ThrowOnRender message="Cannot read properties of undefined (reading 'queryConfigs')" />
|
|
258
|
+
</ErrorBoundary>,
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const disclosure: HTMLDetailsElement | null = screen
|
|
262
|
+
.getByTestId("error-boundary-error-message")
|
|
263
|
+
.closest("details");
|
|
264
|
+
|
|
265
|
+
expect(disclosure).not.toBeNull();
|
|
266
|
+
expect(disclosure!.open).toBe(false);
|
|
267
|
+
});
|
|
268
|
+
|
|
237
269
|
test("offers a support bundle and explains what it contains", () => {
|
|
238
270
|
render(
|
|
239
271
|
<ErrorBoundary>
|
|
@@ -247,6 +279,27 @@ describe("ErrorBoundary", () => {
|
|
|
247
279
|
expect(screen.queryByText(/scrubbed page URL/)).not.toBeNull();
|
|
248
280
|
expect(screen.queryByText(/never reads cookies/)).not.toBeNull();
|
|
249
281
|
});
|
|
282
|
+
|
|
283
|
+
/*
|
|
284
|
+
* A downloaded bundle is only useful if the user knows where to send it,
|
|
285
|
+
* so the address is on the fallback rather than a page they have to find.
|
|
286
|
+
*/
|
|
287
|
+
test("says where to send the support bundle", () => {
|
|
288
|
+
render(
|
|
289
|
+
<ErrorBoundary>
|
|
290
|
+
<ThrowOnRender />
|
|
291
|
+
</ErrorBoundary>,
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
const supportEmailLink: HTMLAnchorElement = screen.getByTestId(
|
|
295
|
+
"error-boundary-support-email",
|
|
296
|
+
) as HTMLAnchorElement;
|
|
297
|
+
|
|
298
|
+
expect(supportEmailLink.textContent).toBe("support@oneuptime.com");
|
|
299
|
+
expect(supportEmailLink.getAttribute("href")).toBe(
|
|
300
|
+
"mailto:support@oneuptime.com",
|
|
301
|
+
);
|
|
302
|
+
});
|
|
250
303
|
});
|
|
251
304
|
|
|
252
305
|
describe("reporting", () => {
|