public async Task <IActionResult> UpdateSubscription(Guid id, [FromBody] SubscriptionUpdate update) { Data.Models.Subscription subscription = await _context.Subscriptions.Where(sub => sub.Id == id) .FirstOrDefaultAsync(); if (subscription == null) { return(NotFound()); } var doUpdate = false; if (!string.IsNullOrEmpty(update.SourceRepository)) { subscription.SourceRepository = update.SourceRepository; doUpdate = true; } if (update.Policy != null) { subscription.PolicyObject = update.Policy.ToDb(); doUpdate = true; } if (!string.IsNullOrEmpty(update.ChannelName)) { Channel channel = await _context.Channels.Where(c => c.Name == update.ChannelName) .FirstOrDefaultAsync(); if (channel == null) { return(BadRequest( new ApiError( "The request is invalid", new[] { $"The channel '{update.ChannelName}' could not be found." }))); } subscription.Channel = channel; doUpdate = true; } if (update.Enabled.HasValue) { subscription.Enabled = update.Enabled.Value; doUpdate = true; } if (doUpdate) { _context.Subscriptions.Update(subscription); await _context.SaveChangesAsync(); } return(Ok(new Subscription(subscription))); }
public async Task <IActionResult> Create([FromBody] SubscriptionData subscription) { Channel channel = await _context.Channels.Where(c => c.Name == subscription.ChannelName) .FirstOrDefaultAsync(); if (channel == null) { return(BadRequest( new ApiError( "the request is invalid", new[] { $"The channel '{subscription.ChannelName}' could not be found." }))); } Repository repo = await _context.Repositories.FindAsync(subscription.TargetRepository); if (subscription.TargetRepository.Contains("github.com")) { // If we have no repository information or an invalid installation id // then we will fail when trying to update things, so we fail early. if (repo == null || repo.InstallationId <= 0) { return(BadRequest( new ApiError( "the request is invalid", new[] { $"The repository '{subscription.TargetRepository}' does not have an associated github installation. " + "The Maestro github application must be installed by the repository's owner and given access to the repository." }))); } } // In the case of a dev.azure.com repository, we don't have an app installation, // but we should add an entry in the repositories table, as this is required when // adding a new subscription policy. // NOTE: // There is a good chance here that we will need to also handle <account>.visualstudio.com // but leaving it out for now as it would be preferred to use the new format else if (subscription.TargetRepository.Contains("dev.azure.com")) { if (repo == null) { _context.Repositories.Add( new Data.Models.Repository { RepositoryName = subscription.TargetRepository, InstallationId = default });
public async Task <IActionResult> Create([FromBody] SubscriptionData subscription) { Channel channel = await _context.Channels.Where(c => c.Name == subscription.ChannelName) .FirstOrDefaultAsync(); if (channel == null) { return(BadRequest( new ApiError( "the request is invalid", new[] { $"The channel '{subscription.ChannelName}' could not be found." }))); } if (subscription.TargetRepository.Contains("github.com")) { // If we have no repository information or an invalid installation id // then we will fail when trying to update things, so we fail early. Repository repo = await _context.Repositories.FindAsync(subscription.TargetRepository); if (repo == null || repo.InstallationId <= 0) { return(BadRequest( new ApiError( "the request is invalid", new[] { $"The repository '{subscription.TargetRepository}' does not have an associated github installation. " + "The Maestro github application must be installed by the repository's owner and given access to the repository." }))); } } Data.Models.Subscription subscriptionModel = subscription.ToDb(); subscriptionModel.Channel = channel; await _context.Subscriptions.AddAsync(subscriptionModel); await _context.SaveChangesAsync(); return(CreatedAtRoute( new { action = "GetSubscription", id = subscriptionModel.Id }, new Subscription(subscriptionModel))); }
public async Task Test( bool mergeOnPassedChecks, PrStatus prStatus, bool existingPrHasChecks, bool existingPrPassedChecks) { const string prCommentId = "5"; Channel channel = CreateChannel(); Build oldBuild = CreateOldBuild(); Build build = CreateNewBuild(); var buildChannels = new[] { new BuildChannel { Build = oldBuild, Channel = channel }, new BuildChannel { Build = build, Channel = channel } }; Subscription subscription = CreateSubscription(mergeOnPassedChecks ? new MergePolicyDefinition { Name = "AllChecksSuccessful" } : null); subscription.Channel = channel; var repoInstallation = new RepoInstallation { Repository = subscription.TargetRepository, InstallationId = 1 }; Asset asset = build.Assets[0]; await Context.RepoInstallations.AddAsync(repoInstallation); await Context.Subscriptions.AddAsync(subscription); await Context.BuildChannels.AddRangeAsync(buildChannels); await Context.SaveChangesAsync(); var actorId = new ActorId(subscription.Id); var existingPr = "https://repo.pr/existing"; var pr = "https://repo.pr/new"; bool shouldMergeExistingPr = prStatus == PrStatus.Open && mergeOnPassedChecks && existingPrHasChecks && existingPrPassedChecks; void SetupCreatePr() { Darc.Setup( d => d.CreatePullRequestAsync( subscription.TargetRepository, subscription.TargetBranch, build.Commit, It.IsAny <IList <AssetData> >(), null, It.IsAny <string>(), It.IsAny <string>())) .ReturnsAsync( ( string repo, string branch, string commit, IList <AssetData> assets, string baseBranch, string title, string description) => { assets.Should().BeEquivalentTo(new AssetData { Name = asset.Name, Version = asset.Version }); return(pr); }); } void SetupUpdatePr() { Darc.Setup( r => r.UpdatePullRequestAsync( existingPr, build.Commit, subscription.TargetBranch, It.IsAny <IList <AssetData> >(), It.IsAny <string>(), It.IsAny <string>())) .ReturnsAsync( ( string url, string commit, string branch, IList <AssetData> assets, string title, string description) => { return(url); }); } void SetupExistingPr() { StateManager.Data[SubscriptionActor.PullRequest] = new InProgressPullRequest { BuildId = oldBuild.Id, Url = existingPr }; Darc.Setup(r => r.GetPullRequestStatusAsync(existingPr)).ReturnsAsync(prStatus); if (mergeOnPassedChecks && prStatus == PrStatus.Open) { if (existingPrHasChecks) { Darc.Setup(r => r.GetPullRequestChecksAsync(existingPr)) .ReturnsAsync( new List <Check> { new Check( existingPrPassedChecks ? CheckState.Success : CheckState.Failure, "check", "https://check.stuff/1") }); } else { Darc.Setup(r => r.GetPullRequestChecksAsync(existingPr)).ReturnsAsync(new List <Check>()); } } if (prStatus == PrStatus.Open) { Darc.Setup(d => d.CreatePullRequestCommentAsync(It.IsAny <string>(), It.IsAny <string>())) .ReturnsAsync(prCommentId); } if (shouldMergeExistingPr) { Darc.Setup(r => r.MergePullRequestAsync(existingPr, null)) .Returns(Task.CompletedTask); } } switch (prStatus) { case PrStatus.None: SetupCreatePr(); break; case PrStatus.Open: SetupExistingPr(); if (shouldMergeExistingPr) { SetupCreatePr(); } else { SetupUpdatePr(); } break; case PrStatus.Merged: SetupExistingPr(); SetupCreatePr(); break; case PrStatus.Closed: SetupExistingPr(); SetupCreatePr(); break; } var actor = ActivatorUtilities.CreateInstance <SubscriptionActor>(Scope.ServiceProvider, actorId); await actor.UpdateAsync(build.Id); if (shouldMergeExistingPr || prStatus == PrStatus.Merged) { subscription.LastAppliedBuildId.Should().Be(oldBuild.Id); } else { subscription.LastAppliedBuildId.Should().Be(null); } StateManager.Data.Should() .BeEquivalentTo( new Dictionary <string, object> { [SubscriptionActor.PullRequest] = new InProgressPullRequest { BuildId = build.Id, Url = prStatus == PrStatus.Open && !shouldMergeExistingPr ? existingPr : pr, StatusCommentId = prStatus == PrStatus.Open && !shouldMergeExistingPr ? prCommentId : null } }); Reminders.Data.Should() .BeEquivalentTo( new Dictionary <string, MockReminderManager.Reminder> { [SubscriptionActor.PullRequestCheck] = new MockReminderManager.Reminder( SubscriptionActor.PullRequestCheck, Array.Empty <byte>(), TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5)) }); }
public async Task SynchronizeInProgressPRAsync( PrStatus prStatus, bool existingPrHasChecks, bool existingPrPassedChecks) { Channel channel = CreateChannel(); Build oldBuild = CreateOldBuild(); Build build = CreateNewBuild(); var buildChannels = new[] { new BuildChannel { Build = oldBuild, Channel = channel }, new BuildChannel { Build = build, Channel = channel } }; Subscription subscription = CreateSubscription(new MergePolicyDefinition { Name = "AllChecksSuccessful", }); subscription.Channel = channel; var repoInstallation = new RepoInstallation { Repository = subscription.TargetRepository, InstallationId = 1 }; await Context.RepoInstallations.AddAsync(repoInstallation); await Context.Subscriptions.AddAsync(subscription); await Context.BuildChannels.AddRangeAsync(buildChannels); await Context.SaveChangesAsync(); var existingPr = "https://repo.pr/existing"; var actorId = new ActorId(subscription.Id); StateManager.Data[SubscriptionActor.PullRequest] = new InProgressPullRequest { BuildId = oldBuild.Id, Url = existingPr }; Darc.Setup(d => d.GetPullRequestStatusAsync(existingPr)).ReturnsAsync(prStatus); if (prStatus == PrStatus.Open) { if (existingPrHasChecks) { Darc.Setup(d => d.GetPullRequestChecksAsync(existingPr)) .ReturnsAsync( new List <Check> { new Check( existingPrPassedChecks ? CheckState.Success : CheckState.Failure, "check", "https://check.stuff/1") }); } else { Darc.Setup(d => d.GetPullRequestChecksAsync(existingPr)).ReturnsAsync(new List <Check>()); } if (existingPrHasChecks && existingPrPassedChecks) { Darc.Setup(d => d.MergePullRequestAsync(existingPr, null)) .Returns(Task.CompletedTask); } Darc.Setup(d => d.CreatePullRequestCommentAsync(It.IsAny <string>(), It.IsAny <string>())) .ReturnsAsync("5"); } var actor = ActivatorUtilities.CreateInstance <SubscriptionActor>(Scope.ServiceProvider, actorId); await actor.ReceiveReminderAsync( SubscriptionActor.PullRequestCheck, Array.Empty <byte>(), TimeSpan.Zero, TimeSpan.FromMinutes(5)); switch (prStatus) { case PrStatus.Merged: subscription.LastAppliedBuildId.Should().Be(oldBuild.Id); goto case PrStatus.Closed; case PrStatus.Closed: Reminders.Data.Should().BeEmpty(); StateManager.Data.Should().BeEmpty(); break; } }