File: Tasks\OneDeploy\OneDeployTests.cs
Web Access
Project: ..\..\..\test\Microsoft.NET.Sdk.Publish.Tasks.Tests\Microsoft.NET.Sdk.Publish.Tasks.Tests.csproj (Microsoft.NET.Sdk.Publish.Tasks.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#nullable disable
 
using System.Net;
using System.Net.Http;
using Microsoft.NET.Sdk.Publish.Tasks.MsDeploy;
using Microsoft.NET.Sdk.Publish.Tasks.Properties;
using Moq;
 
namespace Microsoft.NET.Sdk.Publish.Tasks.OneDeploy.Tests;
 
/// <summary>
/// Unit Tests for <see cref="OneDeploy"/>
/// </summary>
public partial class OneDeployTests
{
    private const string Username = "someUser";
    private const string NotShareableValue = "PLACEHOLDER";
    private const string PublishUrl = "https://mysite.scm.azurewebsites.net";
    private const string UserAgentName = "websdk"; // as OneDeploy.UserAgentName
    private const string DeploymentUrl = $@"{PublishUrl}/api/deployments/056f49ce-fcd7-497c-929b-d74bc6f8905e";
    private const string DeploymentLogUrl = $@"{DeploymentUrl}/log";
    private const string DefaultApiPath = "api/publish"; // as OneDeploy.OneDeployApiPath
    private const string DefaultQueryParam = "RemoteBuild=false"; // as OneDeploy.OneDeployQueryParam
 
    private static readonly Uri OneDeployUri = new UriBuilder(PublishUrl)
    {
        Path = DefaultApiPath,
        Query = DefaultQueryParam
    }.Uri;
 
    private static string _fileToPublish;
    public static string FileToPublish
    {
        get
        {
            if (_fileToPublish == null)
            {
                string codebase = typeof(OneDeployTests).Assembly.Location;
                string assemblyPath = new Uri(codebase, UriKind.Absolute).LocalPath;
                string baseDirectory = Path.GetDirectoryName(assemblyPath);
                _fileToPublish = Path.Combine(baseDirectory, Path.Combine("Resources", "TestPublishContents.zip"));
            }
 
            return _fileToPublish;
        }
    }
 
    [Theory]
    [InlineData(DeploymentStatus.Success, HttpStatusCode.OK, true)]
    [InlineData(DeploymentStatus.Success, HttpStatusCode.Accepted, true)]
    [InlineData(DeploymentStatus.PartialSuccess, HttpStatusCode.OK, true)]
    [InlineData(DeploymentStatus.PartialSuccess, HttpStatusCode.Accepted, true)]
    [InlineData(DeploymentStatus.Failed, HttpStatusCode.OK, false)]
    [InlineData(DeploymentStatus.Failed, HttpStatusCode.Accepted, false)]
    [InlineData(DeploymentStatus.Conflict, HttpStatusCode.OK, false)]
    [InlineData(DeploymentStatus.Conflict, HttpStatusCode.Accepted, false)]
    [InlineData(DeploymentStatus.Cancelled, HttpStatusCode.OK, false)]
    [InlineData(DeploymentStatus.Cancelled, HttpStatusCode.Accepted, false)]
    [InlineData(DeploymentStatus.Unknown, HttpStatusCode.OK, false)]
    [InlineData(DeploymentStatus.Unknown, HttpStatusCode.Accepted, false)]
 
    public async Task OneDeploy_Execute_Completes(DeploymentStatus deployStatus, HttpStatusCode statusCode, bool expectedResult)
    {
        // Arrange
        var httpClientMock = GetHttpClientMock(statusCode);
 
        var deploymentStatusServiceMock = GetDeploymentStatusServiceMock(httpClientMock.Object, deployStatus);
 
        // set messages to log according to result
        var taskLoggerMock = new Mock<ITaskLogger>();
        taskLoggerMock.Setup(l => l.LogMessage(Build.Framework.MessageImportance.High, string.Format(Resources.ONEDEPLOY_PublishingOneDeploy, FileToPublish, OneDeployUri.AbsoluteUri.ToString())));
        taskLoggerMock.Setup(l => l.LogMessage(string.Format(Resources.ONEDEPLOY_Uploaded, FileToPublish)));
 
        if (expectedResult)
        {
            taskLoggerMock.Setup(l => l.LogMessage(Build.Framework.MessageImportance.High, Resources.ONEDEPLOY_Success));
        }
        else
        {
            var failedDeployMsg = string.Format(Resources.ONEDEPLOY_FailedWithLogs, FileToPublish, OneDeployUri.AbsoluteUri, deployStatus, DeploymentLogUrl);
            taskLoggerMock.Setup(l => l.LogError(failedDeployMsg));
        }
 
        var oneDeployTask = new OneDeploy(taskLoggerMock.Object);
 
        // Act
        var result = await oneDeployTask.OneDeployAsync(
            FileToPublish, Username, NotShareableValue, PublishUrl, $"{UserAgentName}/8.0", webjobName: null, webjobType: null, httpClientMock.Object, deploymentStatusServiceMock.Object, CancellationToken.None);
 
        // Assert: deployment operation runs to completion with expected result
        Assert.Equal(expectedResult, result);
 
        httpClientMock.VerifyAll();
        deploymentStatusServiceMock.VerifyAll();
        taskLoggerMock.VerifyAll();
    }
 
    [Theory]
    [InlineData("not-a-location-url")]
    [InlineData(null)]
    public async Task OneDeploy_Execute_Deploy_Location_Missing(string invalidLocationHeaderValue)
    {
        // Arrange
        var httpClientMock = new Mock<IHttpClient>();
 
        // Request
        HttpRequestMessage requestMessage = new();
        httpClientMock.Setup(hc => hc.DefaultRequestHeaders).Returns(requestMessage.Headers);
 
        // Response
        HttpResponseMessage responseMessage = new(HttpStatusCode.OK);
        if (invalidLocationHeaderValue is not null)
        {
            responseMessage.Headers.Add("Location", invalidLocationHeaderValue);
        }
 
        // PostAsync()
        httpClientMock.Setup(hc => hc.PostAsync(OneDeployUri, It.IsAny<StreamContent>())).ReturnsAsync(responseMessage);
 
        var deploymentStatusServiceMock = new Mock<IDeploymentStatusService<DeploymentResponse>>();
 
        // set messages to log according to result
        var taskLoggerMock = new Mock<ITaskLogger>();
        taskLoggerMock.Setup(l => l.LogMessage(Build.Framework.MessageImportance.High, string.Format(Resources.ONEDEPLOY_PublishingOneDeploy, FileToPublish, OneDeployUri.AbsoluteUri.ToString())));
        taskLoggerMock.Setup(l => l.LogMessage(string.Format(Resources.ONEDEPLOY_Uploaded, FileToPublish)));
 
        var oneDeployTask = new OneDeploy(taskLoggerMock.Object);
 
        // Act
        var result = await oneDeployTask.OneDeployAsync(
            FileToPublish, Username, NotShareableValue, PublishUrl, $"{UserAgentName}/8.0", webjobName: null, webjobType: null, httpClientMock.Object, deploymentStatusServiceMock.Object, CancellationToken.None);
 
        // Assert: deployment operation runs to completion, without polling the deployment because 'Location' header was not found in response
        Assert.True(result);
 
        httpClientMock.VerifyAll();
        deploymentStatusServiceMock.VerifyAll();
        taskLoggerMock.VerifyAll();
    }
 
    [Theory]
    [InlineData(HttpStatusCode.Forbidden)]
    [InlineData(HttpStatusCode.NotFound)]
    [InlineData(HttpStatusCode.RequestTimeout)]
    [InlineData(HttpStatusCode.InternalServerError)]
    public async Task OneDeploy_Execute_HttpResponse_Fail(HttpStatusCode statusCode)
    {
        // Arrange
        var httpClientMock = GetHttpClientMock(statusCode, deployLocationHeader: null);
 
        var deploymentStatusServiceMock = new Mock<IDeploymentStatusService<DeploymentResponse>>();
 
        // set messages to log
        var taskLoggerMock = new Mock<ITaskLogger>();
        taskLoggerMock.Setup(l => l.LogMessage(Build.Framework.MessageImportance.High, string.Format(Resources.ONEDEPLOY_PublishingOneDeploy, FileToPublish, OneDeployUri.AbsoluteUri.ToString())));
        taskLoggerMock.Setup(l => l.LogError(string.Format(Resources.ONEDEPLOY_FailedDeployRequest, OneDeployUri.AbsoluteUri.ToString(), statusCode)));
 
        var oneDeployTask = new OneDeploy(taskLoggerMock.Object);
 
        // Act
        var result = await oneDeployTask.OneDeployAsync(
            FileToPublish, Username, NotShareableValue, PublishUrl, $"{UserAgentName}/8.0", webjobName: null, webjobType: null, httpClientMock.Object, deploymentStatusServiceMock.Object, CancellationToken.None);
 
        // Assert: deployment operation fails because HTTP POST request to upload the package returns a failed HTTP Response
        Assert.False(result);
 
        httpClientMock.VerifyAll();
        deploymentStatusServiceMock.VerifyAll();
        taskLoggerMock.VerifyAll();
    }
 
    [Theory]
    [InlineData("not-valid-url")]
    [InlineData("")]
    [InlineData(null)]
    public async Task OneDeploy_Execute_PublishUrl_Invalid(string invalidUrl)
    {
        // Arrange
        var httpClientMock = new Mock<IHttpClient>();
 
        var deploymentStatusServiceMock = new Mock<IDeploymentStatusService<DeploymentResponse>>();
 
        // set messages to log
        var taskLoggerMock = new Mock<ITaskLogger>();
        taskLoggerMock.Setup(l => l.LogError(string.Format(Resources.ONEDEPLOY_InvalidPublishUrl, invalidUrl)));
 
        var oneDeployTask = new OneDeploy(taskLoggerMock.Object);
 
        // Act
        var result = await oneDeployTask.OneDeployAsync(
            FileToPublish, Username, NotShareableValue, invalidUrl, $"{UserAgentName}/8.0", webjobName: null, webjobType: null, httpClientMock.Object, deploymentStatusServiceMock.Object, CancellationToken.None);
 
        // Assert: deployment operation fails because 'PublishUrl' is not valid
        Assert.False(result);
 
        httpClientMock.VerifyAll();
        deploymentStatusServiceMock.VerifyAll();
        taskLoggerMock.VerifyAll();
    }
 
    [Theory]
    [InlineData("z:\\Missing\\Directory\\File")]
    [InlineData("")]
    [InlineData(null)]
    public async Task OneDeploy_Execute_FileToPublish_Missing(string invalidFileToPublish)
    {
        // Arrange
        var httpClientMock = new Mock<IHttpClient>();
 
        var deploymentStatusServiceMock = new Mock<IDeploymentStatusService<DeploymentResponse>>();
 
        // set messages to log
        var taskLoggerMock = new Mock<ITaskLogger>();
        taskLoggerMock.Setup(l => l.LogError(Resources.ONEDEPLOY_FileToPublish_NotFound));
 
        var oneDeployTask = new OneDeploy(taskLoggerMock.Object);
 
        // Act
        var result = await oneDeployTask.OneDeployAsync(
            invalidFileToPublish, Username, NotShareableValue, PublishUrl, $"{UserAgentName}/8.0", webjobName: null, webjobType: null, httpClientMock.Object, deploymentStatusServiceMock.Object, CancellationToken.None);
 
        // Assert: deployment operation fails because 'FileToPublishPath' is not valid
        Assert.False(result);
 
        httpClientMock.VerifyAll();
        deploymentStatusServiceMock.VerifyAll();
        taskLoggerMock.VerifyAll();
    }
 
    [Theory]
    [InlineData(Username, "")]
    [InlineData(Username, null)]
    [InlineData("", "")]
    [InlineData(null, null)]
    public async Task OneDeploy_Execute_Credentials_Missing_Args(string invalidUsername, string invalidPassword)
    {
        // Arrange
        var httpClientMock = new Mock<IHttpClient>();
 
        var deploymentStatusServiceMock = new Mock<IDeploymentStatusService<DeploymentResponse>>();
 
        // set messages to log
        var taskLoggerMock = new Mock<ITaskLogger>();
        taskLoggerMock.Setup(l => l.LogError(Resources.ONEDEPLOY_FailedToRetrieveCredentials));
 
        var oneDeployTask = new OneDeploy(taskLoggerMock.Object);
 
        // Act
        var result = await oneDeployTask.OneDeployAsync(
            FileToPublish, invalidUsername, invalidPassword, PublishUrl, $"{UserAgentName}/8.0", webjobName: null, webjobType: null, httpClientMock.Object, deploymentStatusServiceMock.Object, CancellationToken.None);
 
        // Assert: deployment operation fails because 'Username' and/or 'Password' is
        // not valid nor the could be retrieved from Task HostObject
        Assert.False(result);
 
        httpClientMock.VerifyAll();
        deploymentStatusServiceMock.VerifyAll();
        taskLoggerMock.VerifyAll();
    }
 
    [Fact]
    public async Task OneDeploy_Execute_Credentials_From_TaskHostObject()
    {
        // Arrange
        var httpClientMock = GetHttpClientMock(HttpStatusCode.OK);
 
        var deploymentStatusServiceMock = GetDeploymentStatusServiceMock(httpClientMock.Object, DeploymentStatus.PartialSuccess);
 
        // set messages to log according to result
        var taskLoggerMock = new Mock<ITaskLogger>();
        taskLoggerMock.Setup(l => l.LogMessage(Build.Framework.MessageImportance.High, string.Format(Resources.ONEDEPLOY_PublishingOneDeploy, FileToPublish, OneDeployUri.AbsoluteUri.ToString())));
        taskLoggerMock.Setup(l => l.LogMessage(string.Format(Resources.ONEDEPLOY_Uploaded, FileToPublish)));
        taskLoggerMock.Setup(l => l.LogMessage(Build.Framework.MessageImportance.High, Resources.ONEDEPLOY_Success));
 
        var oneDeployTask = new OneDeploy(taskLoggerMock.Object);
 
        var msbuildHostObject = new VSMsDeployTaskHostObject();
        msbuildHostObject.AddCredentialTaskItemIfExists(Username, NotShareableValue);
        oneDeployTask.HostObject = msbuildHostObject;
 
        // Act
        var result = await oneDeployTask.OneDeployAsync(
            FileToPublish, Username, NotShareableValue, PublishUrl, $"{UserAgentName}/8.0", webjobName: null, webjobType: null, httpClientMock.Object, deploymentStatusServiceMock.Object, CancellationToken.None);
 
        // Assert: deployment operation runs to completion
        // obtaining the credentials from the Task HostObject
        Assert.True(result);
 
        httpClientMock.VerifyAll();
        deploymentStatusServiceMock.VerifyAll();
        taskLoggerMock.VerifyAll();
    }
 
    private Mock<IHttpClient> GetHttpClientMock(
        HttpStatusCode statusCode,
        string deployLocationHeader = DeploymentUrl)
    {
        var httpClientMock = new Mock<IHttpClient>();
 
        // Request
        HttpRequestMessage requestMessage = new();
        httpClientMock
            .Setup(hc => hc.DefaultRequestHeaders)
            .Returns(requestMessage.Headers);
 
        // Response
        HttpResponseMessage responseMessage = new(statusCode);
        if (!string.IsNullOrEmpty(deployLocationHeader))
        {
            responseMessage.Headers.Add("Location", deployLocationHeader);
        }
 
        // PostAsync()
        httpClientMock
            .Setup(hc => hc.PostAsync(OneDeployUri, It.IsAny<StreamContent>()))
            .ReturnsAsync(responseMessage);
 
        return httpClientMock;
    }
 
    [Fact]
    public void Constructor_OK()
    {
        var oneDeploy = new OneDeploy();
 
        // no-arg ctor (as invoked by MSBuild) instantiate instance
        Assert.NotNull(oneDeploy);
    }
 
    private Mock<IDeploymentStatusService<DeploymentResponse>> GetDeploymentStatusServiceMock(
        IHttpClient httpClient,
        DeploymentStatus status = DeploymentStatus.Success,
        string statusText = null)
    {
        var deploymentResponse = new DeploymentResponse()
        {
            Status = status,
            StatusText = statusText,
            LogUrl = DeploymentLogUrl
        };
 
        var statusServiceMock = new Mock<IDeploymentStatusService<DeploymentResponse>>();
 
        statusServiceMock
            .Setup(s => s.PollDeploymentAsync(httpClient, DeploymentUrl, Username, NotShareableValue, $"{UserAgentName}/8.0", It.IsAny<CancellationToken>()))
            .ReturnsAsync(deploymentResponse);
 
        return statusServiceMock;
    }
}