File: BuildManifest\BuildManifestClientTests.cs
Web Access
Project: src\src\VersionTools\Microsoft.DotNet.VersionTools.Tests\Microsoft.DotNet.VersionTools.Tests.csproj (Microsoft.DotNet.VersionTools.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.DotNet.VersionTools.Automation;
using Microsoft.DotNet.VersionTools.Automation.GitHubApi;
using Microsoft.DotNet.VersionTools.BuildManifest;
using Microsoft.DotNet.VersionTools.BuildManifest.Model;
using Microsoft.DotNet.VersionTools.Tests.Util;
using Moq;
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
using FluentAssertions;
 
namespace Microsoft.DotNet.VersionTools.Tests.BuildManifest
{
    public class BuildManifestClientTests : IDisposable
    {
        private readonly ITestOutputHelper _output;
        private readonly XunitTraceListener _listener;
 
        public BuildManifestClientTests(ITestOutputHelper output)
        {
            _output = output;
            Trace.Listeners.Add(_listener = new XunitTraceListener(_output));
        }
 
        public void Dispose()
        {
            _listener.Flush();
            Trace.Listeners.Remove(_listener);
        }
 
        [Fact]
        public async Task TestPushNewBuildAsync()
        {
            var mockGitHub = new Mock<IGitHubClient>(MockBehavior.Strict);
 
            var client = new BuildManifestClient(mockGitHub.Object);
            var build = new OrchestratedBuildModel(new BuildIdentity { Name = "orch", BuildId = "123"});
            var proj = new GitHubProject("versions", "dotnet");
            string @ref = "heads/master";
            string basePath = "build-info/dotnet/product/cli/master";
            string message = "Test build upload commit";
 
            string fakeCommitHash = "fakeCommitHash";
            string fakeTreeHash = "fakeTreeHash";
            string fakeNewCommitHash = "fakeNewCommitHash";
 
            mockGitHub
                .Setup(c => c.GetReferenceAsync(proj, @ref))
                .ReturnsAsync(() => new GitReference
                {
                    Object = new GitReferenceObject { Sha = fakeCommitHash }
                });
 
            mockGitHub
                .Setup(c => c.PostTreeAsync(
                    proj,
                    fakeCommitHash,
                    It.Is<GitObject[]>(
                        objects =>
                            objects.Length == 2 &&
                            objects[0].Path == $"{basePath}/{BuildManifestClient.BuildManifestXmlName}" &&
                            objects[0].Content == build.ToXml().ToString() &&
                            objects[1].Path == $"{basePath}/{SemaphoreModel.BuildSemaphorePath}" &&
                            objects[1].Content == build.Identity.BuildId + "\n")))
                .ReturnsAsync(() => new GitTree { Sha = fakeTreeHash });
 
            mockGitHub
                .Setup(c => c.PostCommitAsync(
                    proj,
                    message,
                    fakeTreeHash,
                    It.Is<string[]>(parents => parents.Single() == fakeCommitHash)))
                .ReturnsAsync(() => new GitCommit { Sha = fakeNewCommitHash });
 
            mockGitHub
                .Setup(c => c.PatchReferenceAsync(proj, @ref, fakeNewCommitHash, false))
                .ReturnsAsync(() => null);
 
            await client.PushNewBuildAsync(
                new BuildManifestLocation(proj, @ref, basePath),
                build,
                null,
                message);
 
            mockGitHub.VerifyAll();
        }
 
        [Fact]
        public async Task TestPushChangeSemaphoreAsync()
        {
            var mockGitHub = new Mock<IGitHubClient>(MockBehavior.Strict);
 
            var client = new BuildManifestClient(mockGitHub.Object);
            var proj = new GitHubProject("versions", "dotnet");
            string @ref = "heads/master";
            string basePath = "build-info/dotnet/product/cli/master";
            string message = "Test change manifest commit";
            string addSemaphorePath = "add-identity.semaphore";
 
            var fakeExistingBuild = new OrchestratedBuildModel(new BuildIdentity { Name = "orch", BuildId = "123"});
            string fakeExistingBuildString = fakeExistingBuild.ToXml().ToString();
            string fakeCommitHash = "fakeCommitHash";
            string fakeTreeHash = "fakeTreeHash";
            string fakeNewCommitHash = "fakeNewCommitHash";
 
            mockGitHub
                .Setup(c => c.GetReferenceAsync(proj, @ref))
                .ReturnsAsync(() => new GitReference
                {
                    Object = new GitReferenceObject { Sha = fakeCommitHash }
                });
 
            mockGitHub
                .Setup(c => c.GetGitHubFileContentsAsync(
                    $"{basePath}/{BuildManifestClient.BuildManifestXmlName}",
                    proj,
                    fakeCommitHash))
                .ReturnsAsync(() => fakeExistingBuildString);
 
            mockGitHub
                .Setup(c => c.PostTreeAsync(
                    proj,
                    fakeCommitHash,
                    It.Is<GitObject[]>(
                        objects =>
                            objects.Length == 1 &&
                            objects[0].Path == $"{basePath}/{addSemaphorePath}" &&
                            objects[0].Content == fakeExistingBuild.Identity.BuildId + "\n")))
                .ReturnsAsync(() => new GitTree { Sha = fakeTreeHash });
 
            mockGitHub
                .Setup(c => c.PostCommitAsync(
                    proj,
                    message,
                    fakeTreeHash,
                    It.Is<string[]>(parents => parents.Single() == fakeCommitHash)))
                .ReturnsAsync(() => new GitCommit { Sha = fakeNewCommitHash });
 
            mockGitHub
                .Setup(c => c.PatchReferenceAsync(proj, @ref, fakeNewCommitHash, false))
                .ReturnsAsync(() => null);
 
            await client.PushChangeAsync(
                new BuildManifestChange(
                    new BuildManifestLocation(proj, @ref, basePath),
                    message,
                    fakeExistingBuild.Identity.BuildId,
                    new[] { addSemaphorePath },
                    _ => { }));
 
            mockGitHub.VerifyAll();
        }
 
        [Fact]
        public async Task TestPushConflictingChangeAsync()
        {
            var mockGitHub = new Mock<IGitHubClient>(MockBehavior.Strict);
 
            var client = new BuildManifestClient(mockGitHub.Object);
            var proj = new GitHubProject("versions", "dotnet");
            string @ref = "heads/master";
            string basePath = "build-info/dotnet/product/cli/master";
            string message = "Test change manifest commit";
            string addSemaphorePath = "add-identity.semaphore";
 
            var fakeExistingBuild = new OrchestratedBuildModel(new BuildIdentity { Name = "orch", BuildId = "123" });
            var fakeNewExistingBuild = new OrchestratedBuildModel(new BuildIdentity { Name = "orch", BuildId = "456" });
            string fakeCommitHash = "fakeCommitHash";
 
            mockGitHub
                .Setup(c => c.GetReferenceAsync(proj, @ref))
                .ReturnsAsync(() => new GitReference
                {
                    Object = new GitReferenceObject { Sha = fakeCommitHash }
                });
 
            mockGitHub
                .Setup(c => c.GetGitHubFileContentsAsync(It.IsAny<string>(), proj, fakeCommitHash))
                .ReturnsAsync(() => fakeNewExistingBuild.ToXml().ToString());
 
            var pushClient = client.PushChangeAsync(
                    new BuildManifestChange(
                        new BuildManifestLocation(proj, @ref, basePath),
                        message,
                        fakeExistingBuild.Identity.BuildId,
                        new[] { addSemaphorePath },
                        _ => { }
                        ));
            Func<Task> act = async () => { await pushClient; };
            await act.Should().ThrowAsync<ManifestChangeOutOfDateException>();
 
            mockGitHub.VerifyAll();
        }
 
        [Fact]
        public async Task TestPushConflictAsync()
        {
            var mockGitHub = new Mock<IGitHubClient>(MockBehavior.Strict);
 
            var client = new BuildManifestClient(mockGitHub.Object);
            var build = new OrchestratedBuildModel(new BuildIdentity { Name = "orch", BuildId = "123" });
            var proj = new GitHubProject("versions", "dotnet");
            string @ref = "heads/master";
            string basePath = "build-info/dotnet/product/cli/master";
            string message = "Test build upload commit";
 
            string fakeCommitHash = "fakeCommitHash";
            string fakeTreeHash = "fakeTreeHash";
            string fakeNewCommitHash = "fakeNewCommitHash";
 
            mockGitHub
                .Setup(c => c.GetReferenceAsync(proj, @ref))
                .ReturnsAsync(() => new GitReference
                {
                    Object = new GitReferenceObject { Sha = fakeCommitHash }
                });
 
            mockGitHub
                .Setup(c => c.PostTreeAsync(proj, fakeCommitHash, It.IsAny<GitObject[]>()))
                .ReturnsAsync(() => new GitTree { Sha = fakeTreeHash });
 
            mockGitHub
                .Setup(c => c.PostCommitAsync(proj, message, fakeTreeHash, It.IsAny<string[]>()))
                .ReturnsAsync(() => new GitCommit { Sha = fakeNewCommitHash });
 
            mockGitHub
                .Setup(c => c.PatchReferenceAsync(proj, @ref, fakeNewCommitHash, false))
                .Callback(() =>
                {
                    // Once the exception is hit, let the next patch call work.
                    mockGitHub
                        .Setup(c => c.PatchReferenceAsync(proj, @ref, fakeNewCommitHash, false))
                        .ReturnsAsync(() => null);
                })
                .ThrowsAsync(new NotFastForwardUpdateException("Testing non-fast-forward update."));
 
            await client.PushNewBuildAsync(
                new BuildManifestLocation(proj, @ref, basePath),
                build,
                null,
                message);
 
            mockGitHub.VerifyAll();
        }
    }
}