@oneuptime/common 13.0.0 → 13.0.1
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/Alert.ts +55 -0
- package/Models/DatabaseModels/CloudResource.ts +45 -7
- package/Models/DatabaseModels/CodeRepository.ts +85 -0
- package/Models/DatabaseModels/GoogleSecOpsConnection.ts +57 -7
- package/Models/DatabaseModels/GoogleSecOpsConnectionRun.ts +263 -0
- package/Models/DatabaseModels/Incident.ts +55 -0
- package/Models/DatabaseModels/Index.ts +16 -0
- package/Models/DatabaseModels/ScheduledMaintenance.ts +55 -0
- package/Models/DatabaseModels/User.ts +51 -2
- package/Models/DatabaseModels/UserNotificationEmailRollupItem.ts +43 -2
- package/Models/DatabaseModels/UserWebAuthn.ts +17 -0
- package/Models/DatabaseModels/VMwareResource.ts +1149 -0
- package/Models/DatabaseModels/VMwareVCenter.ts +1122 -0
- package/Models/DatabaseModels/VMwareVCenterFeed.ts +625 -0
- package/Models/DatabaseModels/VMwareVCenterLabelRule.ts +514 -0
- package/Models/DatabaseModels/VMwareVCenterOwnerRule.ts +596 -0
- package/Models/DatabaseModels/VMwareVCenterOwnerTeam.ts +485 -0
- package/Models/DatabaseModels/VMwareVCenterOwnerUser.ts +484 -0
- package/Models/DatabaseModels/WorkflowVariable.ts +22 -1
- package/Server/API/AIAgentDataAPI.ts +79 -4
- package/Server/API/AIAgentGitHubAPI.ts +523 -0
- package/Server/API/AIChatAPI.ts +20 -1
- package/Server/API/BillingAPI.ts +33 -0
- package/Server/API/DashboardAPI.ts +10 -0
- package/Server/API/GitHubAPI.ts +47 -5
- package/Server/API/GoogleSecOpsConnectionAPI.ts +79 -0
- package/Server/API/UserWebAuthnAPI.ts +21 -1
- package/Server/API/VMwareResourceAPI.ts +143 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791800000000-SplitWebAuthnChallengeByPurpose.ts +68 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1791900000000-AddSeverityAndStateToNotificationEmailRollup.ts +26 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1792000000000-AddVMwareTables.ts +636 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1792100000000-AddGitHubCommandSettingsToCodeRepository.ts +47 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1792200000000-AddUserWebAuthnPurpose.ts +17 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1792300000000-AddGoogleSecOpsDiagnostics.ts +83 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +12 -0
- package/Server/Middleware/IdentityRateLimit.ts +101 -0
- package/Server/Services/AIRunService.ts +13 -0
- package/Server/Services/BillingPaymentMethodService.ts +99 -28
- package/Server/Services/BillingService.ts +287 -20
- package/Server/Services/CloudResourceInstanceService.ts +70 -0
- package/Server/Services/ExceptionAggregationService.ts +1 -0
- package/Server/Services/GoogleSecOpsConnectionRunService.ts +10 -0
- package/Server/Services/GoogleSecOpsConnectionService.ts +12 -0
- package/Server/Services/Index.ts +16 -0
- package/Server/Services/LogAggregationService.ts +1 -0
- package/Server/Services/MetricAggregationService.ts +1 -0
- package/Server/Services/MobilePasskeyLoginService.ts +216 -0
- package/Server/Services/MonitorService.ts +51 -1
- package/Server/Services/MonitorTemplateService.ts +91 -29
- package/Server/Services/NetworkDeviceAutoImportRuleEngineService.ts +132 -1
- package/Server/Services/NetworkDeviceAutoImportRuleService.ts +176 -2
- package/Server/Services/OpenTelemetryIngestService.ts +17 -0
- package/Server/Services/PayAsYouGoBillingService.ts +446 -0
- package/Server/Services/ProjectService.ts +46 -2
- package/Server/Services/ServerlessFunctionInstanceService.ts +71 -0
- package/Server/Services/TeamMemberService.ts +96 -36
- package/Server/Services/TelemetryIngestionKeyService.ts +80 -10
- package/Server/Services/TelemetryUsageBillingService.ts +95 -5
- package/Server/Services/TraceAggregationService.ts +5 -4
- package/Server/Services/UserWebAuthnService.ts +592 -133
- package/Server/Services/VMwareResourceService.ts +648 -0
- package/Server/Services/VMwareVCenterFeedService.ts +99 -0
- package/Server/Services/VMwareVCenterLabelRuleEngineService.ts +240 -0
- package/Server/Services/VMwareVCenterLabelRuleService.ts +14 -0
- package/Server/Services/VMwareVCenterOwnerRuleEngineService.ts +252 -0
- package/Server/Services/VMwareVCenterOwnerRuleService.ts +14 -0
- package/Server/Services/VMwareVCenterOwnerTeamService.ts +139 -0
- package/Server/Services/VMwareVCenterOwnerUserService.ts +133 -0
- package/Server/Services/VMwareVCenterService.ts +629 -0
- package/Server/Services/WorkflowVariableService.ts +203 -0
- package/Server/Types/Billing/MeteredPlan/ActiveMonitoringMeteredPlan.ts +17 -0
- package/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.ts +31 -1
- package/Server/Utils/CodeRepository/GitHub/GitHub.ts +7 -0
- package/Server/Utils/CodeRepository/GitHub/GitHubAgentTaskTrigger.ts +341 -0
- package/Server/Utils/CodeRepository/GitHub/GitHubCommandAuthorizer.ts +274 -0
- package/Server/Utils/CodeRepository/GitHub/GitHubCommandParser.ts +375 -0
- package/Server/Utils/CodeRepository/GitHub/GitHubConversation.ts +846 -0
- package/Server/Utils/CodeRepository/GitHub/GitHubReplyComposer.ts +360 -0
- package/Server/Utils/CodeRepository/GitHub/GitHubRunReply.ts +421 -0
- package/Server/Utils/CodeRepository/GitHub/GitHubWebhookEvents.ts +140 -0
- package/Server/Utils/CodeRepository/GitHub/GitHubWebhookHandler.ts +906 -0
- package/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.ts +143 -0
- package/Server/Utils/DataSource/EgressGuard.ts +160 -15
- package/Server/Utils/Database/TruncateColumnValue.ts +1 -1
- package/Server/Utils/EmailRollup/EmailRollupFlushRunner.ts +2 -0
- package/Server/Utils/EmailRollup/EmailRollupRenderer.ts +33 -0
- package/Server/Utils/EmailRollup/EmailRollupWriter.ts +44 -0
- package/Server/Utils/LogRedaction.ts +2 -1
- package/Server/Utils/Monitor/MonitorAlert.ts +1 -1
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +302 -3
- package/Server/Utils/Monitor/MonitorMaintenanceSuppression.ts +26 -4
- package/Server/Utils/Monitor/MonitorResourceContext.ts +5 -3
- package/Server/Utils/Monitor/MonitorStepResourceIdentity.ts +14 -0
- package/Server/Utils/Monitor/MonitorTemplateUtil.ts +1 -0
- package/Server/Utils/Monitor/SeriesResourceLabels.ts +26 -0
- package/Server/Utils/Monitor/SeriesResourceLinker.ts +28 -5
- package/Server/Utils/ResourceFeed/ResourceFeedUtil.ts +3 -3
- package/Server/Utils/SecurityEvent/ConnectorErrorMessage.ts +14 -9
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.ts +116 -84
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.ts +594 -195
- package/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.ts +466 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +5 -0
- package/Server/Utils/Telemetry/ResourceHeartbeat.ts +2 -1
- package/Server/Utils/Telemetry/TelemetryEntity.ts +181 -1
- package/Server/Utils/Telemetry/VMwareSnapshotScan.ts +1352 -0
- package/Tests/App/Accounts/LoginPasskey.test.tsx +529 -0
- package/Tests/App/Accounts/MobilePasskey.test.tsx +620 -0
- package/Tests/App/Accounts/PasswordRequirements.test.tsx +254 -0
- package/Tests/App/Accounts/RegisterPassword.test.tsx +553 -0
- package/Tests/App/Dashboard/AskAiAvailability.test.ts +224 -0
- package/Tests/App/Dashboard/AskAiDisabled.test.tsx +432 -0
- package/Tests/App/Dashboard/BackupCodes.test.tsx +414 -0
- package/Tests/App/Dashboard/BillingPaidUsageFlow.test.tsx +530 -0
- package/Tests/App/Dashboard/BillingPaymentMethodForm.test.tsx +195 -0
- package/Tests/App/Dashboard/BreadcrumbCoverage.test.tsx +199 -0
- package/Tests/App/Dashboard/CloudDocumentationCard.test.tsx +198 -0
- package/Tests/App/Dashboard/CloudDocumentationMarkdown.test.ts +215 -0
- package/Tests/App/Dashboard/CloudFleetSummary.test.ts +183 -0
- package/Tests/App/Dashboard/CloudFleetSummaryComponent.test.tsx +248 -0
- package/Tests/App/Dashboard/CloudResourceTelemetryScope.test.ts +215 -0
- package/Tests/App/Dashboard/CriteriaFilterDefaults.test.ts +3 -0
- package/Tests/App/Dashboard/DashboardHeaderRequests.test.tsx +441 -0
- package/Tests/App/Dashboard/DashboardLazyPages.test.tsx +458 -0
- package/Tests/App/Dashboard/DashboardToolbarPopupLayering.test.tsx +566 -0
- package/Tests/App/Dashboard/DiscoveryScanLivePage.test.tsx +376 -0
- package/Tests/App/Dashboard/DiscoveryScanLiveUpdates.test.tsx +642 -0
- package/Tests/App/Dashboard/DiscoveryScanProgress.test.tsx +267 -0
- package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +69 -231
- package/Tests/App/Dashboard/GoogleSecOpsConnectionsErrors.test.tsx +244 -0
- package/Tests/App/Dashboard/GoogleSecOpsDiagnostics.test.tsx +724 -0
- package/Tests/App/Dashboard/MonitorTemplateSyncFields.test.tsx +539 -0
- package/Tests/App/Dashboard/OnCallLayerCardOverrides.test.tsx +24 -7
- package/Tests/App/Dashboard/OnCallOverridePresentation.test.ts +496 -0
- package/Tests/App/Dashboard/OnCallScheduleOverrideClarity.test.tsx +677 -0
- package/Tests/App/Dashboard/OnCallSchedulePreviewOverrides.test.tsx +31 -3
- package/Tests/App/Dashboard/PaidUsageConsent.test.tsx +180 -0
- package/Tests/App/Dashboard/PasskeySettings.test.tsx +1220 -0
- package/Tests/App/Dashboard/PayAsYouGoConsentGate.test.tsx +176 -275
- package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +380 -233
- package/Tests/App/Dashboard/ProductMenuBreadcrumbs.test.tsx +3 -23
- package/Tests/App/Dashboard/ResourceFeed.test.tsx +1 -0
- package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +8 -1
- package/Tests/App/Dashboard/TwoFactorStatus.test.tsx +260 -0
- package/Tests/App/Dashboard/VMwareDashboardWidgets.test.ts +573 -0
- package/Tests/App/Dashboard/WidgetCatalog.test.ts +2 -0
- package/Tests/App/Dashboard/WorkflowVariablesTable.test.tsx +768 -0
- package/Tests/App/StatusPage/StatusPageOverviewLiveAndSearch.test.tsx +532 -40
- package/Tests/App/StatusPage/StatusPageResourceSearchBox.test.tsx +19 -2
- package/Tests/Fixtures/MobilePasskeyUser.json +8 -0
- package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +25 -6
- package/Tests/Models/DatabaseModels/VMwareModels.test.ts +1134 -0
- package/Tests/Models/RunnerVersionLabel.test.ts +4 -2
- package/Tests/Server/API/AIChatProvidersAIEnabled.test.ts +259 -0
- package/Tests/Server/API/AIChatRouteAuthorization.test.ts +31 -2
- package/Tests/Server/API/BillingPayAsYouGoStatus.test.ts +116 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +29 -1
- package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +13 -2
- package/Tests/Server/API/GitHubWebhookSignature.test.ts +368 -0
- package/Tests/Server/API/GoogleSecOpsConnectionAPI.test.ts +248 -0
- package/Tests/Server/API/UserWebAuthnAuthenticationOptionsRoute.test.ts +330 -0
- package/Tests/Server/API/UserWebAuthnPasskeyAPI.test.ts +172 -0
- package/Tests/Server/API/VMwareResourceAPI.test.ts +546 -0
- package/Tests/Server/Infrastructure/Postgres/UserNotificationEmailRollupMetadataMigration.test.ts +85 -0
- package/Tests/Server/Infrastructure/Postgres/UserNotificationEmailRollupTableMigration.test.ts +34 -3
- package/Tests/Server/Middleware/PasskeyRateLimit.test.ts +141 -0
- package/Tests/Server/Services/AutoImportRuleRearmRecentScans.test.ts +343 -0
- package/Tests/Server/Services/AutoImportRuleWriteRearmsScans.test.ts +381 -0
- package/Tests/Server/Services/BillingPaymentMethodServiceSync.test.ts +175 -0
- package/Tests/Server/Services/BillingService.test.ts +26 -11
- package/Tests/Server/Services/BillingServiceMeteredPaymentAuthorization.test.ts +245 -0
- package/Tests/Server/Services/BillingServicePaymentMethodReads.test.ts +362 -0
- package/Tests/Server/Services/CloudResourceInstanceStalePrune.test.ts +188 -0
- package/Tests/Server/Services/DiscoveryScanClaimHookFreeSafety.test.ts +16 -2
- package/Tests/Server/Services/GoogleSecOpsDiagnosticsModel.test.ts +153 -0
- package/Tests/Server/Services/HookFreeWriteLengthClamp.test.ts +13 -0
- package/Tests/Server/Services/InventoryItemPromotionGate.test.ts +5 -0
- package/Tests/Server/Services/InventoryUpsertSchemaParity.test.ts +36 -0
- package/Tests/Server/Services/InventoryUpsertTruncationGuard.test.ts +151 -0
- package/Tests/Server/Services/MobilePasskeyLoginIntegration.test.ts +231 -0
- package/Tests/Server/Services/MobilePasskeyLoginService.test.ts +315 -0
- package/Tests/Server/Services/MonitorPayAsYouGoBilling.test.ts +302 -0
- package/Tests/Server/Services/MonitorTemplateServiceFieldSync.test.ts +994 -0
- package/Tests/Server/Services/NetworkDeviceAutoImportRuleService.test.ts +22 -0
- package/Tests/Server/Services/OpenTelemetryResourceRetentionMemo.test.ts +7 -1
- package/Tests/Server/Services/PayAsYouGoBillingService.test.ts +857 -0
- package/Tests/Server/Services/PayAsYouGoStripeRateLimitBudget.test.ts +379 -0
- package/Tests/Server/Services/ProjectServiceExtendTrial.test.ts +99 -1
- package/Tests/Server/Services/ProjectServiceSubscriptionSeats.test.ts +134 -0
- package/Tests/Server/Services/ResourceFeedServices.test.ts +3 -2
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +32 -6
- package/Tests/Server/Services/SecurityEventLastErrorColumnWidth.test.ts +363 -175
- package/Tests/Server/Services/ServerlessFunctionInstanceStalePrune.test.ts +200 -0
- package/Tests/Server/Services/TeamMemberAutoAcceptInvitation.test.ts +3 -0
- package/Tests/Server/Services/TeamMemberBillingSeatSync.test.ts +762 -0
- package/Tests/Server/Services/TeamMemberInviteRegistrationToken.test.ts +3 -0
- package/Tests/Server/Services/TelemetryIngestionKeyPolicyResolution.test.ts +72 -0
- package/Tests/Server/Services/TelemetryIngestionKeyValidation.test.ts +17 -4
- package/Tests/Server/Services/TelemetryPayAsYouGoBilling.test.ts +355 -0
- package/Tests/Server/Services/TraceAnalyticsResourceDisplayNames.test.ts +41 -4
- package/Tests/Server/Services/UserNotificationEmailRollupModels.test.ts +37 -0
- package/Tests/Server/Services/UserWebAuthnPasskeyIntegration.test.ts +401 -0
- package/Tests/Server/Services/UserWebAuthnPasskeyService.test.ts +769 -0
- package/Tests/Server/Services/UserWebAuthnUserVerification.test.ts +649 -0
- package/Tests/Server/Services/VMwareResourceService.test.ts +945 -0
- package/Tests/Server/Services/VMwareVCenterService.test.ts +722 -0
- package/Tests/Server/Services/WorkflowVariableRenameUniqueness.test.ts +842 -0
- package/Tests/Server/TestingUtils/PasskeyHttpIntegration.ts +102 -0
- package/Tests/Server/TestingUtils/SecurityKey.ts +464 -0
- package/Tests/Server/TestingUtils/WebAuthnServiceMocks.ts +60 -0
- package/Tests/Server/Types/Billing/PayAsYouGoMeteredAuthorization.test.ts +573 -0
- package/Tests/Server/Types/Database/Permissions/ProjectCustomerSupportAccessColumn.test.ts +261 -0
- package/Tests/Server/Types/Database/Permissions/WorkflowVariableColumnPermission.test.ts +95 -0
- package/Tests/Server/Utils/CodeRepository/GitHubAgentTaskTrigger.test.ts +2101 -0
- package/Tests/Server/Utils/CodeRepository/GitHubCommandAuthorizer.test.ts +883 -0
- package/Tests/Server/Utils/CodeRepository/GitHubCommandParser.test.ts +1474 -0
- package/Tests/Server/Utils/CodeRepository/GitHubConversation.test.ts +1526 -0
- package/Tests/Server/Utils/CodeRepository/GitHubReplyComposer.test.ts +1389 -0
- package/Tests/Server/Utils/CodeRepository/GitHubRunReply.test.ts +1707 -0
- package/Tests/Server/Utils/CodeRepository/GitHubWebhookHandler.test.ts +2202 -0
- package/Tests/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.test.ts +345 -0
- package/Tests/Server/Utils/DataSource/EgressGuard.test.ts +896 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupMetadataIntegration.test.ts +246 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupRenderer.test.ts +299 -1
- package/Tests/Server/Utils/EmailRollup/EmailRollupTestHarness.ts +6 -0
- package/Tests/Server/Utils/EmailRollup/EmailRollupWriter.test.ts +185 -0
- package/Tests/Server/Utils/LogRedaction.test.ts +33 -0
- package/Tests/Server/Utils/Monitor/CapturedMetricAttributeUtil.test.ts +2 -1
- package/Tests/Server/Utils/Monitor/Criteria/TelemetryStepMetricAlias.test.ts +10 -0
- package/Tests/Server/Utils/Monitor/MonitorAlertResourceLinking.test.ts +66 -1
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +362 -0
- package/Tests/Server/Utils/Monitor/MonitorDependencySuppressionCreatorSkip.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/MonitorIncidentResourceLinking.test.ts +60 -1
- package/Tests/Server/Utils/Monitor/MonitorMaintenanceSuppression.test.ts +60 -0
- package/Tests/Server/Utils/Monitor/MonitorRootCauseTargetResolution.test.ts +423 -0
- package/Tests/Server/Utils/Monitor/MonitorStepResourceIdentity.test.ts +6 -0
- package/Tests/Server/Utils/Monitor/MonitorSummaryPersistence.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/PerSeriesSecondEntityAlerting.test.ts +1 -0
- package/Tests/Server/Utils/Monitor/SeriesResourceLinker.test.ts +46 -2
- package/Tests/Server/Utils/SecurityEvent/ConnectorErrorMessage.test.ts +81 -45
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.test.ts +711 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClickhouseIntegration.test.ts +223 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsClient.test.ts +69 -1
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsDiagnostics.test.ts +838 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsErrorHandling.test.ts +241 -28
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsHttpIntegration.test.ts +651 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPoller.test.ts +11 -0
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerFailureTaxonomy.test.ts +13 -12
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsPollerHardening.test.ts +112 -127
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsRequestContract.test.ts +12 -18
- package/Tests/Server/Utils/SecurityEvent/GoogleSecOpsResponseParsing.test.ts +43 -0
- package/Tests/Server/Utils/Telemetry/TelemetryEntity.test.ts +580 -0
- package/Tests/Server/Utils/Telemetry/VMwareSnapshotScan.test.ts +1748 -0
- package/Tests/Types/Cloud/CloudPlatform.test.ts +574 -0
- package/Tests/Types/Monitor/MetricAndProfileDefaultCriteria.test.ts +2 -0
- package/Tests/Types/Monitor/MonitorCriteriaMetricVariables.test.ts +9 -0
- package/Tests/Types/Monitor/MonitorStepDefaultTelemetryConfig.test.ts +1 -0
- package/Tests/Types/Monitor/MonitorStepInfraMonitorUtils.test.ts +53 -8
- package/Tests/Types/Monitor/MonitorStepInfrastructureMonitors.test.ts +97 -0
- package/Tests/Types/Monitor/MonitorStepMetricViewConfigUtil.test.ts +1 -0
- package/Tests/Types/Monitor/MonitorStepTemplateSyncFields.test.ts +107 -0
- package/Tests/Types/Monitor/MonitorTemplateSyncField.test.ts +172 -0
- package/Tests/Types/Monitor/MonitorType.test.ts +1 -0
- package/Tests/Types/Monitor/MonitorTypeKeywords.test.ts +2 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationAlertDebuggability.test.ts +1 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCatalog.test.ts +9 -0
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationCoverage.test.ts +1 -1
- package/Tests/Types/Monitor/Recommendation/MonitorRecommendationNotificationMode.test.ts +1 -1
- package/Tests/Types/Monitor/SeriesContext/SeriesDebugHints.test.ts +131 -0
- package/Tests/Types/Monitor/SeriesContext/SeriesLabelDisplay.test.ts +10 -0
- package/Tests/Types/Monitor/VMwareAlertTemplates.test.ts +1608 -0
- package/Tests/Types/Monitor/VMwareMetricCatalog.test.ts +488 -0
- package/Tests/Types/OnCallDutyPolicy/OverrideProvenance.test.ts +295 -0
- package/Tests/Types/Runner/RunnerLiveStatus.test.ts +2 -2
- package/Tests/Types/SignupPassword.test.ts +205 -0
- package/Tests/Types/StatusPage/StatusPageLanguage.test.ts +44 -0
- package/Tests/Types/SupportedLanguages.test.ts +119 -0
- package/Tests/Types/Telemetry/EntityTypeGroups.test.ts +19 -0
- package/Tests/UI/ColorPickerBuild.test.ts +86 -0
- package/Tests/UI/Components/Breadcrumbs.test.tsx +32 -0
- package/Tests/UI/Components/Forms/ModelFormPreservesUserInput.test.tsx +937 -0
- package/Tests/UI/Components/Forms/ModelFormWriteOnlyColumnSelect.test.tsx +230 -0
- package/Tests/UI/Components/Forms/ValidationFormSteps.test.ts +3 -4
- package/Tests/UI/Components/LabelRuleImportExport.test.tsx +907 -0
- package/Tests/UI/Components/ModelTable/ModelTableCreateAfterEdit.test.tsx +514 -0
- package/Tests/UI/Components/MonitorGraphs/Uptime.test.tsx +301 -0
- package/Tests/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.test.ts +2 -1
- package/Tests/UI/Components/TooltipLazyContent.test.tsx +373 -0
- package/Tests/UI/MicrosoftTeams/MicrosoftTeamsMessagingEndpointGuidance.test.tsx +43 -12
- package/Tests/UI/ReactDomBuild.test.ts +104 -0
- package/Tests/UI/ReactRouterSingletonBuild.test.ts +11 -1
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +148 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +29 -0
- package/Tests/UI/Utils/DashboardStackingLayers.test.ts +197 -0
- package/Tests/UI/Utils/LabelRuleImportExport.test.ts +546 -0
- package/Tests/UI/Utils/ModelAPITenantHeader.test.ts +109 -2
- package/Tests/UI/Utils/UseDashboardGridDnd.test.tsx +7 -2
- package/Tests/UI/Utils/UserEmailRegistrationStatus.test.tsx +295 -0
- package/Tests/UI/Utils/WebAuthn.test.ts +265 -0
- package/Tests/Utils/Dashboard/Components/DashboardComponentDefaults.test.ts +5 -0
- package/Tests/Utils/Dashboard/Components/DashboardVMwareListComponents.test.ts +548 -0
- package/Tests/Utils/Dashboard/DashboardResourceListSharedArgs.test.ts +17 -3
- package/Tests/Utils/LabelRuleImportExport.test.ts +583 -0
- package/Tests/Utils/Monitor/MonitorSummarySnapshotUtil.test.ts +1 -0
- package/Tests/Utils/Monitor/MonitorTemplateSyncUtil.test.ts +590 -0
- package/Tests/Utils/RequestFailedDetails.test.ts +325 -0
- package/Tests/Utils/Telemetry/CloudInstanceIdentity.test.ts +255 -0
- package/Tests/Utils/Telemetry/EntityRelationship.test.ts +167 -0
- package/Tests/Utils/WebAuthnTestUtil.ts +79 -0
- package/Tests/__mocks__/simplewebauthn.js +33 -4
- package/Types/AI/CodeFixTaskContext.ts +122 -0
- package/Types/AI/CodeFixTaskType.ts +62 -0
- package/Types/Accounts/AccountsLanguage.ts +1 -0
- package/Types/AdminDashboard/AdminDashboardLanguage.ts +1 -0
- package/Types/Calendar/CalendarEvent.ts +16 -0
- package/Types/Cloud/CloudPlatform.ts +372 -0
- package/Types/CodeRepository/GitHubCommand.ts +69 -0
- package/Types/Dashboard/DashboardComponentType.ts +2 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardVMwareHostListComponent.ts +23 -0
- package/Types/Dashboard/DashboardComponents/DashboardVMwareVirtualMachineListComponent.ts +30 -0
- package/Types/Dashboard/DashboardLanguage.ts +1 -0
- package/Types/Dashboard/DashboardTemplates.ts +314 -0
- package/Types/Docs/DocsLanguage.ts +1 -0
- package/Types/Exception/EgressGuardException.ts +65 -0
- package/Types/Icon/IconProp.ts +1 -0
- package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +36 -0
- package/Types/Monitor/MonitorCriteriaInstance.ts +1 -0
- package/Types/Monitor/MonitorStep.ts +78 -17
- package/Types/Monitor/MonitorStepMetricViewConfigUtil.ts +1 -0
- package/Types/Monitor/MonitorStepVMwareMonitor.ts +127 -0
- package/Types/Monitor/MonitorTemplateSyncField.ts +291 -0
- package/Types/Monitor/MonitorType.ts +24 -0
- package/Types/Monitor/Recommendation/MonitorRecommendationCatalog.ts +33 -0
- package/Types/Monitor/Recommendation/MonitorRecommendationTypes.ts +2 -0
- package/Types/Monitor/Recommendation/MonitorRecommendationUtil.ts +3 -0
- package/Types/Monitor/SeriesContext/SeriesDebugHints.ts +158 -0
- package/Types/Monitor/SeriesContext/SeriesLabelDisplay.ts +61 -0
- package/Types/Monitor/VMwareAlertTemplates.ts +1070 -0
- package/Types/Monitor/VMwareMetricCatalog.ts +914 -0
- package/Types/OnCallDutyPolicy/ScheduleShiftUtil.ts +29 -0
- package/Types/OnCallDutyPolicy/UserOverrideUtil.ts +16 -0
- package/Types/Password.ts +84 -5
- package/Types/Permission.ts +265 -0
- package/Types/Probe/RequestFailedDetails.ts +7 -0
- package/Types/Runner/RunnerLiveStatus.ts +1 -1
- package/Types/SecurityEvent/GoogleSecOpsDiagnostics.ts +56 -0
- package/Types/StatusPage/StatusPageLanguage.ts +1 -0
- package/Types/Telemetry/EntityType.ts +21 -0
- package/Types/Telemetry/ServiceType.ts +1 -0
- package/Typings/Index.d.ts +63 -0
- package/UI/Components/Breadcrumbs/Breadcrumbs.tsx +2 -2
- package/UI/Components/Calendar/Calendar.css +78 -0
- package/UI/Components/Calendar/Calendar.tsx +28 -3
- package/UI/Components/Forms/BasicForm.tsx +44 -2
- package/UI/Components/Forms/Fields/ColorPicker.tsx +2 -1
- package/UI/Components/Forms/Fields/FormField.tsx +16 -3
- package/UI/Components/Forms/ModelForm.tsx +159 -5
- package/UI/Components/Forms/Types/Field.ts +8 -1
- package/UI/Components/Graphs/DayUptimeGraph.tsx +8 -10
- package/UI/Components/Icon/Icon.tsx +16 -0
- package/UI/Components/Input/Input.tsx +42 -7
- package/UI/Components/LabelRule/ImportLabelRulesModal.tsx +441 -0
- package/UI/Components/LabelRule/LabelRuleTable.tsx +142 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +22 -5
- package/UI/Components/ModelTable/ModelTable.tsx +14 -6
- package/UI/Components/MonitorGraphs/Uptime.tsx +17 -27
- package/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.ts +3 -1
- package/UI/Components/Tooltip/Tooltip.tsx +52 -6
- package/UI/Utils/DashboardStackingLayers.ts +85 -0
- package/UI/Utils/LabelRuleImportExport.ts +310 -0
- package/UI/Utils/ModelAPI/ModelAPI.ts +4 -3
- package/UI/Utils/TelemetryService.ts +4 -3
- package/UI/Utils/UseDashboardGridDnd.ts +4 -1
- package/UI/Utils/UserEmailRegistrationStatus.ts +33 -5
- package/UI/Utils/WebAuthn.ts +220 -0
- package/UI/esbuild-config.js +4 -0
- package/Utils/API.ts +34 -0
- package/Utils/Dashboard/Components/DashboardVMwareHostListComponent.ts +38 -0
- package/Utils/Dashboard/Components/DashboardVMwareResourceListShared.ts +70 -0
- package/Utils/Dashboard/Components/DashboardVMwareVirtualMachineListComponent.ts +76 -0
- package/Utils/Dashboard/Components/Index.ts +16 -0
- package/Utils/LabelRuleImportExport.ts +496 -0
- package/Utils/Monitor/MonitorTemplateSyncUtil.ts +164 -0
- package/Utils/NetworkDiscovery/DiscoveryScanStatus.ts +8 -0
- package/Utils/Telemetry/CloudInstanceIdentity.ts +94 -0
- package/Utils/Telemetry/EntityKey.ts +19 -0
- package/Utils/Telemetry/EntityRelationship.ts +19 -0
- package/build/dist/Models/DatabaseModels/Alert.js +54 -0
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CloudResource.js +44 -7
- package/build/dist/Models/DatabaseModels/CloudResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CodeRepository.js +87 -0
- package/build/dist/Models/DatabaseModels/CodeRepository.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GoogleSecOpsConnection.js +60 -7
- package/build/dist/Models/DatabaseModels/GoogleSecOpsConnection.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GoogleSecOpsConnectionRun.js +287 -0
- package/build/dist/Models/DatabaseModels/GoogleSecOpsConnectionRun.js.map +1 -0
- package/build/dist/Models/DatabaseModels/Incident.js +54 -0
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +16 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js +54 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/User.js +57 -4
- package/build/dist/Models/DatabaseModels/User.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupItem.js +47 -2
- package/build/dist/Models/DatabaseModels/UserNotificationEmailRollupItem.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserWebAuthn.js +18 -0
- package/build/dist/Models/DatabaseModels/UserWebAuthn.js.map +1 -1
- package/build/dist/Models/DatabaseModels/VMwareResource.js +1201 -0
- package/build/dist/Models/DatabaseModels/VMwareResource.js.map +1 -0
- package/build/dist/Models/DatabaseModels/VMwareVCenter.js +1149 -0
- package/build/dist/Models/DatabaseModels/VMwareVCenter.js.map +1 -0
- package/build/dist/Models/DatabaseModels/VMwareVCenterFeed.js +648 -0
- package/build/dist/Models/DatabaseModels/VMwareVCenterFeed.js.map +1 -0
- package/build/dist/Models/DatabaseModels/VMwareVCenterLabelRule.js +522 -0
- package/build/dist/Models/DatabaseModels/VMwareVCenterLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/VMwareVCenterOwnerRule.js +603 -0
- package/build/dist/Models/DatabaseModels/VMwareVCenterOwnerRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/VMwareVCenterOwnerTeam.js +503 -0
- package/build/dist/Models/DatabaseModels/VMwareVCenterOwnerTeam.js.map +1 -0
- package/build/dist/Models/DatabaseModels/VMwareVCenterOwnerUser.js +502 -0
- package/build/dist/Models/DatabaseModels/VMwareVCenterOwnerUser.js.map +1 -0
- package/build/dist/Models/DatabaseModels/WorkflowVariable.js +22 -1
- package/build/dist/Models/DatabaseModels/WorkflowVariable.js.map +1 -1
- package/build/dist/Server/API/AIAgentDataAPI.js +63 -3
- package/build/dist/Server/API/AIAgentDataAPI.js.map +1 -1
- package/build/dist/Server/API/AIAgentGitHubAPI.js +363 -0
- package/build/dist/Server/API/AIAgentGitHubAPI.js.map +1 -0
- package/build/dist/Server/API/AIChatAPI.js +19 -1
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/BillingAPI.js +22 -0
- package/build/dist/Server/API/BillingAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +10 -0
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/API/GitHubAPI.js +33 -5
- package/build/dist/Server/API/GitHubAPI.js.map +1 -1
- package/build/dist/Server/API/GoogleSecOpsConnectionAPI.js +56 -0
- package/build/dist/Server/API/GoogleSecOpsConnectionAPI.js.map +1 -0
- package/build/dist/Server/API/UserWebAuthnAPI.js +17 -1
- package/build/dist/Server/API/UserWebAuthnAPI.js.map +1 -1
- package/build/dist/Server/API/VMwareResourceAPI.js +106 -0
- package/build/dist/Server/API/VMwareResourceAPI.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791800000000-SplitWebAuthnChallengeByPurpose.js +41 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791800000000-SplitWebAuthnChallengeByPurpose.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791900000000-AddSeverityAndStateToNotificationEmailRollup.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1791900000000-AddSeverityAndStateToNotificationEmailRollup.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792000000000-AddVMwareTables.js +241 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792000000000-AddVMwareTables.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792100000000-AddGitHubCommandSettingsToCodeRepository.js +36 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792100000000-AddGitHubCommandSettingsToCodeRepository.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792200000000-AddUserWebAuthnPurpose.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792200000000-AddUserWebAuthnPurpose.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792300000000-AddGoogleSecOpsDiagnostics.js +34 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1792300000000-AddGoogleSecOpsDiagnostics.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Middleware/IdentityRateLimit.js +80 -0
- package/build/dist/Server/Middleware/IdentityRateLimit.js.map +1 -1
- package/build/dist/Server/Services/AIRunService.js +14 -1
- package/build/dist/Server/Services/AIRunService.js.map +1 -1
- package/build/dist/Server/Services/BillingPaymentMethodService.js +71 -21
- package/build/dist/Server/Services/BillingPaymentMethodService.js.map +1 -1
- package/build/dist/Server/Services/BillingService.js +210 -22
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceInstanceService.js +66 -0
- package/build/dist/Server/Services/CloudResourceInstanceService.js.map +1 -1
- package/build/dist/Server/Services/ExceptionAggregationService.js +1 -0
- package/build/dist/Server/Services/ExceptionAggregationService.js.map +1 -1
- package/build/dist/Server/Services/GoogleSecOpsConnectionRunService.js +9 -0
- package/build/dist/Server/Services/GoogleSecOpsConnectionRunService.js.map +1 -0
- package/build/dist/Server/Services/GoogleSecOpsConnectionService.js +7 -0
- package/build/dist/Server/Services/GoogleSecOpsConnectionService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +16 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/LogAggregationService.js +1 -0
- package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MetricAggregationService.js +1 -0
- package/build/dist/Server/Services/MetricAggregationService.js.map +1 -1
- package/build/dist/Server/Services/MobilePasskeyLoginService.js +137 -0
- package/build/dist/Server/Services/MobilePasskeyLoginService.js.map +1 -0
- package/build/dist/Server/Services/MonitorService.js +34 -1
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +64 -26
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js +124 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js +142 -2
- package/build/dist/Server/Services/NetworkDeviceAutoImportRuleService.js.map +1 -1
- package/build/dist/Server/Services/OpenTelemetryIngestService.js +28 -13
- package/build/dist/Server/Services/OpenTelemetryIngestService.js.map +1 -1
- package/build/dist/Server/Services/PayAsYouGoBillingService.js +297 -0
- package/build/dist/Server/Services/PayAsYouGoBillingService.js.map +1 -0
- package/build/dist/Server/Services/ProjectService.js +38 -2
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionInstanceService.js +66 -0
- package/build/dist/Server/Services/ServerlessFunctionInstanceService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +85 -28
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryIngestionKeyService.js +61 -10
- package/build/dist/Server/Services/TelemetryIngestionKeyService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryUsageBillingService.js +62 -5
- package/build/dist/Server/Services/TelemetryUsageBillingService.js.map +1 -1
- package/build/dist/Server/Services/TraceAggregationService.js +5 -4
- package/build/dist/Server/Services/TraceAggregationService.js.map +1 -1
- package/build/dist/Server/Services/UserWebAuthnService.js +345 -113
- package/build/dist/Server/Services/UserWebAuthnService.js.map +1 -1
- package/build/dist/Server/Services/VMwareResourceService.js +395 -0
- package/build/dist/Server/Services/VMwareResourceService.js.map +1 -0
- package/build/dist/Server/Services/VMwareVCenterFeedService.js +89 -0
- package/build/dist/Server/Services/VMwareVCenterFeedService.js.map +1 -0
- package/build/dist/Server/Services/VMwareVCenterLabelRuleEngineService.js +197 -0
- package/build/dist/Server/Services/VMwareVCenterLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/VMwareVCenterLabelRuleService.js +13 -0
- package/build/dist/Server/Services/VMwareVCenterLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/VMwareVCenterOwnerRuleEngineService.js +214 -0
- package/build/dist/Server/Services/VMwareVCenterOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/VMwareVCenterOwnerRuleService.js +13 -0
- package/build/dist/Server/Services/VMwareVCenterOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/VMwareVCenterOwnerTeamService.js +129 -0
- package/build/dist/Server/Services/VMwareVCenterOwnerTeamService.js.map +1 -0
- package/build/dist/Server/Services/VMwareVCenterOwnerUserService.js +122 -0
- package/build/dist/Server/Services/VMwareVCenterOwnerUserService.js.map +1 -0
- package/build/dist/Server/Services/VMwareVCenterService.js +533 -0
- package/build/dist/Server/Services/VMwareVCenterService.js.map +1 -0
- package/build/dist/Server/Services/WorkflowVariableService.js +169 -0
- package/build/dist/Server/Services/WorkflowVariableService.js.map +1 -1
- package/build/dist/Server/Types/Billing/MeteredPlan/ActiveMonitoringMeteredPlan.js +12 -1
- package/build/dist/Server/Types/Billing/MeteredPlan/ActiveMonitoringMeteredPlan.js.map +1 -1
- package/build/dist/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.js +25 -2
- package/build/dist/Server/Types/Billing/MeteredPlan/TelemetryMeteredPlan.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubAgentTaskTrigger.js +275 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubAgentTaskTrigger.js.map +1 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubCommandAuthorizer.js +226 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubCommandAuthorizer.js.map +1 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubCommandParser.js +297 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubCommandParser.js.map +1 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubConversation.js +582 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubConversation.js.map +1 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubReplyComposer.js +275 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubReplyComposer.js.map +1 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubRunReply.js +365 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubRunReply.js.map +1 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubWebhookEvents.js +103 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubWebhookEvents.js.map +1 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubWebhookHandler.js +661 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHubWebhookHandler.js.map +1 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js +113 -0
- package/build/dist/Server/Utils/Dashboard/PublicDashboardResourceListPolicy.js.map +1 -1
- package/build/dist/Server/Utils/DataSource/EgressGuard.js +99 -15
- package/build/dist/Server/Utils/DataSource/EgressGuard.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js +2 -0
- package/build/dist/Server/Utils/EmailRollup/EmailRollupFlushRunner.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js +18 -0
- package/build/dist/Server/Utils/EmailRollup/EmailRollupRenderer.js.map +1 -1
- package/build/dist/Server/Utils/EmailRollup/EmailRollupWriter.js +31 -2
- package/build/dist/Server/Utils/EmailRollup/EmailRollupWriter.js.map +1 -1
- package/build/dist/Server/Utils/LogRedaction.js +2 -1
- package/build/dist/Server/Utils/LogRedaction.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorAlert.js +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +219 -4
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js +12 -4
- package/build/dist/Server/Utils/Monitor/MonitorMaintenanceSuppression.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js +5 -3
- package/build/dist/Server/Utils/Monitor/MonitorResourceContext.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js +22 -9
- package/build/dist/Server/Utils/Monitor/MonitorStepResourceIdentity.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorTemplateUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js +21 -0
- package/build/dist/Server/Utils/Monitor/SeriesResourceLabels.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js +21 -6
- package/build/dist/Server/Utils/Monitor/SeriesResourceLinker.js.map +1 -1
- package/build/dist/Server/Utils/ResourceFeed/ResourceFeedUtil.js +3 -3
- package/build/dist/Server/Utils/SecurityEvent/ConnectorErrorMessage.js +9 -9
- package/build/dist/Server/Utils/SecurityEvent/ConnectorErrorMessage.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js +92 -71
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsClient.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js +428 -143
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsPoller.js.map +1 -1
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.js +376 -0
- package/build/dist/Server/Utils/SecurityEvent/GoogleSecOps/GoogleSecOpsRunExecutor.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +5 -0
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js +136 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryEntity.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/VMwareSnapshotScan.js +933 -0
- package/build/dist/Server/Utils/Telemetry/VMwareSnapshotScan.js.map +1 -0
- package/build/dist/Types/AI/CodeFixTaskContext.js +40 -0
- package/build/dist/Types/AI/CodeFixTaskContext.js.map +1 -1
- package/build/dist/Types/AI/CodeFixTaskType.js +57 -0
- package/build/dist/Types/AI/CodeFixTaskType.js.map +1 -1
- package/build/dist/Types/Accounts/AccountsLanguage.js +1 -0
- package/build/dist/Types/Accounts/AccountsLanguage.js.map +1 -1
- package/build/dist/Types/AdminDashboard/AdminDashboardLanguage.js +1 -0
- package/build/dist/Types/AdminDashboard/AdminDashboardLanguage.js.map +1 -1
- package/build/dist/Types/Cloud/CloudPlatform.js +277 -0
- package/build/dist/Types/Cloud/CloudPlatform.js.map +1 -0
- package/build/dist/Types/CodeRepository/GitHubCommand.js +52 -0
- package/build/dist/Types/CodeRepository/GitHubCommand.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardVMwareHostListComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardVMwareHostListComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardVMwareVirtualMachineListComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardVMwareVirtualMachineListComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardLanguage.js +1 -0
- package/build/dist/Types/Dashboard/DashboardLanguage.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardTemplates.js +286 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Docs/DocsLanguage.js +1 -0
- package/build/dist/Types/Docs/DocsLanguage.js.map +1 -1
- package/build/dist/Types/Exception/EgressGuardException.js +59 -0
- package/build/dist/Types/Exception/EgressGuardException.js.map +1 -0
- package/build/dist/Types/Icon/IconProp.js +1 -0
- package/build/dist/Types/Icon/IconProp.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js +1 -0
- package/build/dist/Types/Monitor/MonitorCriteriaInstance.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +61 -18
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepMetricViewConfigUtil.js +1 -0
- package/build/dist/Types/Monitor/MonitorStepMetricViewConfigUtil.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepVMwareMonitor.js +71 -0
- package/build/dist/Types/Monitor/MonitorStepVMwareMonitor.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorTemplateSyncField.js +211 -0
- package/build/dist/Types/Monitor/MonitorTemplateSyncField.js.map +1 -0
- package/build/dist/Types/Monitor/MonitorType.js +23 -0
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js +28 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationCatalog.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js +1 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationTypes.js.map +1 -1
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js +2 -0
- package/build/dist/Types/Monitor/Recommendation/MonitorRecommendationUtil.js.map +1 -1
- package/build/dist/Types/Monitor/SeriesContext/SeriesDebugHints.js +120 -0
- package/build/dist/Types/Monitor/SeriesContext/SeriesDebugHints.js.map +1 -1
- package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js +60 -0
- package/build/dist/Types/Monitor/SeriesContext/SeriesLabelDisplay.js.map +1 -1
- package/build/dist/Types/Monitor/VMwareAlertTemplates.js +775 -0
- package/build/dist/Types/Monitor/VMwareAlertTemplates.js.map +1 -0
- package/build/dist/Types/Monitor/VMwareMetricCatalog.js +799 -0
- package/build/dist/Types/Monitor/VMwareMetricCatalog.js.map +1 -0
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js +25 -0
- package/build/dist/Types/OnCallDutyPolicy/ScheduleShiftUtil.js.map +1 -1
- package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js +5 -0
- package/build/dist/Types/OnCallDutyPolicy/UserOverrideUtil.js.map +1 -1
- package/build/dist/Types/Password.js +63 -5
- package/build/dist/Types/Password.js.map +1 -1
- package/build/dist/Types/Permission.js +234 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Probe/RequestFailedDetails.js +7 -0
- package/build/dist/Types/Probe/RequestFailedDetails.js.map +1 -1
- package/build/dist/Types/SecurityEvent/GoogleSecOpsDiagnostics.js +2 -0
- package/build/dist/Types/SecurityEvent/GoogleSecOpsDiagnostics.js.map +1 -0
- package/build/dist/Types/StatusPage/StatusPageLanguage.js +1 -0
- package/build/dist/Types/StatusPage/StatusPageLanguage.js.map +1 -1
- package/build/dist/Types/Telemetry/EntityType.js +21 -0
- package/build/dist/Types/Telemetry/EntityType.js.map +1 -1
- package/build/dist/Types/Telemetry/ServiceType.js +1 -0
- package/build/dist/Types/Telemetry/ServiceType.js.map +1 -1
- package/build/dist/UI/Components/Breadcrumbs/Breadcrumbs.js +2 -2
- package/build/dist/UI/Components/Breadcrumbs/Breadcrumbs.js.map +1 -1
- package/build/dist/UI/Components/Calendar/Calendar.js +24 -4
- package/build/dist/UI/Components/Calendar/Calendar.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicForm.js +28 -3
- package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/ColorPicker.js +1 -1
- package/build/dist/UI/Components/Forms/Fields/ColorPicker.js.map +1 -1
- package/build/dist/UI/Components/Forms/Fields/FormField.js +10 -3
- package/build/dist/UI/Components/Forms/Fields/FormField.js.map +1 -1
- package/build/dist/UI/Components/Forms/ModelForm.js +89 -6
- package/build/dist/UI/Components/Forms/ModelForm.js.map +1 -1
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.js +5 -6
- package/build/dist/UI/Components/Graphs/DayUptimeGraph.js.map +1 -1
- package/build/dist/UI/Components/Icon/Icon.js +14 -0
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/Input/Input.js +37 -8
- package/build/dist/UI/Components/Input/Input.js.map +1 -1
- package/build/dist/UI/Components/LabelRule/ImportLabelRulesModal.js +228 -0
- package/build/dist/UI/Components/LabelRule/ImportLabelRulesModal.js.map +1 -0
- package/build/dist/UI/Components/LabelRule/LabelRuleTable.js +102 -0
- package/build/dist/UI/Components/LabelRule/LabelRuleTable.js.map +1 -0
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +15 -2
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/ModelTable.js +13 -5
- package/build/dist/UI/Components/ModelTable/ModelTable.js.map +1 -1
- package/build/dist/UI/Components/MonitorGraphs/Uptime.js +14 -15
- package/build/dist/UI/Components/MonitorGraphs/Uptime.js.map +1 -1
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js +2 -0
- package/build/dist/UI/Components/MonitorTemplateVariables/TemplateVariablesCatalog.js.map +1 -1
- package/build/dist/UI/Components/Tooltip/Tooltip.js +33 -4
- package/build/dist/UI/Components/Tooltip/Tooltip.js.map +1 -1
- package/build/dist/UI/Utils/DashboardStackingLayers.js +44 -0
- package/build/dist/UI/Utils/DashboardStackingLayers.js.map +1 -0
- package/build/dist/UI/Utils/LabelRuleImportExport.js +195 -0
- package/build/dist/UI/Utils/LabelRuleImportExport.js.map +1 -0
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js +4 -4
- package/build/dist/UI/Utils/ModelAPI/ModelAPI.js.map +1 -1
- package/build/dist/UI/Utils/TelemetryService.js +3 -2
- package/build/dist/UI/Utils/TelemetryService.js.map +1 -1
- package/build/dist/UI/Utils/UseDashboardGridDnd.js +2 -1
- package/build/dist/UI/Utils/UseDashboardGridDnd.js.map +1 -1
- package/build/dist/UI/Utils/UserEmailRegistrationStatus.js +21 -4
- package/build/dist/UI/Utils/UserEmailRegistrationStatus.js.map +1 -1
- package/build/dist/UI/Utils/WebAuthn.js +121 -0
- package/build/dist/UI/Utils/WebAuthn.js.map +1 -0
- package/build/dist/Utils/API.js +29 -0
- package/build/dist/Utils/API.js.map +1 -1
- package/build/dist/Utils/Dashboard/Components/DashboardVMwareHostListComponent.js +33 -0
- package/build/dist/Utils/Dashboard/Components/DashboardVMwareHostListComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardVMwareResourceListShared.js +54 -0
- package/build/dist/Utils/Dashboard/Components/DashboardVMwareResourceListShared.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardVMwareVirtualMachineListComponent.js +60 -0
- package/build/dist/Utils/Dashboard/Components/DashboardVMwareVirtualMachineListComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/Index.js +8 -0
- package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
- package/build/dist/Utils/LabelRuleImportExport.js +356 -0
- package/build/dist/Utils/LabelRuleImportExport.js.map +1 -0
- package/build/dist/Utils/Monitor/MonitorTemplateSyncUtil.js +110 -0
- package/build/dist/Utils/Monitor/MonitorTemplateSyncUtil.js.map +1 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveryScanStatus.js +6 -0
- package/build/dist/Utils/NetworkDiscovery/DiscoveryScanStatus.js.map +1 -1
- package/build/dist/Utils/Telemetry/CloudInstanceIdentity.js +88 -0
- package/build/dist/Utils/Telemetry/CloudInstanceIdentity.js.map +1 -0
- package/build/dist/Utils/Telemetry/EntityKey.js +15 -0
- package/build/dist/Utils/Telemetry/EntityKey.js.map +1 -1
- package/build/dist/Utils/Telemetry/EntityRelationship.js +13 -0
- package/build/dist/Utils/Telemetry/EntityRelationship.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,1526 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ---------------------------------------------------------------------------
|
|
3
|
+
* GitHubConversation — the CONVERSATION half of the interactive GitHub App.
|
|
4
|
+
*
|
|
5
|
+
* Every method here writes into a repository OneUptime does not own, on behalf
|
|
6
|
+
* of whoever typed "@oneuptime <something>" into a thread that anyone with a
|
|
7
|
+
* GitHub account can post to. Four invariants are pinned below, and each one
|
|
8
|
+
* is a real incident if it regresses:
|
|
9
|
+
*
|
|
10
|
+
* 1. AUTHORIZATION FAILS CLOSED. getUserRepositoryPermission is the only
|
|
11
|
+
* question that decides whether a command runs at all — `author_association`
|
|
12
|
+
* on the webhook payload describes past activity, not permission. So every
|
|
13
|
+
* way this call can go wrong (404, 403, 500, a thrown request, an
|
|
14
|
+
* installation token that can no longer be minted) has to answer None.
|
|
15
|
+
* Refusing a command because GitHub was unreachable is a bad minute;
|
|
16
|
+
* running one because GitHub was unreachable is a stranger writing to
|
|
17
|
+
* someone else's repository. `role_name` is preferred over the
|
|
18
|
+
* backwards-compatible `permission` field for the same reason: that field
|
|
19
|
+
* flattens "triage" to "read" and "maintain" to "write", and Triage — which
|
|
20
|
+
* may label and close but may not push — must never be able to command the
|
|
21
|
+
* agent.
|
|
22
|
+
*
|
|
23
|
+
* 2. A REVIEW IS A COMMENT, NEVER AN APPROVAL. An APPROVE submitted by the app
|
|
24
|
+
* satisfies a branch protection rule, which turns an automated reviewer into
|
|
25
|
+
* an automated merger. The review's opinion belongs in its text.
|
|
26
|
+
*
|
|
27
|
+
* 3. NOTHING COSMETIC MAY FAIL A COMMAND, AND NOTHING MAY LOOP. A reaction
|
|
28
|
+
* that does not land returns false instead of throwing, and a comment
|
|
29
|
+
* written by ANY bot is recognised as one so the app never answers itself.
|
|
30
|
+
*
|
|
31
|
+
* 4. A FORK'S BRANCH IS NOT PUSHABLE. isFromFork gates the "revise this" path;
|
|
32
|
+
* a wrong answer either pushes nothing or creates a same-named branch in the
|
|
33
|
+
* BASE repository that the pull request does not point at.
|
|
34
|
+
*
|
|
35
|
+
* The installation-token cache is covered here too. That token is a live write
|
|
36
|
+
* credential for other people's repositories, so both halves matter: one token
|
|
37
|
+
* for a burst of calls, and never one installation's token on another
|
|
38
|
+
* installation's request.
|
|
39
|
+
* ---------------------------------------------------------------------------
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import GitHubConversation, {
|
|
43
|
+
GitHubIssueComment,
|
|
44
|
+
GitHubPullRequestDetails,
|
|
45
|
+
GitHubReaction,
|
|
46
|
+
GitHubRepositoryPermission,
|
|
47
|
+
} from "../../../../Server/Utils/CodeRepository/GitHub/GitHubConversation";
|
|
48
|
+
import GitHubUtil, {
|
|
49
|
+
GitHubInstallationToken,
|
|
50
|
+
} from "../../../../Server/Utils/CodeRepository/GitHub/GitHub";
|
|
51
|
+
import logger from "../../../../Server/Utils/Logger";
|
|
52
|
+
import HTTPErrorResponse from "../../../../Types/API/HTTPErrorResponse";
|
|
53
|
+
import HTTPResponse from "../../../../Types/API/HTTPResponse";
|
|
54
|
+
import Headers from "../../../../Types/API/Headers";
|
|
55
|
+
import URL from "../../../../Types/API/URL";
|
|
56
|
+
import { JSONArray, JSONObject } from "../../../../Types/JSON";
|
|
57
|
+
import { afterEach, beforeEach, describe, expect, test } from "@jest/globals";
|
|
58
|
+
|
|
59
|
+
const getMock: jest.Mock = jest.fn();
|
|
60
|
+
const postMock: jest.Mock = jest.fn();
|
|
61
|
+
const patchMock: jest.Mock = jest.fn();
|
|
62
|
+
|
|
63
|
+
/*
|
|
64
|
+
* jest hoists this above the imports, so the factory runs before
|
|
65
|
+
* GitHubConversation is loaded. The mocks are only dereferenced inside the
|
|
66
|
+
* arrows — reading them in the factory body itself would hit the TDZ.
|
|
67
|
+
*
|
|
68
|
+
* The path is the one the source resolves to ("../../../../Utils/API" from
|
|
69
|
+
* Common/Server/Utils/CodeRepository/GitHub), reached from this file's own
|
|
70
|
+
* directory.
|
|
71
|
+
*/
|
|
72
|
+
jest.mock("../../../../Utils/API", () => {
|
|
73
|
+
return {
|
|
74
|
+
__esModule: true,
|
|
75
|
+
default: {
|
|
76
|
+
get: (...args: Array<unknown>) => {
|
|
77
|
+
return getMock(...args);
|
|
78
|
+
},
|
|
79
|
+
post: (...args: Array<unknown>) => {
|
|
80
|
+
return postMock(...args);
|
|
81
|
+
},
|
|
82
|
+
patch: (...args: Array<unknown>) => {
|
|
83
|
+
return patchMock(...args);
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const REPO: {
|
|
90
|
+
installationId: string;
|
|
91
|
+
organizationName: string;
|
|
92
|
+
repositoryName: string;
|
|
93
|
+
} = {
|
|
94
|
+
installationId: "installation-1",
|
|
95
|
+
organizationName: "acme",
|
|
96
|
+
repositoryName: "checkout",
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
let tokenSpy: jest.SpyInstance;
|
|
100
|
+
let warnSpy: jest.SpyInstance;
|
|
101
|
+
let debugSpy: jest.SpyInstance;
|
|
102
|
+
|
|
103
|
+
function installationToken(
|
|
104
|
+
token: string,
|
|
105
|
+
expiresInSeconds: number,
|
|
106
|
+
): GitHubInstallationToken {
|
|
107
|
+
return {
|
|
108
|
+
token: token,
|
|
109
|
+
expiresAt: new Date(Date.now() + expiresInSeconds * 1000),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function okJson(data: JSONObject): HTTPResponse<JSONObject> {
|
|
114
|
+
return new HTTPResponse<JSONObject>(200, data, {});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function okArray(data: Array<JSONObject>): HTTPResponse<JSONArray> {
|
|
118
|
+
return new HTTPResponse<JSONArray>(200, data, {});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function errorResponse(
|
|
122
|
+
statusCode: number,
|
|
123
|
+
data: JSONObject,
|
|
124
|
+
): HTTPErrorResponse {
|
|
125
|
+
return new HTTPErrorResponse(statusCode, data, {});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
interface ApiCall {
|
|
129
|
+
url: URL;
|
|
130
|
+
data?: JSONObject | undefined;
|
|
131
|
+
headers: Headers;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// The Nth call's options object, 1-indexed so retries read the way they happen.
|
|
135
|
+
function callAt(
|
|
136
|
+
mock: jest.Mock,
|
|
137
|
+
callNumber: number,
|
|
138
|
+
methodName: string,
|
|
139
|
+
): ApiCall {
|
|
140
|
+
const args: Array<ApiCall> | undefined = mock.mock.calls[callNumber - 1] as
|
|
141
|
+
| Array<ApiCall>
|
|
142
|
+
| undefined;
|
|
143
|
+
|
|
144
|
+
if (!args || !args[0]) {
|
|
145
|
+
throw new Error(`API.${methodName} was not called ${callNumber} time(s)`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return args[0];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function postCall(callNumber: number): ApiCall {
|
|
152
|
+
return callAt(postMock, callNumber, "post");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function getCall(callNumber: number): ApiCall {
|
|
156
|
+
return callAt(getMock, callNumber, "get");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function patchCall(callNumber: number): ApiCall {
|
|
160
|
+
return callAt(patchMock, callNumber, "patch");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function postBody(callNumber: number): JSONObject {
|
|
164
|
+
return postCall(callNumber).data || {};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function everyPostedBody(): Array<JSONObject> {
|
|
168
|
+
return postMock.mock.calls.map((args: Array<unknown>): JSONObject => {
|
|
169
|
+
return (args[0] as ApiCall).data || {};
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// GitHub's issue-comment shape, trimmed to the fields the mapper reads.
|
|
174
|
+
function commentJson(overrides: JSONObject): JSONObject {
|
|
175
|
+
return {
|
|
176
|
+
id: 555,
|
|
177
|
+
html_url: "https://github.com/acme/checkout/issues/42#issuecomment-555",
|
|
178
|
+
body: "on it",
|
|
179
|
+
user: { login: "octocat", type: "User" },
|
|
180
|
+
created_at: "2026-09-01T10:00:00Z",
|
|
181
|
+
...overrides,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function postAComment(body: string): Promise<GitHubIssueComment> {
|
|
186
|
+
return GitHubConversation.createIssueComment({
|
|
187
|
+
...REPO,
|
|
188
|
+
issueNumber: 42,
|
|
189
|
+
body: body,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
beforeEach(() => {
|
|
194
|
+
/*
|
|
195
|
+
* The token cache is a module-level Map, so without this the first case to
|
|
196
|
+
* mint a token would silently satisfy every case after it.
|
|
197
|
+
*/
|
|
198
|
+
GitHubConversation.clearTokenCache();
|
|
199
|
+
|
|
200
|
+
getMock.mockReset();
|
|
201
|
+
postMock.mockReset();
|
|
202
|
+
patchMock.mockReset();
|
|
203
|
+
|
|
204
|
+
tokenSpy = jest
|
|
205
|
+
.spyOn(GitHubUtil, "getInstallationAccessToken")
|
|
206
|
+
.mockResolvedValue(installationToken("ghs_installation_1", 60 * 60));
|
|
207
|
+
|
|
208
|
+
warnSpy = jest.spyOn(logger, "warn").mockImplementation((): void => {
|
|
209
|
+
return undefined;
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
debugSpy = jest.spyOn(logger, "debug").mockImplementation((): void => {
|
|
213
|
+
return undefined;
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
afterEach(() => {
|
|
218
|
+
// Spies mutate shared module state that leaks between tests in one worker.
|
|
219
|
+
jest.restoreAllMocks();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe("GitHubConversation", () => {
|
|
223
|
+
describe("slugifyAppName — the display name a mention has to match", () => {
|
|
224
|
+
test("lowercases a single-word app name", () => {
|
|
225
|
+
expect(GitHubConversation.slugifyAppName("OneUptime")).toBe("oneuptime");
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
/*
|
|
229
|
+
* The whole reason this exists: GITHUB_APP_NAME holds the DISPLAY name,
|
|
230
|
+
* and GitHub derives "@acme-ai-bot" from it. An app configured as
|
|
231
|
+
* "Acme AI Bot" that looked for "@Acme AI Bot" would never match a
|
|
232
|
+
* mention, and every command would be silently dropped.
|
|
233
|
+
*/
|
|
234
|
+
test("turns each run of spaces in a display name into one hyphen", () => {
|
|
235
|
+
expect(GitHubConversation.slugifyAppName("Acme AI Bot")).toBe(
|
|
236
|
+
"acme-ai-bot",
|
|
237
|
+
);
|
|
238
|
+
expect(GitHubConversation.slugifyAppName("Acme AI Bot")).toBe(
|
|
239
|
+
"acme-ai-bot",
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test("trims surrounding whitespace before slugifying", () => {
|
|
244
|
+
expect(GitHubConversation.slugifyAppName(" OneUptime ")).toBe(
|
|
245
|
+
"oneuptime",
|
|
246
|
+
);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
test("drops leading and trailing punctuation instead of hyphenating it", () => {
|
|
250
|
+
expect(GitHubConversation.slugifyAppName(" ~OneUptime! ")).toBe(
|
|
251
|
+
"oneuptime",
|
|
252
|
+
);
|
|
253
|
+
expect(GitHubConversation.slugifyAppName("---OneUptime---")).toBe(
|
|
254
|
+
"oneuptime",
|
|
255
|
+
);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("keeps digits, which are legal in a GitHub slug", () => {
|
|
259
|
+
expect(GitHubConversation.slugifyAppName("OneUptime 2")).toBe(
|
|
260
|
+
"oneuptime-2",
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("collapses emoji and other non-alphanumerics inside the name", () => {
|
|
265
|
+
expect(GitHubConversation.slugifyAppName("OneUptime 🚀 AI")).toBe(
|
|
266
|
+
"oneuptime-ai",
|
|
267
|
+
);
|
|
268
|
+
expect(GitHubConversation.slugifyAppName("One_Uptime/AI")).toBe(
|
|
269
|
+
"one-uptime-ai",
|
|
270
|
+
);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
test("answers null for an empty, blank or missing app name", () => {
|
|
274
|
+
expect(GitHubConversation.slugifyAppName("")).toBeNull();
|
|
275
|
+
expect(GitHubConversation.slugifyAppName(" ")).toBeNull();
|
|
276
|
+
expect(GitHubConversation.slugifyAppName("\t\n")).toBeNull();
|
|
277
|
+
expect(GitHubConversation.slugifyAppName(null)).toBeNull();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
/*
|
|
281
|
+
* A name with nothing sluggable left must answer null, never "". An empty
|
|
282
|
+
* slug would build a bot login of "[bot]", and isBotLogin("[bot]") is true
|
|
283
|
+
* for it — i.e. every bot on the platform would read as THIS app.
|
|
284
|
+
*/
|
|
285
|
+
test("answers null when nothing sluggable survives, never an empty slug", () => {
|
|
286
|
+
expect(GitHubConversation.slugifyAppName("!!!")).toBeNull();
|
|
287
|
+
expect(GitHubConversation.slugifyAppName("---")).toBeNull();
|
|
288
|
+
expect(GitHubConversation.slugifyAppName("日本語")).toBeNull();
|
|
289
|
+
expect(GitHubConversation.slugifyAppName("🚀")).toBeNull();
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
describe("toBotLogin", () => {
|
|
294
|
+
test("appends GitHub's [bot] suffix to the slug", () => {
|
|
295
|
+
expect(GitHubConversation.toBotLogin("oneuptime")).toBe("oneuptime[bot]");
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
test("lowercases the slug, because GitHub's bot login is lowercase", () => {
|
|
299
|
+
expect(GitHubConversation.toBotLogin("OneUptime")).toBe("oneuptime[bot]");
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test("composes with slugifyAppName to go from display name to bot login", () => {
|
|
303
|
+
const slug: string | null =
|
|
304
|
+
GitHubConversation.slugifyAppName("Acme AI Bot");
|
|
305
|
+
|
|
306
|
+
expect(slug).not.toBeNull();
|
|
307
|
+
expect(GitHubConversation.toBotLogin(slug as string)).toBe(
|
|
308
|
+
"acme-ai-bot[bot]",
|
|
309
|
+
);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test("round-trips: the login it builds is recognised as a bot login", () => {
|
|
313
|
+
expect(
|
|
314
|
+
GitHubConversation.isBotLogin(
|
|
315
|
+
GitHubConversation.toBotLogin("oneuptime"),
|
|
316
|
+
),
|
|
317
|
+
).toBe(true);
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
describe("isBotLogin — the loop guard", () => {
|
|
322
|
+
test("recognises this app's own login", () => {
|
|
323
|
+
expect(GitHubConversation.isBotLogin("oneuptime[bot]")).toBe(true);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
/*
|
|
327
|
+
* Deliberately broad: two bots mentioning each other is the failure mode
|
|
328
|
+
* that burns a budget overnight, so ANY app's login counts.
|
|
329
|
+
*/
|
|
330
|
+
test("recognises any other app's login too, not only this app's", () => {
|
|
331
|
+
expect(GitHubConversation.isBotLogin("dependabot[bot]")).toBe(true);
|
|
332
|
+
expect(GitHubConversation.isBotLogin("renovate[bot]")).toBe(true);
|
|
333
|
+
expect(GitHubConversation.isBotLogin("github-actions[bot]")).toBe(true);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("is case-insensitive about the suffix", () => {
|
|
337
|
+
expect(GitHubConversation.isBotLogin("OneUptime[BOT]")).toBe(true);
|
|
338
|
+
expect(GitHubConversation.isBotLogin("OneUptime[Bot]")).toBe(true);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
test("does not treat a human login as a bot", () => {
|
|
342
|
+
expect(GitHubConversation.isBotLogin("octocat")).toBe(false);
|
|
343
|
+
expect(GitHubConversation.isBotLogin("bot")).toBe(false);
|
|
344
|
+
expect(GitHubConversation.isBotLogin("not-a-bot")).toBe(false);
|
|
345
|
+
expect(GitHubConversation.isBotLogin("robot")).toBe(false);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
/*
|
|
349
|
+
* The suffix has to be at the END. A human who put "[bot]" anywhere else
|
|
350
|
+
* in their login would otherwise have every command they type dropped.
|
|
351
|
+
*/
|
|
352
|
+
test("only matches the suffix, not [bot] anywhere in the login", () => {
|
|
353
|
+
expect(GitHubConversation.isBotLogin("[bot]octocat")).toBe(false);
|
|
354
|
+
expect(GitHubConversation.isBotLogin("oct[bot]ocat")).toBe(false);
|
|
355
|
+
expect(GitHubConversation.isBotLogin("oneuptime[bot]-real")).toBe(false);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test("treats a missing login as human-unknown rather than throwing", () => {
|
|
359
|
+
expect(GitHubConversation.isBotLogin(null)).toBe(false);
|
|
360
|
+
expect(GitHubConversation.isBotLogin(undefined)).toBe(false);
|
|
361
|
+
expect(GitHubConversation.isBotLogin("")).toBe(false);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
describe("canCommandApp — who may drive the agent", () => {
|
|
366
|
+
test("allows the three permissions that can already push", () => {
|
|
367
|
+
expect(
|
|
368
|
+
GitHubConversation.canCommandApp(GitHubRepositoryPermission.Write),
|
|
369
|
+
).toBe(true);
|
|
370
|
+
expect(
|
|
371
|
+
GitHubConversation.canCommandApp(GitHubRepositoryPermission.Maintain),
|
|
372
|
+
).toBe(true);
|
|
373
|
+
expect(
|
|
374
|
+
GitHubConversation.canCommandApp(GitHubRepositoryPermission.Admin),
|
|
375
|
+
).toBe(true);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
/*
|
|
379
|
+
* Read is anyone who can see the repository — on a public repository, the
|
|
380
|
+
* whole internet. Triage may label and close but may NOT push, so it must
|
|
381
|
+
* not be able to make the app push either.
|
|
382
|
+
*/
|
|
383
|
+
test("refuses Read, Triage and None", () => {
|
|
384
|
+
expect(
|
|
385
|
+
GitHubConversation.canCommandApp(GitHubRepositoryPermission.Read),
|
|
386
|
+
).toBe(false);
|
|
387
|
+
expect(
|
|
388
|
+
GitHubConversation.canCommandApp(GitHubRepositoryPermission.Triage),
|
|
389
|
+
).toBe(false);
|
|
390
|
+
expect(
|
|
391
|
+
GitHubConversation.canCommandApp(GitHubRepositoryPermission.None),
|
|
392
|
+
).toBe(false);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// A permission level added later defaults to refused until considered.
|
|
396
|
+
test("allows exactly three of the known permission levels", () => {
|
|
397
|
+
const allowed: Array<GitHubRepositoryPermission> = Object.values(
|
|
398
|
+
GitHubRepositoryPermission,
|
|
399
|
+
).filter((permission: GitHubRepositoryPermission) => {
|
|
400
|
+
return GitHubConversation.canCommandApp(permission);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
expect(allowed).toEqual([
|
|
404
|
+
GitHubRepositoryPermission.Write,
|
|
405
|
+
GitHubRepositoryPermission.Maintain,
|
|
406
|
+
GitHubRepositoryPermission.Admin,
|
|
407
|
+
]);
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
describe("isFromFork — whether the head branch is pushable at all", () => {
|
|
412
|
+
function isFork(headRepositoryFullName: string | null): boolean {
|
|
413
|
+
return GitHubConversation.isFromFork({
|
|
414
|
+
headRepositoryFullName: headRepositoryFullName,
|
|
415
|
+
organizationName: "Acme",
|
|
416
|
+
repositoryName: "Checkout",
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
test("says no for a branch in the same repository", () => {
|
|
421
|
+
expect(isFork("Acme/Checkout")).toBe(false);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
/*
|
|
425
|
+
* GitHub owner and repository names are case-insensitive, and the webhook
|
|
426
|
+
* payload's casing does not always match the configuration's. Treating a
|
|
427
|
+
* case difference as a fork would refuse every revision on a repository
|
|
428
|
+
* whose name was stored with different capitalisation.
|
|
429
|
+
*/
|
|
430
|
+
test("compares owner and repository case-insensitively", () => {
|
|
431
|
+
expect(isFork("acme/checkout")).toBe(false);
|
|
432
|
+
expect(isFork("ACME/CHECKOUT")).toBe(false);
|
|
433
|
+
expect(isFork("aCmE/ChEcKoUt")).toBe(false);
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
test("says yes when the owner differs", () => {
|
|
437
|
+
expect(isFork("contributor/Checkout")).toBe(true);
|
|
438
|
+
expect(isFork("evil-acme/Checkout")).toBe(true);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
test("says yes when the repository name differs", () => {
|
|
442
|
+
expect(isFork("Acme/Checkout-fork")).toBe(true);
|
|
443
|
+
expect(isFork("Acme/Check")).toBe(true);
|
|
444
|
+
expect(isFork("Acme/checkout2")).toBe(true);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
/*
|
|
448
|
+
* GitHub reports a null head repository for a fork that has been deleted.
|
|
449
|
+
* That branch is certainly not one this installation can push to, so the
|
|
450
|
+
* answer has to be "fork" — the fail-closed direction.
|
|
451
|
+
*/
|
|
452
|
+
test("treats a deleted fork (null head repository) as a fork", () => {
|
|
453
|
+
expect(isFork(null)).toBe(true);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
test("treats an empty or whitespace-padded head repository as a fork", () => {
|
|
457
|
+
expect(isFork("")).toBe(true);
|
|
458
|
+
expect(isFork(" Acme/Checkout")).toBe(true);
|
|
459
|
+
expect(isFork("Acme/Checkout ")).toBe(true);
|
|
460
|
+
});
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
describe("getUserRepositoryPermission — the authorization question", () => {
|
|
464
|
+
function permissionOf(
|
|
465
|
+
username: string,
|
|
466
|
+
): Promise<GitHubRepositoryPermission> {
|
|
467
|
+
return GitHubConversation.getUserRepositoryPermission({
|
|
468
|
+
...REPO,
|
|
469
|
+
username: username,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/*
|
|
474
|
+
* The top-level `permission` field collapses "maintain" to "write" and
|
|
475
|
+
* "triage" to "read" for backwards compatibility, so role_name has to win.
|
|
476
|
+
*/
|
|
477
|
+
test("prefers role_name so a maintainer is not flattened to write", async () => {
|
|
478
|
+
getMock.mockResolvedValueOnce(
|
|
479
|
+
okJson({ permission: "write", role_name: "maintain" }),
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
await expect(permissionOf("octocat")).resolves.toBe(
|
|
483
|
+
GitHubRepositoryPermission.Maintain,
|
|
484
|
+
);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
/*
|
|
488
|
+
* The security-relevant half of the same preference. A triage user reads
|
|
489
|
+
* as "read" on the compatibility field; either way they may not command
|
|
490
|
+
* the app, and the answer must name the real role.
|
|
491
|
+
*/
|
|
492
|
+
test("prefers role_name so a triage user is reported as Triage, and refused", async () => {
|
|
493
|
+
getMock.mockResolvedValueOnce(
|
|
494
|
+
okJson({ permission: "read", role_name: "triage" }),
|
|
495
|
+
);
|
|
496
|
+
|
|
497
|
+
const permission: GitHubRepositoryPermission =
|
|
498
|
+
await permissionOf("triager");
|
|
499
|
+
|
|
500
|
+
expect(permission).toBe(GitHubRepositoryPermission.Triage);
|
|
501
|
+
expect(GitHubConversation.canCommandApp(permission)).toBe(false);
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
test("reads an admin as Admin", async () => {
|
|
505
|
+
getMock.mockResolvedValueOnce(
|
|
506
|
+
okJson({ permission: "admin", role_name: "admin" }),
|
|
507
|
+
);
|
|
508
|
+
|
|
509
|
+
await expect(permissionOf("owner")).resolves.toBe(
|
|
510
|
+
GitHubRepositoryPermission.Admin,
|
|
511
|
+
);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
test("falls back to the permission field when GitHub sends no role_name", async () => {
|
|
515
|
+
getMock.mockResolvedValueOnce(okJson({ permission: "write" }));
|
|
516
|
+
|
|
517
|
+
await expect(permissionOf("octocat")).resolves.toBe(
|
|
518
|
+
GitHubRepositoryPermission.Write,
|
|
519
|
+
);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
test("lowercases whatever casing GitHub used", async () => {
|
|
523
|
+
getMock
|
|
524
|
+
.mockResolvedValueOnce(okJson({ permission: "WRITE" }))
|
|
525
|
+
.mockResolvedValueOnce(
|
|
526
|
+
okJson({ permission: "write", role_name: "Maintain" }),
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
await expect(permissionOf("octocat")).resolves.toBe(
|
|
530
|
+
GitHubRepositoryPermission.Write,
|
|
531
|
+
);
|
|
532
|
+
await expect(permissionOf("octocat")).resolves.toBe(
|
|
533
|
+
GitHubRepositoryPermission.Maintain,
|
|
534
|
+
);
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
/*
|
|
538
|
+
* An organization's custom role name is not one of GitHub's six. Falling
|
|
539
|
+
* back to the coarse field keeps a custom read-only role read-only rather
|
|
540
|
+
* than inventing a level for it.
|
|
541
|
+
*/
|
|
542
|
+
test("falls back to the coarse field for an organization's custom role name", async () => {
|
|
543
|
+
getMock.mockResolvedValueOnce(
|
|
544
|
+
okJson({ permission: "read", role_name: "security-auditor" }),
|
|
545
|
+
);
|
|
546
|
+
|
|
547
|
+
const permission: GitHubRepositoryPermission =
|
|
548
|
+
await permissionOf("auditor");
|
|
549
|
+
|
|
550
|
+
expect(permission).toBe(GitHubRepositoryPermission.Read);
|
|
551
|
+
expect(GitHubConversation.canCommandApp(permission)).toBe(false);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
test("answers None when neither field names a permission it knows", async () => {
|
|
555
|
+
getMock.mockResolvedValueOnce(
|
|
556
|
+
okJson({ permission: "banana", role_name: "banana" }),
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
await expect(permissionOf("octocat")).resolves.toBe(
|
|
560
|
+
GitHubRepositoryPermission.None,
|
|
561
|
+
);
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
test("answers None for a response body with no permission at all", async () => {
|
|
565
|
+
getMock.mockResolvedValueOnce(okJson({}));
|
|
566
|
+
|
|
567
|
+
await expect(permissionOf("octocat")).resolves.toBe(
|
|
568
|
+
GitHubRepositoryPermission.None,
|
|
569
|
+
);
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
/*
|
|
573
|
+
* 404 is GitHub's answer both for "not a collaborator" and for "no such
|
|
574
|
+
* user". Both are ordinary — the overwhelming majority of commands from
|
|
575
|
+
* strangers land here — and logging them as warnings would bury the real
|
|
576
|
+
* failures.
|
|
577
|
+
*/
|
|
578
|
+
test("answers None for a 404 WITHOUT logging it as a problem", async () => {
|
|
579
|
+
getMock.mockResolvedValueOnce(
|
|
580
|
+
errorResponse(404, { message: "Not Found" }),
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
await expect(permissionOf("a-stranger")).resolves.toBe(
|
|
584
|
+
GitHubRepositoryPermission.None,
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
test("answers None for a 403, and does say so in the log", async () => {
|
|
591
|
+
getMock.mockResolvedValueOnce(
|
|
592
|
+
errorResponse(403, { message: "Resource not accessible" }),
|
|
593
|
+
);
|
|
594
|
+
|
|
595
|
+
await expect(permissionOf("octocat")).resolves.toBe(
|
|
596
|
+
GitHubRepositoryPermission.None,
|
|
597
|
+
);
|
|
598
|
+
|
|
599
|
+
expect(warnSpy).toHaveBeenCalledTimes(1);
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
test("answers None for a 500 rather than letting the command through", async () => {
|
|
603
|
+
getMock.mockResolvedValueOnce(
|
|
604
|
+
errorResponse(500, { message: "Server Error" }),
|
|
605
|
+
);
|
|
606
|
+
|
|
607
|
+
await expect(permissionOf("octocat")).resolves.toBe(
|
|
608
|
+
GitHubRepositoryPermission.None,
|
|
609
|
+
);
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
/*
|
|
613
|
+
* The fail-closed property. A rejected request — DNS, TLS, a socket
|
|
614
|
+
* hang-up — must not propagate as an exception the caller might treat as
|
|
615
|
+
* "check skipped", and must never resolve to a usable permission.
|
|
616
|
+
*/
|
|
617
|
+
test("answers None when the request throws instead of answering", async () => {
|
|
618
|
+
getMock.mockRejectedValueOnce(new Error("socket hang up"));
|
|
619
|
+
|
|
620
|
+
await expect(permissionOf("octocat")).resolves.toBe(
|
|
621
|
+
GitHubRepositoryPermission.None,
|
|
622
|
+
);
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
test("answers None when the installation token cannot be minted", async () => {
|
|
626
|
+
tokenSpy.mockRejectedValueOnce(new Error("installation not found"));
|
|
627
|
+
|
|
628
|
+
await expect(permissionOf("octocat")).resolves.toBe(
|
|
629
|
+
GitHubRepositoryPermission.None,
|
|
630
|
+
);
|
|
631
|
+
|
|
632
|
+
expect(getMock).not.toHaveBeenCalled();
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
test("asks the collaborator permission endpoint with the installation token", async () => {
|
|
636
|
+
getMock.mockResolvedValueOnce(okJson({ permission: "write" }));
|
|
637
|
+
|
|
638
|
+
await permissionOf("octocat");
|
|
639
|
+
|
|
640
|
+
expect(getCall(1).url.toString()).toBe(
|
|
641
|
+
"https://api.github.com/repos/acme/checkout/collaborators/octocat/permission",
|
|
642
|
+
);
|
|
643
|
+
expect(getCall(1).headers["Authorization"]).toBe(
|
|
644
|
+
"Bearer ghs_installation_1",
|
|
645
|
+
);
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
/*
|
|
649
|
+
* The username comes off a webhook payload, i.e. from whoever typed the
|
|
650
|
+
* comment. Encoding keeps it a single path segment, so it cannot walk out
|
|
651
|
+
* of /collaborators/ into another endpoint whose answer would be read as
|
|
652
|
+
* a permission.
|
|
653
|
+
*/
|
|
654
|
+
test("url-encodes the username so it cannot escape the collaborators path", async () => {
|
|
655
|
+
getMock.mockResolvedValueOnce(okJson({ permission: "none" }));
|
|
656
|
+
|
|
657
|
+
await permissionOf("../../../orgs/acme/members");
|
|
658
|
+
|
|
659
|
+
const requestedUrl: string = getCall(1).url.toString();
|
|
660
|
+
|
|
661
|
+
expect(requestedUrl).toContain(
|
|
662
|
+
"/collaborators/..%2F..%2F..%2Forgs%2Facme%2Fmembers/permission",
|
|
663
|
+
);
|
|
664
|
+
expect(requestedUrl).not.toContain("/orgs/acme/members");
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
test("url-encodes a username with spaces or unicode rather than sending it raw", async () => {
|
|
668
|
+
getMock.mockResolvedValueOnce(okJson({ permission: "none" }));
|
|
669
|
+
|
|
670
|
+
await permissionOf("oct cat");
|
|
671
|
+
|
|
672
|
+
expect(getCall(1).url.toString()).toContain(
|
|
673
|
+
"/collaborators/oct%20cat/permission",
|
|
674
|
+
);
|
|
675
|
+
});
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
describe("createPullRequestReview — a comment, never an approval", () => {
|
|
679
|
+
const REVIEW_URL: string =
|
|
680
|
+
"https://api.github.com/repos/acme/checkout/pulls/12/reviews";
|
|
681
|
+
|
|
682
|
+
function reviewCreated(): HTTPResponse<JSONObject> {
|
|
683
|
+
return okJson({
|
|
684
|
+
html_url:
|
|
685
|
+
"https://github.com/acme/checkout/pull/12#pullrequestreview-9001",
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function postReview(data: {
|
|
690
|
+
body?: string;
|
|
691
|
+
comments?: Array<{ path: string; line: number; body: string }>;
|
|
692
|
+
commitSha?: string | undefined;
|
|
693
|
+
}): Promise<string> {
|
|
694
|
+
return GitHubConversation.createPullRequestReview({
|
|
695
|
+
...REPO,
|
|
696
|
+
pullRequestNumber: 12,
|
|
697
|
+
body: data.body ?? "Two things stood out.",
|
|
698
|
+
comments: data.comments ?? [],
|
|
699
|
+
commitSha: data.commitSha,
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
test("submits the review as an event of COMMENT", async () => {
|
|
704
|
+
postMock.mockResolvedValueOnce(reviewCreated());
|
|
705
|
+
|
|
706
|
+
await postReview({});
|
|
707
|
+
|
|
708
|
+
expect(postMock).toHaveBeenCalledTimes(1);
|
|
709
|
+
expect(postBody(1)["event"]).toBe("COMMENT");
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
/*
|
|
713
|
+
* An APPROVE from the app satisfies a branch protection rule, which makes
|
|
714
|
+
* the automated reviewer an automated merger. REQUEST_CHANGES is the other
|
|
715
|
+
* side of the same coin: it BLOCKS a merge until the app dismisses it.
|
|
716
|
+
*/
|
|
717
|
+
test("never sends APPROVE or REQUEST_CHANGES, whatever the review text says", async () => {
|
|
718
|
+
postMock
|
|
719
|
+
.mockResolvedValueOnce(errorResponse(422, { message: "bad anchor" }))
|
|
720
|
+
.mockResolvedValueOnce(reviewCreated());
|
|
721
|
+
|
|
722
|
+
await postReview({
|
|
723
|
+
body: "LGTM — I approve of this, no changes requested.",
|
|
724
|
+
comments: [{ path: "src/a.ts", line: 3, body: "approve" }],
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
const serialized: string = JSON.stringify(everyPostedBody());
|
|
728
|
+
|
|
729
|
+
expect(serialized).not.toContain("APPROVE");
|
|
730
|
+
expect(serialized).not.toContain("REQUEST_CHANGES");
|
|
731
|
+
|
|
732
|
+
for (const body of everyPostedBody()) {
|
|
733
|
+
expect(body["event"]).toBe("COMMENT");
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
test("posts once, with no comments key, when there are no inline comments", async () => {
|
|
738
|
+
postMock.mockResolvedValueOnce(reviewCreated());
|
|
739
|
+
|
|
740
|
+
await postReview({ body: "Looks reasonable." });
|
|
741
|
+
|
|
742
|
+
expect(postMock).toHaveBeenCalledTimes(1);
|
|
743
|
+
expect(Object.keys(postBody(1))).not.toContain("comments");
|
|
744
|
+
expect(postBody(1)["body"]).toBe("Looks reasonable.");
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
test("anchors inline comments to the RIGHT side of the diff", async () => {
|
|
748
|
+
postMock.mockResolvedValueOnce(reviewCreated());
|
|
749
|
+
|
|
750
|
+
await postReview({
|
|
751
|
+
comments: [
|
|
752
|
+
{ path: "src/checkout.ts", line: 42, body: "cart may be undefined" },
|
|
753
|
+
{ path: "src/cart.ts", line: 7, body: "unused import" },
|
|
754
|
+
],
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
expect(postBody(1)["comments"]).toEqual([
|
|
758
|
+
{
|
|
759
|
+
path: "src/checkout.ts",
|
|
760
|
+
line: 42,
|
|
761
|
+
side: "RIGHT",
|
|
762
|
+
body: "cart may be undefined",
|
|
763
|
+
},
|
|
764
|
+
{ path: "src/cart.ts", line: 7, side: "RIGHT", body: "unused import" },
|
|
765
|
+
]);
|
|
766
|
+
});
|
|
767
|
+
|
|
768
|
+
test("pins the review to the reviewed commit when a sha is given", async () => {
|
|
769
|
+
postMock.mockResolvedValueOnce(reviewCreated());
|
|
770
|
+
|
|
771
|
+
await postReview({ commitSha: "abc123" });
|
|
772
|
+
|
|
773
|
+
expect(postBody(1)["commit_id"]).toBe("abc123");
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
test("omits commit_id entirely when no sha is given", async () => {
|
|
777
|
+
postMock.mockResolvedValueOnce(reviewCreated());
|
|
778
|
+
|
|
779
|
+
await postReview({ commitSha: undefined });
|
|
780
|
+
|
|
781
|
+
expect(Object.keys(postBody(1))).not.toContain("commit_id");
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
test("posts to the pull request's reviews endpoint with the installation token", async () => {
|
|
785
|
+
postMock.mockResolvedValueOnce(reviewCreated());
|
|
786
|
+
|
|
787
|
+
await postReview({});
|
|
788
|
+
|
|
789
|
+
expect(postCall(1).url.toString()).toBe(REVIEW_URL);
|
|
790
|
+
expect(postCall(1).headers["Authorization"]).toBe(
|
|
791
|
+
"Bearer ghs_installation_1",
|
|
792
|
+
);
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
test("returns the review's html url", async () => {
|
|
796
|
+
postMock.mockResolvedValueOnce(reviewCreated());
|
|
797
|
+
|
|
798
|
+
await expect(postReview({})).resolves.toBe(
|
|
799
|
+
"https://github.com/acme/checkout/pull/12#pullrequestreview-9001",
|
|
800
|
+
);
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
test("returns an empty string when GitHub answers without an html url", async () => {
|
|
804
|
+
postMock.mockResolvedValueOnce(okJson({ id: 1 }));
|
|
805
|
+
|
|
806
|
+
await expect(postReview({})).resolves.toBe("");
|
|
807
|
+
});
|
|
808
|
+
|
|
809
|
+
test("sends a unicode review body through verbatim", async () => {
|
|
810
|
+
postMock.mockResolvedValueOnce(reviewCreated());
|
|
811
|
+
|
|
812
|
+
const body: string = "レビュー: `cart` は undefined になり得ます 🚨";
|
|
813
|
+
|
|
814
|
+
await postReview({ body: body });
|
|
815
|
+
|
|
816
|
+
expect(postBody(1)["body"]).toBe(body);
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
describe("when GitHub rejects the inline anchors", () => {
|
|
820
|
+
/*
|
|
821
|
+
* 422 is GitHub's answer for an anchor that is not part of the diff — a
|
|
822
|
+
* line the agent misjudged, a file whose patch was elided, a review
|
|
823
|
+
* racing a force-push. Losing the whole review to one bad anchor is far
|
|
824
|
+
* worse than losing the anchors.
|
|
825
|
+
*/
|
|
826
|
+
test("retries with the body alone and succeeds", async () => {
|
|
827
|
+
postMock
|
|
828
|
+
.mockResolvedValueOnce(
|
|
829
|
+
errorResponse(422, {
|
|
830
|
+
message: "Validation Failed",
|
|
831
|
+
errors: [{ resource: "PullRequestReviewComment", field: "line" }],
|
|
832
|
+
}),
|
|
833
|
+
)
|
|
834
|
+
.mockResolvedValueOnce(reviewCreated());
|
|
835
|
+
|
|
836
|
+
await expect(
|
|
837
|
+
postReview({
|
|
838
|
+
comments: [{ path: "gone.ts", line: 999, body: "?" }],
|
|
839
|
+
}),
|
|
840
|
+
).resolves.toBe(
|
|
841
|
+
"https://github.com/acme/checkout/pull/12#pullrequestreview-9001",
|
|
842
|
+
);
|
|
843
|
+
|
|
844
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
845
|
+
expect(Object.keys(postBody(2))).not.toContain("comments");
|
|
846
|
+
});
|
|
847
|
+
|
|
848
|
+
test("keeps the body, the event and the commit on the retry", async () => {
|
|
849
|
+
postMock
|
|
850
|
+
.mockResolvedValueOnce(errorResponse(422, { message: "nope" }))
|
|
851
|
+
.mockResolvedValueOnce(reviewCreated());
|
|
852
|
+
|
|
853
|
+
await postReview({
|
|
854
|
+
body: "The retry must still say this.",
|
|
855
|
+
comments: [{ path: "gone.ts", line: 999, body: "?" }],
|
|
856
|
+
commitSha: "abc123",
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
expect(postBody(2)).toEqual({
|
|
860
|
+
body: "The retry must still say this.",
|
|
861
|
+
event: "COMMENT",
|
|
862
|
+
commit_id: "abc123",
|
|
863
|
+
});
|
|
864
|
+
});
|
|
865
|
+
|
|
866
|
+
/*
|
|
867
|
+
* The dropped anchors are the one thing a reader of the posted review
|
|
868
|
+
* cannot see, so the log has to name the pull request they were dropped
|
|
869
|
+
* from — and say what happened instead, rather than reading as a failure.
|
|
870
|
+
*/
|
|
871
|
+
test("logs which pull request lost its anchors, and that the body still went out", async () => {
|
|
872
|
+
postMock
|
|
873
|
+
.mockResolvedValueOnce(errorResponse(422, { message: "nope" }))
|
|
874
|
+
.mockResolvedValueOnce(reviewCreated());
|
|
875
|
+
|
|
876
|
+
await postReview({
|
|
877
|
+
comments: [{ path: "gone.ts", line: 999, body: "?" }],
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
const logged: string = String(debugSpy.mock.calls[0]?.[0] ?? "");
|
|
881
|
+
|
|
882
|
+
expect(logged).toContain("acme/checkout#12");
|
|
883
|
+
expect(logged).toContain("on its own");
|
|
884
|
+
expect(warnSpy).not.toHaveBeenCalled();
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
/*
|
|
888
|
+
* With no comments there is nothing to drop, so a retry would re-post an
|
|
889
|
+
* identical review — either a duplicate on the pull request or a second
|
|
890
|
+
* copy of the same failure in place of the real one.
|
|
891
|
+
*/
|
|
892
|
+
test("does NOT retry a 422 when there were no inline comments to drop", async () => {
|
|
893
|
+
const failure: HTTPErrorResponse = errorResponse(422, {
|
|
894
|
+
message: "Validation Failed",
|
|
895
|
+
});
|
|
896
|
+
|
|
897
|
+
postMock.mockResolvedValueOnce(failure);
|
|
898
|
+
|
|
899
|
+
await expect(postReview({})).rejects.toBe(failure);
|
|
900
|
+
|
|
901
|
+
expect(postMock).toHaveBeenCalledTimes(1);
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
test("does NOT retry a failure that is not a 422", async () => {
|
|
905
|
+
const failure: HTTPErrorResponse = errorResponse(403, {
|
|
906
|
+
message: "Resource not accessible by integration",
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
postMock.mockResolvedValueOnce(failure);
|
|
910
|
+
|
|
911
|
+
await expect(
|
|
912
|
+
postReview({
|
|
913
|
+
comments: [{ path: "src/a.ts", line: 1, body: "?" }],
|
|
914
|
+
}),
|
|
915
|
+
).rejects.toBe(failure);
|
|
916
|
+
|
|
917
|
+
expect(postMock).toHaveBeenCalledTimes(1);
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
test("surfaces the retry's own failure rather than the first one", async () => {
|
|
921
|
+
const retryFailure: HTTPErrorResponse = errorResponse(401, {
|
|
922
|
+
message: "Bad credentials",
|
|
923
|
+
});
|
|
924
|
+
|
|
925
|
+
postMock
|
|
926
|
+
.mockResolvedValueOnce(errorResponse(422, { message: "nope" }))
|
|
927
|
+
.mockResolvedValueOnce(retryFailure);
|
|
928
|
+
|
|
929
|
+
await expect(
|
|
930
|
+
postReview({
|
|
931
|
+
comments: [{ path: "src/a.ts", line: 1, body: "?" }],
|
|
932
|
+
}),
|
|
933
|
+
).rejects.toBe(retryFailure);
|
|
934
|
+
|
|
935
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
test("retries at most once, never in a loop", async () => {
|
|
939
|
+
postMock
|
|
940
|
+
.mockResolvedValueOnce(errorResponse(422, { message: "nope" }))
|
|
941
|
+
.mockResolvedValueOnce(errorResponse(422, { message: "nope again" }));
|
|
942
|
+
|
|
943
|
+
await expect(
|
|
944
|
+
postReview({
|
|
945
|
+
comments: [{ path: "src/a.ts", line: 1, body: "?" }],
|
|
946
|
+
}),
|
|
947
|
+
).rejects.toBeInstanceOf(HTTPErrorResponse);
|
|
948
|
+
|
|
949
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
950
|
+
});
|
|
951
|
+
});
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
describe("the installation token cache", () => {
|
|
955
|
+
beforeEach(() => {
|
|
956
|
+
postMock.mockResolvedValue(okJson(commentJson({})));
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
/*
|
|
960
|
+
* Answering one mention touches GitHub five or six times. Minting a fresh
|
|
961
|
+
* ghs_ token for each is pointless latency and a real slice of the
|
|
962
|
+
* installation's rate limit.
|
|
963
|
+
*/
|
|
964
|
+
test("mints ONE token for two conversation calls in a row", async () => {
|
|
965
|
+
await postAComment("first");
|
|
966
|
+
await postAComment("second");
|
|
967
|
+
|
|
968
|
+
expect(tokenSpy).toHaveBeenCalledTimes(1);
|
|
969
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
970
|
+
expect(postCall(1).headers["Authorization"]).toBe(
|
|
971
|
+
"Bearer ghs_installation_1",
|
|
972
|
+
);
|
|
973
|
+
expect(postCall(2).headers["Authorization"]).toBe(
|
|
974
|
+
"Bearer ghs_installation_1",
|
|
975
|
+
);
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
/*
|
|
979
|
+
* The cache is keyed by installation. One shared entry would send one
|
|
980
|
+
* tenant's write credential to another tenant's repository — the single
|
|
981
|
+
* worst thing this file could do.
|
|
982
|
+
*/
|
|
983
|
+
test("never reuses one installation's token on another installation's request", async () => {
|
|
984
|
+
tokenSpy
|
|
985
|
+
.mockResolvedValueOnce(installationToken("ghs_tenant_a", 60 * 60))
|
|
986
|
+
.mockResolvedValueOnce(installationToken("ghs_tenant_b", 60 * 60));
|
|
987
|
+
|
|
988
|
+
await GitHubConversation.createIssueComment({
|
|
989
|
+
installationId: "installation-a",
|
|
990
|
+
organizationName: "acme",
|
|
991
|
+
repositoryName: "checkout",
|
|
992
|
+
issueNumber: 1,
|
|
993
|
+
body: "hello",
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
await GitHubConversation.createIssueComment({
|
|
997
|
+
installationId: "installation-b",
|
|
998
|
+
organizationName: "other",
|
|
999
|
+
repositoryName: "shop",
|
|
1000
|
+
issueNumber: 1,
|
|
1001
|
+
body: "hello",
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
expect(tokenSpy).toHaveBeenCalledTimes(2);
|
|
1005
|
+
expect(postCall(1).headers["Authorization"]).toBe("Bearer ghs_tenant_a");
|
|
1006
|
+
expect(postCall(2).headers["Authorization"]).toBe("Bearer ghs_tenant_b");
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
test("re-mints a token GitHub has already expired", async () => {
|
|
1010
|
+
tokenSpy
|
|
1011
|
+
.mockResolvedValueOnce(installationToken("ghs_expired", -5 * 60))
|
|
1012
|
+
.mockResolvedValueOnce(installationToken("ghs_fresh", 60 * 60));
|
|
1013
|
+
|
|
1014
|
+
await postAComment("first");
|
|
1015
|
+
await postAComment("second");
|
|
1016
|
+
|
|
1017
|
+
expect(tokenSpy).toHaveBeenCalledTimes(2);
|
|
1018
|
+
expect(postCall(2).headers["Authorization"]).toBe("Bearer ghs_fresh");
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
/*
|
|
1022
|
+
* The safety margin. A token with 30 seconds left passes a naive
|
|
1023
|
+
* "expiresAt > now" check and then expires between that check and the
|
|
1024
|
+
* request that uses it — a 401 on a command the user watched acknowledge.
|
|
1025
|
+
*/
|
|
1026
|
+
test("re-mints a token that expires inside the safety margin", async () => {
|
|
1027
|
+
tokenSpy
|
|
1028
|
+
.mockResolvedValueOnce(installationToken("ghs_nearly_stale", 30))
|
|
1029
|
+
.mockResolvedValueOnce(installationToken("ghs_fresh", 60 * 60));
|
|
1030
|
+
|
|
1031
|
+
await postAComment("first");
|
|
1032
|
+
await postAComment("second");
|
|
1033
|
+
|
|
1034
|
+
expect(tokenSpy).toHaveBeenCalledTimes(2);
|
|
1035
|
+
expect(postCall(2).headers["Authorization"]).toBe("Bearer ghs_fresh");
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
test("keeps a token that is comfortably clear of the margin", async () => {
|
|
1039
|
+
tokenSpy.mockResolvedValueOnce(installationToken("ghs_good", 5 * 60));
|
|
1040
|
+
|
|
1041
|
+
await postAComment("first");
|
|
1042
|
+
await postAComment("second");
|
|
1043
|
+
|
|
1044
|
+
expect(tokenSpy).toHaveBeenCalledTimes(1);
|
|
1045
|
+
expect(postCall(2).headers["Authorization"]).toBe("Bearer ghs_good");
|
|
1046
|
+
});
|
|
1047
|
+
|
|
1048
|
+
test("clearTokenCache forces the next call to mint again", async () => {
|
|
1049
|
+
await postAComment("first");
|
|
1050
|
+
|
|
1051
|
+
GitHubConversation.clearTokenCache();
|
|
1052
|
+
|
|
1053
|
+
await postAComment("second");
|
|
1054
|
+
|
|
1055
|
+
expect(tokenSpy).toHaveBeenCalledTimes(2);
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
/*
|
|
1059
|
+
* Least privilege for the conversation path: it may write conversation
|
|
1060
|
+
* (GitHub routes pull request comments through the issues API) and
|
|
1061
|
+
* reviews, but only READ code. Anything that changes code goes through
|
|
1062
|
+
* GitHub.ts with its own, wider token.
|
|
1063
|
+
*/
|
|
1064
|
+
test("asks for issues:write and pull_requests:write but only contents:read", async () => {
|
|
1065
|
+
await postAComment("first");
|
|
1066
|
+
|
|
1067
|
+
expect(tokenSpy).toHaveBeenCalledWith("installation-1", {
|
|
1068
|
+
permissions: {
|
|
1069
|
+
issues: "write",
|
|
1070
|
+
pull_requests: "write",
|
|
1071
|
+
contents: "read",
|
|
1072
|
+
metadata: "read",
|
|
1073
|
+
},
|
|
1074
|
+
});
|
|
1075
|
+
});
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
describe("addReactionToComment / addReactionToIssue", () => {
|
|
1079
|
+
test("reacts on the comment and reports that it landed", async () => {
|
|
1080
|
+
postMock.mockResolvedValueOnce(okJson({ id: 1, content: "eyes" }));
|
|
1081
|
+
|
|
1082
|
+
await expect(
|
|
1083
|
+
GitHubConversation.addReactionToComment({
|
|
1084
|
+
...REPO,
|
|
1085
|
+
commentId: 99,
|
|
1086
|
+
reaction: GitHubReaction.Eyes,
|
|
1087
|
+
}),
|
|
1088
|
+
).resolves.toBe(true);
|
|
1089
|
+
|
|
1090
|
+
expect(postCall(1).url.toString()).toBe(
|
|
1091
|
+
"https://api.github.com/repos/acme/checkout/issues/comments/99/reactions",
|
|
1092
|
+
);
|
|
1093
|
+
expect(postBody(1)).toEqual({ content: "eyes" });
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
/*
|
|
1097
|
+
* The issue route and the comment route differ by one path segment, and
|
|
1098
|
+
* reacting on the wrong one puts an emoji on an unrelated object — comment
|
|
1099
|
+
* ids and issue numbers are different id spaces.
|
|
1100
|
+
*/
|
|
1101
|
+
test("reacts on the issue itself through the issue route", async () => {
|
|
1102
|
+
postMock.mockResolvedValueOnce(okJson({ id: 1, content: "rocket" }));
|
|
1103
|
+
|
|
1104
|
+
await expect(
|
|
1105
|
+
GitHubConversation.addReactionToIssue({
|
|
1106
|
+
...REPO,
|
|
1107
|
+
issueNumber: 7,
|
|
1108
|
+
reaction: GitHubReaction.Rocket,
|
|
1109
|
+
}),
|
|
1110
|
+
).resolves.toBe(true);
|
|
1111
|
+
|
|
1112
|
+
expect(postCall(1).url.toString()).toBe(
|
|
1113
|
+
"https://api.github.com/repos/acme/checkout/issues/7/reactions",
|
|
1114
|
+
);
|
|
1115
|
+
expect(postBody(1)).toEqual({ content: "rocket" });
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
/*
|
|
1119
|
+
* A reaction is the cheapest acknowledgement there is, and it is never
|
|
1120
|
+
* worth failing a command over. Every failure below has to come back as
|
|
1121
|
+
* false — a throw here would abort a run the user had already been told
|
|
1122
|
+
* was starting.
|
|
1123
|
+
*/
|
|
1124
|
+
test("returns false, without throwing, when GitHub refuses the reaction", async () => {
|
|
1125
|
+
postMock.mockResolvedValueOnce(
|
|
1126
|
+
errorResponse(403, { message: "Resource not accessible" }),
|
|
1127
|
+
);
|
|
1128
|
+
|
|
1129
|
+
await expect(
|
|
1130
|
+
GitHubConversation.addReactionToComment({
|
|
1131
|
+
...REPO,
|
|
1132
|
+
commentId: 99,
|
|
1133
|
+
reaction: GitHubReaction.Confused,
|
|
1134
|
+
}),
|
|
1135
|
+
).resolves.toBe(false);
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
test("returns false when the request itself throws", async () => {
|
|
1139
|
+
postMock.mockRejectedValueOnce(new Error("socket hang up"));
|
|
1140
|
+
|
|
1141
|
+
await expect(
|
|
1142
|
+
GitHubConversation.addReactionToComment({
|
|
1143
|
+
...REPO,
|
|
1144
|
+
commentId: 99,
|
|
1145
|
+
reaction: GitHubReaction.Eyes,
|
|
1146
|
+
}),
|
|
1147
|
+
).resolves.toBe(false);
|
|
1148
|
+
});
|
|
1149
|
+
|
|
1150
|
+
test("returns false when the installation token cannot be minted", async () => {
|
|
1151
|
+
tokenSpy.mockRejectedValueOnce(new Error("installation not found"));
|
|
1152
|
+
|
|
1153
|
+
await expect(
|
|
1154
|
+
GitHubConversation.addReactionToIssue({
|
|
1155
|
+
...REPO,
|
|
1156
|
+
issueNumber: 7,
|
|
1157
|
+
reaction: GitHubReaction.Eyes,
|
|
1158
|
+
}),
|
|
1159
|
+
).resolves.toBe(false);
|
|
1160
|
+
|
|
1161
|
+
expect(postMock).not.toHaveBeenCalled();
|
|
1162
|
+
});
|
|
1163
|
+
|
|
1164
|
+
test("returns false for a 404 on a comment that has since been deleted", async () => {
|
|
1165
|
+
postMock.mockResolvedValueOnce(
|
|
1166
|
+
errorResponse(404, { message: "Not Found" }),
|
|
1167
|
+
);
|
|
1168
|
+
|
|
1169
|
+
await expect(
|
|
1170
|
+
GitHubConversation.addReactionToComment({
|
|
1171
|
+
...REPO,
|
|
1172
|
+
commentId: 99,
|
|
1173
|
+
reaction: GitHubReaction.Confused,
|
|
1174
|
+
}),
|
|
1175
|
+
).resolves.toBe(false);
|
|
1176
|
+
});
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
describe("comment mapping — who wrote it, and is it a bot", () => {
|
|
1180
|
+
test("marks a comment as a bot when GitHub types the author as Bot", async () => {
|
|
1181
|
+
postMock.mockResolvedValueOnce(
|
|
1182
|
+
okJson(
|
|
1183
|
+
commentJson({ user: { login: "some-integration", type: "Bot" } }),
|
|
1184
|
+
),
|
|
1185
|
+
);
|
|
1186
|
+
|
|
1187
|
+
const comment: GitHubIssueComment = await postAComment("hi");
|
|
1188
|
+
|
|
1189
|
+
expect(comment.isBot).toBe(true);
|
|
1190
|
+
expect(comment.authorLogin).toBe("some-integration");
|
|
1191
|
+
});
|
|
1192
|
+
|
|
1193
|
+
/*
|
|
1194
|
+
* The other half of the OR. Some payloads report a bot author as type
|
|
1195
|
+
* "User" while the login still carries the suffix, and either one alone
|
|
1196
|
+
* would let the app answer itself.
|
|
1197
|
+
*/
|
|
1198
|
+
test("marks a comment as a bot when the login ends in [bot], whatever the type says", async () => {
|
|
1199
|
+
postMock.mockResolvedValueOnce(
|
|
1200
|
+
okJson(
|
|
1201
|
+
commentJson({ user: { login: "oneuptime[bot]", type: "User" } }),
|
|
1202
|
+
),
|
|
1203
|
+
);
|
|
1204
|
+
|
|
1205
|
+
await expect(postAComment("hi")).resolves.toMatchObject({
|
|
1206
|
+
isBot: true,
|
|
1207
|
+
authorLogin: "oneuptime[bot]",
|
|
1208
|
+
});
|
|
1209
|
+
});
|
|
1210
|
+
|
|
1211
|
+
test("marks a bot login with unusual casing as a bot", async () => {
|
|
1212
|
+
postMock.mockResolvedValueOnce(
|
|
1213
|
+
okJson(commentJson({ user: { login: "Renovate[BOT]", type: "User" } })),
|
|
1214
|
+
);
|
|
1215
|
+
|
|
1216
|
+
await expect(postAComment("hi")).resolves.toMatchObject({ isBot: true });
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1219
|
+
test("does not mark an ordinary human author as a bot", async () => {
|
|
1220
|
+
postMock.mockResolvedValueOnce(
|
|
1221
|
+
okJson(commentJson({ user: { login: "octocat", type: "User" } })),
|
|
1222
|
+
);
|
|
1223
|
+
|
|
1224
|
+
await expect(postAComment("hi")).resolves.toMatchObject({
|
|
1225
|
+
isBot: false,
|
|
1226
|
+
authorLogin: "octocat",
|
|
1227
|
+
});
|
|
1228
|
+
});
|
|
1229
|
+
|
|
1230
|
+
/*
|
|
1231
|
+
* The loop guard reads the AUTHOR, not the text. A human quoting the bot's
|
|
1232
|
+
* login must still be answered.
|
|
1233
|
+
*/
|
|
1234
|
+
test("does not mark a human who merely mentions a bot login as a bot", async () => {
|
|
1235
|
+
postMock.mockResolvedValueOnce(
|
|
1236
|
+
okJson(
|
|
1237
|
+
commentJson({
|
|
1238
|
+
body: "cc @oneuptime[bot] — can you look?",
|
|
1239
|
+
user: { login: "carol", type: "User" },
|
|
1240
|
+
}),
|
|
1241
|
+
),
|
|
1242
|
+
);
|
|
1243
|
+
|
|
1244
|
+
await expect(postAComment("hi")).resolves.toMatchObject({
|
|
1245
|
+
isBot: false,
|
|
1246
|
+
authorLogin: "carol",
|
|
1247
|
+
});
|
|
1248
|
+
});
|
|
1249
|
+
|
|
1250
|
+
test("survives a comment with no author object at all", async () => {
|
|
1251
|
+
postMock.mockResolvedValueOnce(okJson(commentJson({ user: null })));
|
|
1252
|
+
|
|
1253
|
+
await expect(postAComment("hi")).resolves.toMatchObject({
|
|
1254
|
+
isBot: false,
|
|
1255
|
+
authorLogin: "",
|
|
1256
|
+
});
|
|
1257
|
+
});
|
|
1258
|
+
|
|
1259
|
+
test("fills in safe defaults for a sparse comment payload", async () => {
|
|
1260
|
+
postMock.mockResolvedValueOnce(okJson({}));
|
|
1261
|
+
|
|
1262
|
+
await expect(postAComment("hi")).resolves.toEqual({
|
|
1263
|
+
commentId: 0,
|
|
1264
|
+
htmlUrl: "",
|
|
1265
|
+
body: "",
|
|
1266
|
+
authorLogin: "",
|
|
1267
|
+
isBot: false,
|
|
1268
|
+
createdAt: "",
|
|
1269
|
+
});
|
|
1270
|
+
});
|
|
1271
|
+
|
|
1272
|
+
test("throws when GitHub refuses the comment, unlike a reaction", async () => {
|
|
1273
|
+
const failure: HTTPErrorResponse = errorResponse(403, {
|
|
1274
|
+
message: "Resource not accessible by integration",
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
postMock.mockResolvedValueOnce(failure);
|
|
1278
|
+
|
|
1279
|
+
await expect(postAComment("hi")).rejects.toBe(failure);
|
|
1280
|
+
});
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
describe("updateIssueComment — one comment per command, edited in place", () => {
|
|
1284
|
+
/*
|
|
1285
|
+
* A long-running run that appended a comment per status change would turn
|
|
1286
|
+
* somebody's thread into a status log (and email every watcher each time).
|
|
1287
|
+
* The acknowledgement is EDITED instead.
|
|
1288
|
+
*/
|
|
1289
|
+
test("patches the existing comment instead of posting a new one", async () => {
|
|
1290
|
+
patchMock.mockResolvedValueOnce(okJson(commentJson({ body: "done" })));
|
|
1291
|
+
|
|
1292
|
+
const comment: GitHubIssueComment =
|
|
1293
|
+
await GitHubConversation.updateIssueComment({
|
|
1294
|
+
...REPO,
|
|
1295
|
+
commentId: 555,
|
|
1296
|
+
body: "done",
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
expect(postMock).not.toHaveBeenCalled();
|
|
1300
|
+
expect(patchMock).toHaveBeenCalledTimes(1);
|
|
1301
|
+
expect(patchCall(1).url.toString()).toBe(
|
|
1302
|
+
"https://api.github.com/repos/acme/checkout/issues/comments/555",
|
|
1303
|
+
);
|
|
1304
|
+
expect(patchCall(1).data).toEqual({ body: "done" });
|
|
1305
|
+
expect(comment.body).toBe("done");
|
|
1306
|
+
});
|
|
1307
|
+
|
|
1308
|
+
test("throws when the edit is refused, so the caller does not report success", async () => {
|
|
1309
|
+
const failure: HTTPErrorResponse = errorResponse(410, {
|
|
1310
|
+
message: "Gone",
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
patchMock.mockResolvedValueOnce(failure);
|
|
1314
|
+
|
|
1315
|
+
await expect(
|
|
1316
|
+
GitHubConversation.updateIssueComment({
|
|
1317
|
+
...REPO,
|
|
1318
|
+
commentId: 555,
|
|
1319
|
+
body: "done",
|
|
1320
|
+
}),
|
|
1321
|
+
).rejects.toBe(failure);
|
|
1322
|
+
});
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
describe("listIssueComments — the thread the agent reads", () => {
|
|
1326
|
+
function commentsPage(): Array<JSONObject> {
|
|
1327
|
+
return [
|
|
1328
|
+
commentJson({ id: 1, body: "one", user: { login: "a", type: "User" } }),
|
|
1329
|
+
commentJson({
|
|
1330
|
+
id: 2,
|
|
1331
|
+
body: "two",
|
|
1332
|
+
user: { login: "oneuptime[bot]", type: "Bot" },
|
|
1333
|
+
}),
|
|
1334
|
+
commentJson({
|
|
1335
|
+
id: 3,
|
|
1336
|
+
body: "three",
|
|
1337
|
+
user: { login: "c", type: "User" },
|
|
1338
|
+
}),
|
|
1339
|
+
commentJson({
|
|
1340
|
+
id: 4,
|
|
1341
|
+
body: "four",
|
|
1342
|
+
user: { login: "d", type: "User" },
|
|
1343
|
+
}),
|
|
1344
|
+
];
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
/*
|
|
1348
|
+
* GitHub is asked for the NEWEST comments, not the first page of the
|
|
1349
|
+
* oldest. On a 200-comment issue the first page is the discussion from six
|
|
1350
|
+
* months ago, while the review feedback that prompted this run is on the
|
|
1351
|
+
* last page — so a reader that took page one would feed the agent
|
|
1352
|
+
* everything except the request it is answering.
|
|
1353
|
+
*/
|
|
1354
|
+
test("asks GitHub for the newest comments, not the oldest page", async () => {
|
|
1355
|
+
getMock.mockResolvedValueOnce(okArray([]));
|
|
1356
|
+
|
|
1357
|
+
await GitHubConversation.listIssueComments({
|
|
1358
|
+
...REPO,
|
|
1359
|
+
issueNumber: 42,
|
|
1360
|
+
maxComments: 5,
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1363
|
+
const requestedUrl: string = getMock.mock.calls[0]![0].url.toString();
|
|
1364
|
+
|
|
1365
|
+
expect(requestedUrl).toContain("direction=desc");
|
|
1366
|
+
expect(requestedUrl).toContain("sort=created");
|
|
1367
|
+
});
|
|
1368
|
+
|
|
1369
|
+
/*
|
|
1370
|
+
* ...and then handed back OLDEST FIRST, so the agent reads the thread the
|
|
1371
|
+
* way a person would. The mock answers in the descending order GitHub
|
|
1372
|
+
* actually returns for that request, so this pins the whole round trip
|
|
1373
|
+
* rather than just the mapper.
|
|
1374
|
+
*/
|
|
1375
|
+
test("returns the newest comments, re-ordered oldest first", async () => {
|
|
1376
|
+
getMock.mockResolvedValueOnce(okArray([...commentsPage()].reverse()));
|
|
1377
|
+
|
|
1378
|
+
const comments: Array<GitHubIssueComment> =
|
|
1379
|
+
await GitHubConversation.listIssueComments({
|
|
1380
|
+
...REPO,
|
|
1381
|
+
issueNumber: 42,
|
|
1382
|
+
maxComments: 2,
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
expect(
|
|
1386
|
+
comments.map((comment: GitHubIssueComment): string => {
|
|
1387
|
+
return comment.body;
|
|
1388
|
+
}),
|
|
1389
|
+
).toEqual(["three", "four"]);
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
// The app's own comments come back too; the caller decides what to do.
|
|
1393
|
+
test("returns the app's own comments, marked as bot-authored", async () => {
|
|
1394
|
+
getMock.mockResolvedValueOnce(okArray([...commentsPage()].reverse()));
|
|
1395
|
+
|
|
1396
|
+
const comments: Array<GitHubIssueComment> =
|
|
1397
|
+
await GitHubConversation.listIssueComments({
|
|
1398
|
+
...REPO,
|
|
1399
|
+
issueNumber: 42,
|
|
1400
|
+
maxComments: 10,
|
|
1401
|
+
});
|
|
1402
|
+
|
|
1403
|
+
expect(comments).toHaveLength(4);
|
|
1404
|
+
expect(comments[1]?.isBot).toBe(true);
|
|
1405
|
+
expect(comments[0]?.isBot).toBe(false);
|
|
1406
|
+
});
|
|
1407
|
+
|
|
1408
|
+
/*
|
|
1409
|
+
* per_page is GitHub's hard maximum. Asking for more is refused outright,
|
|
1410
|
+
* which would lose the whole thread rather than truncate it.
|
|
1411
|
+
*/
|
|
1412
|
+
test("never asks GitHub for more than 100 comments in a page", async () => {
|
|
1413
|
+
getMock.mockResolvedValueOnce(okArray(commentsPage()));
|
|
1414
|
+
|
|
1415
|
+
await GitHubConversation.listIssueComments({
|
|
1416
|
+
...REPO,
|
|
1417
|
+
issueNumber: 42,
|
|
1418
|
+
maxComments: 5000,
|
|
1419
|
+
});
|
|
1420
|
+
|
|
1421
|
+
expect(getCall(1).url.toString()).toContain("per_page=100");
|
|
1422
|
+
});
|
|
1423
|
+
|
|
1424
|
+
test("survives a thread with no comments", async () => {
|
|
1425
|
+
getMock.mockResolvedValueOnce(okArray([]));
|
|
1426
|
+
|
|
1427
|
+
await expect(
|
|
1428
|
+
GitHubConversation.listIssueComments({
|
|
1429
|
+
...REPO,
|
|
1430
|
+
issueNumber: 42,
|
|
1431
|
+
maxComments: 10,
|
|
1432
|
+
}),
|
|
1433
|
+
).resolves.toEqual([]);
|
|
1434
|
+
});
|
|
1435
|
+
});
|
|
1436
|
+
|
|
1437
|
+
describe("getPullRequestDetails — feeding the fork check", () => {
|
|
1438
|
+
test("carries the head repository's full name through for the fork check", async () => {
|
|
1439
|
+
getMock.mockResolvedValueOnce(
|
|
1440
|
+
okJson({
|
|
1441
|
+
number: 12,
|
|
1442
|
+
head: {
|
|
1443
|
+
ref: "patch-1",
|
|
1444
|
+
sha: "abc123",
|
|
1445
|
+
repo: { full_name: "contributor/checkout" },
|
|
1446
|
+
},
|
|
1447
|
+
base: { ref: "main" },
|
|
1448
|
+
}),
|
|
1449
|
+
);
|
|
1450
|
+
|
|
1451
|
+
const pullRequest: GitHubPullRequestDetails =
|
|
1452
|
+
await GitHubConversation.getPullRequestDetails({
|
|
1453
|
+
...REPO,
|
|
1454
|
+
pullRequestNumber: 12,
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
expect(pullRequest.headRepositoryFullName).toBe("contributor/checkout");
|
|
1458
|
+
expect(
|
|
1459
|
+
GitHubConversation.isFromFork({
|
|
1460
|
+
headRepositoryFullName: pullRequest.headRepositoryFullName,
|
|
1461
|
+
organizationName: REPO.organizationName,
|
|
1462
|
+
repositoryName: REPO.repositoryName,
|
|
1463
|
+
}),
|
|
1464
|
+
).toBe(true);
|
|
1465
|
+
});
|
|
1466
|
+
|
|
1467
|
+
/*
|
|
1468
|
+
* GitHub sends `head.repo: null` once the fork is deleted. Reading that
|
|
1469
|
+
* must not throw, and must produce the null that isFromFork treats as
|
|
1470
|
+
* "not pushable".
|
|
1471
|
+
*/
|
|
1472
|
+
test("maps a deleted fork's head repository to null instead of throwing", async () => {
|
|
1473
|
+
getMock.mockResolvedValueOnce(
|
|
1474
|
+
okJson({
|
|
1475
|
+
number: 12,
|
|
1476
|
+
head: { ref: "patch-1", sha: "abc123", repo: null },
|
|
1477
|
+
base: { ref: "main" },
|
|
1478
|
+
}),
|
|
1479
|
+
);
|
|
1480
|
+
|
|
1481
|
+
const pullRequest: GitHubPullRequestDetails =
|
|
1482
|
+
await GitHubConversation.getPullRequestDetails({
|
|
1483
|
+
...REPO,
|
|
1484
|
+
pullRequestNumber: 12,
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
expect(pullRequest.headRepositoryFullName).toBeNull();
|
|
1488
|
+
expect(
|
|
1489
|
+
GitHubConversation.isFromFork({
|
|
1490
|
+
headRepositoryFullName: pullRequest.headRepositoryFullName,
|
|
1491
|
+
organizationName: REPO.organizationName,
|
|
1492
|
+
repositoryName: REPO.repositoryName,
|
|
1493
|
+
}),
|
|
1494
|
+
).toBe(true);
|
|
1495
|
+
});
|
|
1496
|
+
|
|
1497
|
+
test("reports a same-repository branch as pushable", async () => {
|
|
1498
|
+
getMock.mockResolvedValueOnce(
|
|
1499
|
+
okJson({
|
|
1500
|
+
number: 12,
|
|
1501
|
+
head: {
|
|
1502
|
+
ref: "oneuptime-ai/fix",
|
|
1503
|
+
sha: "abc123",
|
|
1504
|
+
repo: { full_name: "Acme/Checkout" },
|
|
1505
|
+
},
|
|
1506
|
+
base: { ref: "main" },
|
|
1507
|
+
}),
|
|
1508
|
+
);
|
|
1509
|
+
|
|
1510
|
+
const pullRequest: GitHubPullRequestDetails =
|
|
1511
|
+
await GitHubConversation.getPullRequestDetails({
|
|
1512
|
+
...REPO,
|
|
1513
|
+
pullRequestNumber: 12,
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
expect(
|
|
1517
|
+
GitHubConversation.isFromFork({
|
|
1518
|
+
headRepositoryFullName: pullRequest.headRepositoryFullName,
|
|
1519
|
+
organizationName: REPO.organizationName,
|
|
1520
|
+
repositoryName: REPO.repositoryName,
|
|
1521
|
+
}),
|
|
1522
|
+
).toBe(false);
|
|
1523
|
+
expect(pullRequest.headRefName).toBe("oneuptime-ai/fix");
|
|
1524
|
+
});
|
|
1525
|
+
});
|
|
1526
|
+
});
|