File: BuildManifest\ManifestModelTests.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.BuildManifest.Model;
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.Linq;
using Xunit;
using Xunit.Abstractions;
using FluentAssertions;
 
namespace Microsoft.DotNet.VersionTools.Tests.BuildManifest
{
    public class ManifestModelTests
    {
        private readonly ITestOutputHelper _output;
 
        public ManifestModelTests(ITestOutputHelper output)
        {
            _output = output;
        }
 
        /// <summary>
        /// Given a set of file sign extension infos,
        /// ToXml should throw with an appropriate error message if they are invalid 
        /// </summary>
        [Theory]
        [InlineData(null, ".ps1", "foocert")]
        [InlineData(null, ".ps1", "foocert", ".PS1", "foocert")]
        [InlineData(null, ".bar", "foocert", ".PS1", "foocert")]
        [InlineData(null, ".ps1", "FOOCERT", ".PS1", "foocert")]
        [InlineData(typeof(ArgumentException), ".ps1", "FOOCERT", ".ps1", "barcert")] // Conflict
        [InlineData(typeof(ArgumentException), ".ps1", "FOOCERT", ".PS1", "barcert")] // Conflict
        [InlineData(typeof(ArgumentException), "foo.ps1", "FOOCERT", ".PS1", "barcert")] // Conflict
        [InlineData(typeof(ArgumentException), ".ps1", "")] // Can't be empty
        [InlineData(typeof(ArgumentException), "", "bar")] // Can't be empty
        public void ManifestModelToXmlValidatesFileExtensionSignInfos(Type exceptionType, params string[] infos)
        {
            if (infos.Length % 2 != 0)
            {
                throw new ArgumentException();
            }
 
            List<FileExtensionSignInfoModel> models = new List<FileExtensionSignInfoModel>();
 
            // Include is first arg, cert name is second
            // InlineData can't pass tuple types so using this instead.
            for (int i = 0; i < infos.Length / 2; i++)
            {
                models.Add(new FileExtensionSignInfoModel() { Include = infos[i * 2], CertificateName = infos[i * 2 + 1] });
            }
 
            SigningInformationModel signInfo = new SigningInformationModel()
            {
                FileExtensionSignInfo = models
            };
 
            VerifyToXml(exceptionType, signInfo);
        }
 
        private static void VerifyToXml(Type expectedExceptionType, SigningInformationModel signInfo)
        {
            if (expectedExceptionType != null)
            {
                Action act = () => signInfo.ToXml();
                act.Should().Throw<Exception>().And.Should().BeOfType(expectedExceptionType);
            }
            else
            {
                signInfo.ToXml().Should().NotBeNull();
            }
        }
 
        /// <summary>
        /// Given a set of file sign extension infos,
        /// Parse should throw with an appropriate error message if they are invalid.
        /// </summary>
        [Theory]
        [InlineData(null, ".ps1", "foocert")]
        [InlineData(null, ".ps1", "foocert", ".PS1", "foocert")]
        [InlineData(null, ".bar", "foocert", ".PS1", "foocert")]
        [InlineData(null, ".ps1", "FOOCERT", ".PS1", "foocert")]
        [InlineData(typeof(ArgumentException), ".ps1", "FOOCERT", ".ps1", "barcert")] // Conflict
        [InlineData(typeof(ArgumentException), ".ps1", "FOOCERT", ".PS1", "barcert")] // Conflict
        [InlineData(typeof(ArgumentException), "foo.ps1", "FOOCERT", ".PS1", "barcert")] // Conflict
        [InlineData(typeof(ArgumentException), ".ps1", "")] // Can't be empty
        [InlineData(typeof(ArgumentException), "", "bar")] // Can't be empty
        public void ManifestModelFromXmlValidatesFileExtensionSignInfos(Type exceptionType, params string[] infos)
        {
            if (infos.Length % 2 != 0)
            {
                throw new ArgumentException();
            }
 
            StringBuilder builder = new StringBuilder();
            builder.AppendLine("<SigningInformation>");
 
            // Include is first arg, cert name is second
            // InlineData can't pass tuple types so using this instead.
            for (int i = 0; i < infos.Length / 2; i++)
            {
                builder.AppendLine($"<FileExtensionSignInfo Include=\"{infos[i * 2]}\" CertificateName=\"{infos[i * 2 + 1]}\" />");
            }
 
            builder.AppendLine("</SigningInformation>");
 
            VerifyFromXml(exceptionType, builder);
        }
 
        private static void VerifyFromXml(Type expectedExceptionType, StringBuilder builder)
        {
            if (expectedExceptionType != null)
            {
                Action act = () => SigningInformationModel.Parse(XElement.Parse(builder.ToString()));
                act.Should().Throw<Exception>().And.Should().BeOfType(expectedExceptionType);
            }
            else
            {
                SigningInformationModel.Parse(XElement.Parse(builder.ToString())).Should().NotBeNull();
            }
        }
 
        /// <summary>
        /// Given a set of explicit file sign infos that conflict,
        /// ToXml should throw with an appropriate error message.
        /// 
        /// param order is Include CertificateName TargetFramework PublicKeyToken
        /// </summary>
        [Theory]
        [InlineData(null, "bar.bat", "foocert", null, null)]
        [InlineData(typeof(ArgumentException), "foo/bar.bat", "foocert", null, null)] // Invalid file name
        [InlineData(typeof(ArgumentException), "foo\\bar.bat", "foocert", null, null)] // Invalid file name
        [InlineData(typeof(ArgumentException), "bar.bat", "", null, null)] // Empty cert
        [InlineData(typeof(ArgumentException), "bar.bat", null, null, null)] // Null cert
        [InlineData(typeof(ArgumentException), "bar.bat", "foocert", "net5", null)] // Invalid tfm
        [InlineData(typeof(ArgumentException), "bar.bat", "foocert", "net5.0", "zzz")] // Invalid pkt
        [InlineData(null, "bar.bat", "foocert", null, null, "bar.bat2", "foocert", null, null)]
        [InlineData(typeof(ArgumentException), "bar.bat", "foocert2", null, null, "bar.bat", "foocert", null, null)]
        [InlineData(null, "bar.bat", "foocert2", ".NETCoreApp,Version=v5.0", null, "bar.bat", "foocert", ".NETCoreApp,Version=v3.1", null)]
        [InlineData(null, "bar.bat", "foocert2", ".NETCoreApp,Version=v1.0", "aaaaaaaaaaaaaaaa", "bar.bat", "foocert", ".NETCoreApp,Version=v1.0", "aaaaaaaaaaaaaaab")]
        [InlineData(typeof(ArgumentException), "bar.bat", "foocert2", ".NETCoreApp,Version=v1.0", "aaaaaaaaaaaaaaaa", "bar.bat", "foocert", ".NETCoreApp,Version=v1.0", "aaaaaaaaaaaaaaaa")]
        public void ManifestModelToXmlValidatesFileSignInfos(Type exceptionType, params string[] infos)
        {
            if (infos.Length % 4 != 0)
            {
                throw new ArgumentException();
            }
 
            List<FileSignInfoModel> models = new List<FileSignInfoModel>();
 
            for (int i = 0; i < infos.Length / 4; i++)
            {
                models.Add(new FileSignInfoModel()
                    {
                        Include = infos[i * 4],
                        CertificateName = infos[i * 4 + 1],
                        TargetFramework = infos[i * 4 + 2],
                        PublicKeyToken = infos[i * 4 + 3],
                });
            }
 
            SigningInformationModel signInfo = new SigningInformationModel()
            {
                FileSignInfo = models
            };
 
            VerifyToXml(exceptionType, signInfo);
        }
 
        /// <summary>
        /// Per issue: dotnet/arcade#7064
        /// </summary>
        [Fact]
        public void ValidateSymreaderFileSignInfos()
        {
            List<FileSignInfoModel> models = new List<FileSignInfoModel>();            
            models.Add(new FileSignInfoModel()
            {
                Include = "Microsoft.DiaSymReader.dll",
                CertificateName = "MicrosoftWin8WinBlue",
                TargetFramework = ".NETFramework,Version=v2.0",
                PublicKeyToken = "31bf3856ad364e35",
            });
            models.Add(new FileSignInfoModel()
            {
                Include = "Microsoft.DiaSymReader.dll",
                CertificateName = "Microsoft101240624", // lgtm [cs/common-default-passwords] Safe, these are certificate names
                TargetFramework = ".NETStandard,Version=v1.1",
                PublicKeyToken = "31bf3856ad364e35",
            });
 
            SigningInformationModel signInfo = new SigningInformationModel()
            {
                FileSignInfo = models
            };
 
            signInfo.ToXml().Should().NotBeNull();
        }
 
        /// <summary>
        /// Given a set of explicit file sign infos that or are invalid,
        /// Parse should throw with an appropriate error message if they are invalid.
        /// 
        /// param order is Include CertificateName TargetFramework PublicKeyToken
        [Theory]
        [InlineData(null, "bar.bat", "foocert", null, null)]
        [InlineData(typeof(ArgumentException), "foo/bar.bat", "foocert", null, null)] // Invalid file name
        [InlineData(typeof(ArgumentException), "foo\\bar.bat", "foocert", null, null)] // Invalid file name
        [InlineData(typeof(ArgumentException), "bar.bat", "", null, null)] // Empty cert
        [InlineData(typeof(ArgumentException), "bar.bat", null, null, null)] // Null cert
        [InlineData(typeof(ArgumentException), "bar.bat", "foocert", "net5", null)] // Invalid tfm
        [InlineData(typeof(ArgumentException), "bar.bat", "foocert", "net5.0", "zzz")] // Invalid pkt
        [InlineData(null, "bar.bat", "foocert", null, null, "bar.bat2", "foocert", null, null)]
        [InlineData(typeof(ArgumentException), "bar.bat", "foocert2", null, null, "bar.bat", "foocert", null, null)]
        [InlineData(null, "bar.bat", "foocert2", ".NETCoreApp,Version=v5.0", null, "bar.bat", "foocert", ".NETCoreApp,Version=v3.1", null)]
        [InlineData(null, "bar.bat", "foocert2", ".NETCoreApp,Version=v1.0", "aaaaaaaaaaaaaaaa", "bar.bat", "foocert", ".NETCoreApp,Version=v1.0", "aaaaaaaaaaaaaaab")]
        [InlineData(typeof(ArgumentException), "bar.bat", "foocert2", ".NETCoreApp,Version=v1.0", "aaaaaaaaaaaaaaaa", "bar.bat", "foocert", ".NETCoreApp,Version=v1.0", "aaaaaaaaaaaaaaaa")]
        public void ManifestModelFromXmlValidatesFileSignInfos(Type exceptionType, params string[] infos)
        {
            if (infos.Length % 4 != 0)
            {
                throw new ArgumentException();
            }
 
            StringBuilder builder = new StringBuilder();
            builder.AppendLine("<SigningInformation>");
 
            List<FileSignInfoModel> models = new List<FileSignInfoModel>();
 
            for (int i = 0; i < infos.Length / 4; i++)
            {
                string targetFramework = infos[i * 4 + 2] != null ? $"TargetFramework=\"{infos[i * 4 + 2]}\"" : "";
                string publicKeyToken = infos[i * 4 + 3] != null ? $"PublicKeyToken=\"{infos[i * 4 + 3]}\"" : "";
                builder.AppendLine($"<FileSignInfo Include=\"{infos[i * 4]}\" CertificateName=\"{infos[i * 4 + 1]}\" {targetFramework} {publicKeyToken} />");
            }
 
            builder.AppendLine("</SigningInformation>");
 
            VerifyFromXml(exceptionType, builder);
        }
 
        /// <summary>
        /// Given a set of certificate sign infos that conflict or are invalid,
        /// ToXml should throw with an appropriate error message.
        /// 
        /// param order is CertificateName DualSigningAllowed
        /// </summary>
        [Theory]
        [InlineData(null, "foocert", "true")]
        [InlineData(null, "foocert", "false")]
        [InlineData(typeof(ArgumentException), "foocert", "true", "foocert", "false")]
        [InlineData(null, "foocert", "false", "foocert2", "false")]
        public void ManifestModelToXmlValidatesCertificateSignInfo(Type exceptionType, params string[] infos)
        {
            if (infos.Length % 2 != 0)
            {
                throw new ArgumentException();
            }
 
            List<CertificatesSignInfoModel> models = new List<CertificatesSignInfoModel>();
 
            for (int i = 0; i < infos.Length / 2; i++)
            {
                models.Add(new CertificatesSignInfoModel()
                {
                    Include = infos[i * 2],
                    DualSigningAllowed = bool.Parse(infos[i * 2 + 1])
                });
            }
 
            SigningInformationModel signInfo = new SigningInformationModel()
            {
                CertificatesSignInfo = models
            };
 
            VerifyToXml(exceptionType, signInfo);
        }
 
        /// <summary>
        /// Given a set of certificate sign infos that conflict or are invalid,
        /// Parse should throw with an appropriate error message if they are invalid.
        /// 
        /// param order is Include DualSigningAllowed
        [Theory]
        [InlineData(null, "foocert", "true")]
        [InlineData(null, "foocert", "false")]
        [InlineData(typeof(ArgumentException), "", "true")] // No cert
        [InlineData(typeof(ArgumentException), "", "false")] // No cert
        [InlineData(typeof(FormatException), "foocert", "FORKS")] // Invalid bool
        [InlineData(typeof(FormatException), "foocert", "")] // No dual signing allowed param
        [InlineData(typeof(ArgumentException), "foocert", "true", "foocert", "false")]
        [InlineData(null, "foocert", "false", "foocert2", "false")]
        public void ManifestModelFromXmlValidatesCertificateSignInfo(Type exceptionType, params string[] infos)
        {
            if (infos.Length % 2 != 0)
            {
                throw new ArgumentException();
            }
 
            StringBuilder builder = new StringBuilder();
            builder.AppendLine("<SigningInformation>");
 
            List<FileSignInfoModel> models = new List<FileSignInfoModel>();
 
            for (int i = 0; i < infos.Length / 2; i++)
            {
                builder.AppendLine($"<CertificatesSignInfo Include=\"{infos[i * 2]}\" DualSigningAllowed=\"{infos[i * 2 + 1]}\" />");
            }
 
            builder.AppendLine("</SigningInformation>");
 
            VerifyFromXml(exceptionType, builder);
        }
 
        /// <summary>
        /// Given a set of strong name sign that conflict or are invalid,
        /// ToXml should throw with an appropriate error message.
        /// 
        /// param order is Include (strong name) PublicKeyToken CertificateName
        /// </summary>
        [Theory]
        [InlineData(null, "MyStrongName", "aaaaaaaaaaaaaaaa", "Mycert")] // Valid
        [InlineData(typeof(ArgumentException), "MyStrongName", "", "Mycert")] // Invalid strong name key
        [InlineData(typeof(ArgumentException), "MyStrongName", "aaaaaaaaaaaaaaaa", "")] // Invalid cert
        [InlineData(typeof(ArgumentException), "MyStrongName", "aaaaaaaaaaaa", "Mycert")] // Invalid strong name key
        [InlineData(null, "MyStrongName", "aaaaaaaaaaaaaaaa", "Mycert", "MyStrongName", "aaaaaaaaaaaaaaaa", "Mycert")] // No conflicts
        [InlineData(typeof(ArgumentException), "MyStrongName2", "aaaaaaaaaaaaaaaa", "Mycert", "MyStrongName", "aaaaaaaaaaaaaaaa", "Mycert")] // Different strong names
        [InlineData(typeof(ArgumentException), "MyStrongName", "aaaaaaaaaaaaaaaa", "Mycert", "MyStrongName", "aaaaaaaaaaaaaaaa", "Mycert2")] // Different certs
        [InlineData(null, "MyStrongName", "aaaaaaaaaaaaaaab", "Mycert", "MyStrongName", "aaaaaaaaaaaaaaaa", "Mycert2")] // No conflict
        public void ManifestModelToXmlValidatesStrongNameSignInfo(Type exceptionType, params string[] infos)
        {
            if (infos.Length % 3 != 0)
            {
                throw new ArgumentException();
            }
 
            List<StrongNameSignInfoModel> models = new List<StrongNameSignInfoModel>();
 
            for (int i = 0; i < infos.Length / 3; i++)
            {
                models.Add(new StrongNameSignInfoModel()
                {
                    Include = infos[i * 3],
                    PublicKeyToken = infos[i * 3 + 1],
                    CertificateName = infos[i * 3 + 2]
                });
            }
 
            SigningInformationModel signInfo = new SigningInformationModel()
            {
                StrongNameSignInfo = models
            };
 
            VerifyToXml(exceptionType, signInfo);
        }
 
        /// <summary>
        /// Given a set of strong name sign that conflict or are invalid,
        /// Parse should throw with an appropriate error message if they are invalid.
        /// 
        /// param order is Include (strong name) PublicKeyToken CertificateName
        [Theory]
        [InlineData(null, "MyStrongName", "aaaaaaaaaaaaaaaa", "Mycert")] // Valid
        [InlineData(typeof(ArgumentException), "MyStrongName", "", "Mycert")] // Invalid strong name key
        [InlineData(typeof(ArgumentException), "MyStrongName", "aaaaaaaaaaaaaaaa", "")] // Invalid cert
        [InlineData(typeof(ArgumentException), "MyStrongName", "aaaaaaaaaaaa", "Mycert")] // Invalid strong name key
        [InlineData(null, "MyStrongName", "aaaaaaaaaaaaaaaa", "Mycert", "MyStrongName", "aaaaaaaaaaaaaaaa", "Mycert")] // No conflicts
        [InlineData(typeof(ArgumentException), "MyStrongName2", "aaaaaaaaaaaaaaaa", "Mycert", "MyStrongName", "aaaaaaaaaaaaaaaa", "Mycert")] // Different strong names
        [InlineData(typeof(ArgumentException), "MyStrongName", "aaaaaaaaaaaaaaaa", "Mycert", "MyStrongName", "aaaaaaaaaaaaaaaa", "Mycert2")] // Different certs
        [InlineData(null, "MyStrongName", "aaaaaaaaaaaaaaab", "Mycert", "MyStrongName", "aaaaaaaaaaaaaaaa", "Mycert2")] // No conflict
        public void ManifestModelFromXmlValidatesStrongNameSignInfo(Type exceptionType, params string[] infos)
        {
            if (infos.Length % 3 != 0)
            {
                throw new ArgumentException();
            }
 
            StringBuilder builder = new StringBuilder();
            builder.AppendLine("<SigningInformation>");
 
            List<FileSignInfoModel> models = new List<FileSignInfoModel>();
 
            for (int i = 0; i < infos.Length / 3; i++)
            {
                builder.AppendLine($"<StrongNameSignInfo Include=\"{infos[i * 3]}\" PublicKeyToken=\"{infos[i * 3 + 1]}\" CertificateName=\"{infos[i * 3 + 2]}\" />");
            }
 
            builder.AppendLine("</SigningInformation>");
 
            VerifyFromXml(exceptionType, builder);
        }
 
        [Fact]
        public void TestExampleBuildManifestRoundtrip()
        {
            XElement xml = XElement.Parse(ExampleBuildString);
            var model = BuildModel.Parse(xml);
            XElement modelXml = model.ToXml();
 
            XNode.DeepEquals(xml, modelXml).Should().BeTrue("Model failed to output the parsed XML.");
        }
 
        [Fact]
        public void TestExampleOrchestratedBuildManifestRoundtrip()
        {
            XElement xml = XElement.Parse(ExampleOrchestratedBuildString);
            var model = OrchestratedBuildModel.Parse(xml);
            XElement modelXml = model.ToXml();
 
            XNode.DeepEquals(xml, modelXml).Should().BeTrue("Model failed to output the parsed XML.");
        }
 
        [Fact]
        public void TestExampleCustomBuildIdentityRoundtrip()
        {
            XElement xml = XElement.Parse(
                @"<Build Name=""Example"" BuildId=""123"" ProductVersion=""1.0.0-preview"" Branch=""master"" Commit=""abcdef"" BlankExtra="""" Extra=""extra-foo"" />");
            var model = BuildModel.Parse(xml);
            XElement modelXml = model.ToXml();
 
            XNode.DeepEquals(xml, modelXml).Should().BeTrue("Model failed to output the parsed XML.");
        }
 
        [Fact]
        public void TestPackageOnlyBuildManifest()
        {
            var model = CreatePackageOnlyBuildManifestModel();
            XElement modelXml = model.ToXml();
            XElement xml = XElement.Parse(@"<Build Name=""SimpleBuildManifest"" BuildId=""123""><Package Id=""Foo"" Version=""1.2.3-example"" /></Build>");
 
            XNode.DeepEquals(xml, modelXml).Should().BeTrue("Model failed to output the parsed XML.");
        }
 
        [Fact]
        public void TestMergeBuildManifests()
        {
            var orchestratedModel = new OrchestratedBuildModel(new BuildIdentity { Name = "Orchestrated", BuildId = "123" })
            {
                Endpoints = new List<EndpointModel>
                {
                    EndpointModel.CreateOrchestratedBlobFeed("http://example.org")
                }
            };
 
            orchestratedModel.AddParticipantBuild(CreatePackageOnlyBuildManifestModel());
            orchestratedModel.AddParticipantBuild(BuildModel.Parse(XElement.Parse(ExampleBuildString)));
 
            XElement modelXml = orchestratedModel.ToXml();
            XElement xml = XElement.Parse(@"
<OrchestratedBuild Name=""Orchestrated"" BuildId=""123"">
  <Endpoint Id=""Orchestrated"" Type=""BlobFeed"" Url=""http://example.org"">
    <Package Id=""Foo"" Version=""1.2.3-example"" />
    <Package Id=""runtime.rhel.6-x64.Microsoft.Private.CoreFx.NETCoreApp"" Version=""4.5.0-preview1-25929-04"" Category=""noship"" />
    <Package Id=""System.Memory"" Version=""4.5.0-preview1-25927-01"" />
    <Blob Id=""symbols/inner/blank-dir-nonshipping"" NonShipping=""false"" />
    <Blob Id=""symbols/runtime.rhel.6-x64.Microsoft.Private.CoreFx.NETCoreApp.4.5.0-preview1-25929-04.symbols.nupkg"" />
    <Blob Id=""symbols/System.ValueTuple.4.5.0-preview1-25929-04.symbols.nupkg"" NonShipping=""true"" />
  </Endpoint>
  <Build Name=""SimpleBuildManifest"" BuildId=""123"" />
  <Build Name=""corefx"" BuildId=""20171129-04"" Branch=""master"" Commit=""defb6d52047cc3d6b5f5d0853b0afdb1512dfbf4"" />
</OrchestratedBuild>");
 
            XNode.DeepEquals(xml, modelXml).Should().BeTrue("Model failed to output the parsed XML.");
        }
 
        [Fact]
        public void TestManifestWithSigningInformation()
        {
            var buildModel = CreateSigningInformationBuildManifestModel();
 
            XElement modelXml = buildModel.ToXml();
            XElement xml = XElement.Parse(ExampleBuildStringWithSigningInformation);
 
            XNode.DeepEquals(xml, modelXml).Should().BeTrue("Model failed to output the parsed XML.");
        }
 
        [Fact]
        public void PackageArtifactModelEquals_ReturnsTrueWhenTwoObjectsHaveMatchingAttributes()
        {
            PackageArtifactModel packageArtifact = new PackageArtifactModel
            {
                Attributes = new Dictionary<string, string>
                    {
                        { "NonShipping", true.ToString().ToLower() },
                    },
                Id = "AssetName",
                Version = "AssetVersion"
            };
 
            PackageArtifactModel otherPackageArtifact = new PackageArtifactModel
            {
                Attributes = new Dictionary<string, string>
                    {
                        { "NonShipping", true.ToString().ToLower() },
                    },
                Id = "AssetName",
                Version = "AssetVersion"
            };
 
            Assert.True(packageArtifact.Equals(otherPackageArtifact));
        }
 
        [Fact]
        public void PackageArtifactModelEquals_ReturnsFalseWhenTwoObjectsDoNotHaveMatchingAttributes()
        {
            PackageArtifactModel packageArtifact = new PackageArtifactModel
            {
                Attributes = new Dictionary<string, string>
                    {
                        { "Shipping", true.ToString().ToLower() },
                    },
                Id = "AssetName",
                Version = "AssetVersion"
            };
 
            PackageArtifactModel otherPackageArtifact = new PackageArtifactModel
            {
                Attributes = new Dictionary<string, string>
                    {
                        { "NonShipping", true.ToString().ToLower() },
                    },
                Id = "AssetName",
                Version = "AssetVersion"
            };
 
            Assert.False(packageArtifact.Equals(otherPackageArtifact));
        }
 
        [Fact]
        public void PackageArtifactModelEquals_ReturnsTrueWhenMatchingAttributesAreNull()
        {
            PackageArtifactModel packageArtifact = new PackageArtifactModel
            {
                Attributes = new Dictionary<string, string>
                    {
                        { "NonShipping", true.ToString().ToLower() },
                    },
                Id = "AssetName",
                Version = null
            };
 
            PackageArtifactModel otherPackageArtifact = new PackageArtifactModel
            {
                Attributes = new Dictionary<string, string>
                    {
                        { "NonShipping", true.ToString().ToLower() },
                    },
                Id = "AssetName",
                Version = null
            };
 
            Assert.True(packageArtifact.Equals(otherPackageArtifact));
        }
 
        [Fact]
        public void PackageArtifactModelEquals_ReturnsFalseWhenObjectsAreDifferentTypes()
        {
            PackageArtifactModel packageArtifact = new PackageArtifactModel
            {
                Attributes = new Dictionary<string, string>
                    {
                        { "NonShipping", true.ToString().ToLower() },
                    },
                Id = "AssetName",
                Version = null
            };
 
            Assert.False(packageArtifact.Equals("thisIsNotAPackageArtifact!"));
        }
 
        [Fact]
        public void BlobArtifactModelEquals_ReturnsTrueWhenTwoObjectsHaveMatchingAttributes()
        {
            BlobArtifactModel blobArtifact = new BlobArtifactModel
            {
                Attributes = new Dictionary<string, string>
                    {
                        { "NonShipping", true.ToString().ToLower() },
                    },
                Id = "AssetName"
            };
 
            BlobArtifactModel otherBlobArtifact = new BlobArtifactModel
            {
                Attributes = new Dictionary<string, string>
                    {
                        { "NonShipping", true.ToString().ToLower() },
                    },
                Id = "AssetName"
            };
 
            Assert.True(blobArtifact.Equals(otherBlobArtifact));
        }
 
        [Fact]
        public void BlobArtifactModelEquals_ReturnsFalseWhenTwoObjectsDoNotHaveMatchingAttributes()
        {
            BlobArtifactModel blobArtifact = new BlobArtifactModel
            {
                Attributes = new Dictionary<string, string>
                    {
                        { "Shipping", true.ToString().ToLower() },
                    },
                Id = "AssetName"
            };
 
            BlobArtifactModel otherBlobArtifact = new BlobArtifactModel
            {
                Attributes = new Dictionary<string, string>
                    {
                        { "NonShipping", true.ToString().ToLower() },
                    },
                Id = "AssetName"
            };
 
            Assert.False(blobArtifact.Equals(otherBlobArtifact));
        }
 
        [Fact]
        public void BlobArtifactModelEquals_ReturnsTrueWhenMatchingAttributesAreNull()
        {
            BlobArtifactModel blobArtifact = new BlobArtifactModel
            {
                Attributes = new Dictionary<string, string>
                    {
                        { "NonShipping", true.ToString().ToLower() },
                    },
                Id = null
            };
 
            BlobArtifactModel otherBlobArtifact = new BlobArtifactModel
            {
                Attributes = new Dictionary<string, string>
                    {
                        { "NonShipping", true.ToString().ToLower() },
                    },
                Id = null
            };
 
            Assert.True(blobArtifact.Equals(otherBlobArtifact));
        }
 
        [Fact]
        public void BlobArtifactModelEquals_ReturnsFalseWhenObjectsAreDifferentTypes()
        {
            BlobArtifactModel blobArtifact = new BlobArtifactModel
            {
                Attributes = new Dictionary<string, string>
                    {
                        { "NonShipping", true.ToString().ToLower() },
                    },
                Id = null
            };
 
            Assert.False(blobArtifact.Equals("thisIsNotABlobArtifact!"));
        }
 
        private BuildModel CreatePackageOnlyBuildManifestModel()
        {
            return new BuildModel(new BuildIdentity { Name = "SimpleBuildManifest", BuildId = "123" })
            {
                Artifacts = new ArtifactSet
                {
                    Packages = new List<PackageArtifactModel>
                    {
                        new PackageArtifactModel
                        {
                            Id = "Foo",
                            Version = "1.2.3-example"
                        }
                    }
                }
            };
        }
 
        private BuildModel CreateSigningInformationBuildManifestModel()
        {
            return new BuildModel(new BuildIdentity { Name = "SigningInformationBuildManifest", BuildId = "123", Branch = "refs/heads/Test", 
                Commit = "test_commit", IsStable = false, PublishingVersion = (PublishingInfraVersion)3 })
            {
                Artifacts = new ArtifactSet
                {
                    Packages = new List<PackageArtifactModel>
                    {
                        new PackageArtifactModel
                        {
                            Id = "TestPackage",
                            Version = "5.0.0",
                        },
                        new PackageArtifactModel
                        {
                            Id = "ArcadeSdkTest",
                            Version = "5.0.0",
                        },
                    },
                    Blobs = new List<BlobArtifactModel>
                    {
                        new BlobArtifactModel
                        {
                            Id = "assets/symbols/test.nupkg",
                        },
                    }
                },
                SigningInformation = new SigningInformationModel
                {
                    FileExtensionSignInfo = new List<FileExtensionSignInfoModel>
                    {
                        new FileExtensionSignInfoModel
                        {
                            Include = ".dll",
                            CertificateName = "Microsoft400", // lgtm [cs/common-default-passwords] Safe, these are certificate names
                        },
                        new FileExtensionSignInfoModel
                        {
                            Include = ".jar",
                            CertificateName = "MicrosoftJARSHA2",
                        },
                        new FileExtensionSignInfoModel
                        {
                            Include = ".nupkg",
                            CertificateName = "NuGet",
                        },
                    },
                    FileSignInfo = new List<FileSignInfoModel>
                    {
                        new FileSignInfoModel
                        {
                            Include = "Dll.dll",
                            CertificateName = "3PartySHA2",
                        },
                        new FileSignInfoModel
                        {
                            Include = "Another.dll",
                            CertificateName = "AnotherCert",
                        },
                    },
                    ItemsToSign = new List<ItemToSignModel>
                    {
                        new ItemToSignModel
                        {
                            Include = "Package1.nupkg",
                        },
                        new ItemToSignModel
                        {
                            Include = "Package2.nupkg",
                        },
                        new ItemToSignModel
                        {
                            Include = "Package3.nupkg",
                        },
                    },
                    StrongNameSignInfo = new List<StrongNameSignInfoModel>
                    {
                        new StrongNameSignInfoModel
                        {
                            Include = "StrongNameTime",
                            PublicKeyToken = "0123456789abcdef",
                            CertificateName = "Microsoft400", // lgtm [cs/common-default-passwords] Safe, these are certificate names
                        },
                        new StrongNameSignInfoModel
                        {
                            Include = "StrongButKindName",
                            PublicKeyToken = "fedcba9876543210",
                            CertificateName = "Microsoft404", // lgtm [cs/common-default-passwords] Safe, these are certificate names
                        },
                    },
                },
            };
        }
 
        private const string ExampleBuildString = @"
<Build
  Name=""corefx""
  BuildId=""20171129-04""
  Branch=""master""
  Commit=""defb6d52047cc3d6b5f5d0853b0afdb1512dfbf4"">
 
  <Package Id=""runtime.rhel.6-x64.Microsoft.Private.CoreFx.NETCoreApp"" Version=""4.5.0-preview1-25929-04"" Category=""noship"" />
  <Package Id=""System.Memory"" Version=""4.5.0-preview1-25927-01"" />
 
  <Blob Id=""symbols/inner/blank-dir-nonshipping"" NonShipping=""false"" />
  <Blob Id=""symbols/runtime.rhel.6-x64.Microsoft.Private.CoreFx.NETCoreApp.4.5.0-preview1-25929-04.symbols.nupkg"" />
  <Blob Id=""symbols/System.ValueTuple.4.5.0-preview1-25929-04.symbols.nupkg"" NonShipping=""true"" />
 
</Build>";
 
        private const string ExampleOrchestratedBuildString = @"
<OrchestratedBuild
  Name=""core-setup""
  BuildId=""20171129-02""
  Branch=""master"">
 
  <Endpoint
    Id=""Orchestrated""
    Type=""BlobFeed""
    Url=""https://dotnetfeed.blob.core.windows.net/orchestrated-aspnet/20171129-02/index.json"">
 
    <Package Id=""Microsoft.NETCore.App"" Version=""2.1.0-preview1-26001-02"" />
    <Package Id=""Microsoft.NETCore.UniversalWindowsPlatform"" Version=""6.1.0-preview1-25927-01"" NonShipping=""true"" />
    <Package Id=""runtime.rhel.6-x64.Microsoft.Private.CoreFx.NETCoreApp"" Version=""4.5.0-preview1-25929-04"" NonShipping=""true"" />
    <Package Id=""System.Memory"" Version=""4.5.0-preview1-25927-01"" />
 
    <Blob Id=""orchestration-metadata/manifests/core-setup.xml"" />
    <Blob Id=""orchestration-metadata/manifests/corefx.xml"" />
    <Blob Id=""orchestration-metadata/PackageVersions.props"" />
    <Blob Id=""Runtime/2.1.0-preview1-25929-04/dotnet-runtime-2.1.0-preview1-25929-04-win-x64.msi"" ShipInstaller=""dotnetcli"" />
    <Blob Id=""Runtime/2.1.0-preview1-25929-04/dotnet-runtime-2.1.0-preview1-25929-04-win-x64.msi.sha512"" ShipInstaller=""dotnetclichecksums"" />
    <Blob Id=""symbols/Microsoft.DotNet.PlatformAbstractions.2.1.0-preview1-25929-04.symbols.nupkg"" />
    <Blob Id=""symbols/runtime.rhel.6-x64.Microsoft.Private.CoreFx.NETCoreApp.4.5.0-preview1-25929-04.symbols.nupkg"" />
    <Blob Id=""symbols/System.ValueTuple.4.5.0-preview1-25929-04.symbols.nupkg"" />
 
  </Endpoint>
 
  <Build
    Name=""corefx""
    BuildId=""20171129-04""
    Branch=""master""
    Commit=""defb6d52047cc3d6b5f5d0853b0afdb1512dfbf4"" />
 
  <Build
    Name=""core-setup""
    BuildId=""20171129-04""
    Branch=""master""
    Commit=""152dbe8a4b4e30eee26208ff6a850e9aa73c07f8"" />
 
</OrchestratedBuild>
";
 
        private const string ExampleBuildStringWithSigningInformation = @"
<Build PublishingVersion=""3"" Name=""SigningInformationBuildManifest"" BuildId=""123"" Branch=""refs/heads/Test"" Commit=""test_commit"" IsStable=""false"">
  <Package Id=""ArcadeSdkTest"" Version=""5.0.0"" />
  <Package Id=""TestPackage"" Version=""5.0.0"" />
  <Blob Id=""assets/symbols/test.nupkg""/>
  <SigningInformation>
    <FileExtensionSignInfo Include="".dll"" CertificateName=""Microsoft400"" />
    <FileExtensionSignInfo Include="".jar"" CertificateName=""MicrosoftJARSHA2"" />
    <FileExtensionSignInfo Include="".nupkg"" CertificateName=""NuGet"" />
    <FileSignInfo Include=""Another.dll"" CertificateName=""AnotherCert"" />
    <FileSignInfo Include=""Dll.dll"" CertificateName=""3PartySHA2"" />
    <ItemsToSign Include=""Package1.nupkg"" />
    <ItemsToSign Include=""Package2.nupkg"" />
    <ItemsToSign Include=""Package3.nupkg"" />
    <StrongNameSignInfo Include=""StrongButKindName"" PublicKeyToken=""fedcba9876543210"" CertificateName=""Microsoft404"" />
    <StrongNameSignInfo Include=""StrongNameTime"" PublicKeyToken=""0123456789abcdef"" CertificateName=""Microsoft400"" />
  </SigningInformation>
</Build>
";
    }
}