File: StaticWebAssets\GenerateStaticWebAssetsPropsFileTest.cs
Web Access
Project: ..\..\..\test\Microsoft.NET.Sdk.StaticWebAssets.Tests\Microsoft.NET.Sdk.StaticWebAssets.Tests.csproj (Microsoft.NET.Sdk.StaticWebAssets.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 Microsoft.AspNetCore.StaticWebAssets.Tasks;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Moq;
using NuGet.Packaging.Core;
 
namespace Microsoft.NET.Sdk.Razor.Test
{
    public class GenerateStaticWebAssetsPropsFileTest
    {
        [Fact]
        public void Fails_WhenStaticWebAsset_DoesNotContainSourceType()
        {
            // Arrange
            var errorMessages = new List<string>();
            var buildEngine = new Mock<IBuildEngine>();
            buildEngine.Setup(e => e.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
                .Callback<BuildErrorEventArgs>(args => errorMessages.Add(args.Message));
 
            var task = new GenerateStaticWebAssetsPropsFile
            {
                BuildEngine = buildEngine.Object,
                StaticWebAssets = new TaskItem[]
                {
                    CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
                    {
                        ["SourceId"] = "MyLibrary",
                        ["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
                        ["BasePath"] = "_content/mylibrary",
                        ["RelativePath"] = Path.Combine("js", "sample.js"),
                    })
                }
            };
 
            // Act
            var result = task.Execute();
 
            // Assert
            result.Should().BeFalse();
            var expectedError = $"Missing required metadata 'SourceType' for '{Path.Combine("wwwroot", "js", "sample.js")}'.";
            errorMessages.Should().ContainSingle(message => message == expectedError);
        }
 
        [Fact]
        public void Fails_WhenStaticWebAsset_DoesNotContainSourceId()
        {
            // Arrange
            var errorMessages = new List<string>();
            var buildEngine = new Mock<IBuildEngine>();
            buildEngine.Setup(e => e.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
                .Callback<BuildErrorEventArgs>(args => errorMessages.Add(args.Message));
 
            var task = new GenerateStaticWebAssetsPropsFile
            {
                BuildEngine = buildEngine.Object,
                StaticWebAssets = new TaskItem[]
                {
                    CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
                    {
                        ["SourceType"] = "Discovered",
                        ["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
                        ["BasePath"] = "_content/mylibrary",
                        ["RelativePath"] = Path.Combine("js", "sample.js"),
                    })
                }
            };
 
            // Act
            var result = task.Execute();
 
            // Assert
            result.Should().BeFalse();
            var expectedError = $"Missing required metadata 'SourceId' for '{Path.Combine("wwwroot", "js", "sample.js")}'.";
            errorMessages.Should().ContainSingle(message => message == expectedError);
        }
 
        [Fact]
        public void Fails_WhenStaticWebAsset_DoesNotContainContentRoot()
        {
            // Arrange
            var errorMessages = new List<string>();
            var buildEngine = new Mock<IBuildEngine>();
            buildEngine.Setup(e => e.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
                .Callback<BuildErrorEventArgs>(args => errorMessages.Add(args.Message));
 
            var task = new GenerateStaticWebAssetsPropsFile
            {
                BuildEngine = buildEngine.Object,
                StaticWebAssets = new TaskItem[]
                {
                    CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
                    {
                        ["SourceType"] = "Discovered",
                        ["SourceId"] = "MyLibrary",
                        ["BasePath"] = "_content/mylibrary",
                        ["RelativePath"] = Path.Combine("js", "sample.js"),
                    })
                }
            };
 
            // Act
            var result = task.Execute();
 
            // Assert
            result.Should().BeFalse();
            var expectedError = $"Missing required metadata 'ContentRoot' for '{Path.Combine("wwwroot", "js", "sample.js")}'.";
            errorMessages.Should().ContainSingle(message => message == expectedError);
        }
 
        [Fact]
        public void Fails_WhenStaticWebAsset_DoesNotContainBasePath()
        {
            // Arrange
            var errorMessages = new List<string>();
            var buildEngine = new Mock<IBuildEngine>();
            buildEngine.Setup(e => e.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
                .Callback<BuildErrorEventArgs>(args => errorMessages.Add(args.Message));
 
            var task = new GenerateStaticWebAssetsPropsFile
            {
                BuildEngine = buildEngine.Object,
                StaticWebAssets = new TaskItem[]
                {
                    CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
                    {
                        ["SourceType"] = "Discovered",
                        ["SourceId"] = "MyLibrary",
                        ["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
                        ["RelativePath"] = Path.Combine("js", "sample.js"),
                    })
                }
            };
 
            // Act
            var result = task.Execute();
 
            // Assert
            result.Should().BeFalse();
            var expectedError = $"Missing required metadata 'BasePath' for '{Path.Combine("wwwroot", "js", "sample.js")}'.";
            errorMessages.Should().ContainSingle(message => message == expectedError);
        }
 
        [Fact]
        public void Fails_WhenStaticWebAsset_DoesNotContainRelativePath()
        {
            // Arrange
            var errorMessages = new List<string>();
            var buildEngine = new Mock<IBuildEngine>();
            buildEngine.Setup(e => e.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
                .Callback<BuildErrorEventArgs>(args => errorMessages.Add(args.Message));
 
            var task = new GenerateStaticWebAssetsPropsFile
            {
                BuildEngine = buildEngine.Object,
                StaticWebAssets = new TaskItem[]
                {
                    CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
                    {
                        ["SourceType"] = "Discovered",
                        ["SourceId"] = "MyLibrary",
                        ["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
                        ["BasePath"] = "_content/mylibrary",
                        ["AssetKind"] = "All",
                        ["AssetMode"] = "All",
                        ["AssetRole"] = "Primary",
                        ["RelatedAsset"] = "",
                        ["AssetTraitName"] = "",
                        ["AssetTraitValue"] = "",
                        ["CopyToOutputDirectory"] = "Never",
                        ["CopyToPublishDirectory"] = "PreserveNewest"
                    })
                }
            };
 
            // Act
            var result = task.Execute();
 
            // Assert
            result.Should().BeFalse();
            var expectedError = $"Missing required metadata 'RelativePath' for '{Path.Combine("wwwroot", "js", "sample.js")}'.";
            errorMessages.Should().ContainSingle(message => message == expectedError);
        }
 
        [Fact]
        public void Fails_WhenStaticWebAsset_HasInvalidSourceType()
        {
            // Arrange
 
            var expectedError = $"Static web asset '{Path.Combine("wwwroot", "css", "site.css")}' has invalid source type 'Package'.";
 
            var errorMessages = new List<string>();
            var buildEngine = new Mock<IBuildEngine>();
            buildEngine.Setup(e => e.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
                .Callback<BuildErrorEventArgs>(args => errorMessages.Add(args.Message));
 
            var task = new GenerateStaticWebAssetsPropsFile
            {
                BuildEngine = buildEngine.Object,
                StaticWebAssets = new TaskItem[]
                {
                    CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
                    {
                        ["SourceType"] = "Discovered",
                        ["SourceId"] = "MyLibrary",
                        ["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
                        ["BasePath"] = "_content/mylibrary",
                        ["RelativePath"] = Path.Combine("js", "sample.js"),
                        ["AssetKind"] = "All",
                        ["AssetMode"] = "All",
                        ["AssetRole"] = "Primary",
                        ["RelatedAsset"] = "",
                        ["AssetTraitName"] = "",
                        ["AssetTraitValue"] = "",
                        ["CopyToOutputDirectory"] = "Never",
                        ["CopyToPublishDirectory"] = "PreserveNewest"
                    }),
                    CreateItem(Path.Combine("wwwroot","css","site.css"), new Dictionary<string,string>
                    {
                        ["SourceType"] = "Package",
                        ["SourceId"] = "MyLibrary",
                        ["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
                        ["BasePath"] = "_content/mylibrary",
                        ["RelativePath"] = Path.Combine("css", "site.css"),
                        ["AssetKind"] = "All",
                        ["AssetMode"] = "All",
                        ["AssetRole"] = "Primary",
                        ["RelatedAsset"] = "",
                        ["AssetTraitName"] = "",
                        ["AssetTraitValue"] = "",
                        ["CopyToOutputDirectory"] = "Never",
                        ["CopyToPublishDirectory"] = "PreserveNewest"
                    })
                }
            };
 
            // Act
            var result = task.Execute();
 
            // Assert
            result.Should().BeFalse();
            errorMessages.Should().ContainSingle(message => message == expectedError);
        }
 
        [Fact]
        public void Fails_WhenStaticWebAsset_HaveDifferentSourceId()
        {
            // Arrange
            var expectedError = "Static web assets have different 'SourceId' metadata values " +
                "'MyLibrary' and 'MyLibrary2' " +
                $"for '{Path.Combine("wwwroot", "js", "sample.js")}' and '{Path.Combine("wwwroot", "css", "site.css")}'.";
 
            var errorMessages = new List<string>();
            var buildEngine = new Mock<IBuildEngine>();
            buildEngine.Setup(e => e.LogErrorEvent(It.IsAny<BuildErrorEventArgs>()))
                .Callback<BuildErrorEventArgs>(args => errorMessages.Add(args.Message));
 
            var task = new GenerateStaticWebAssetsPropsFile
            {
                BuildEngine = buildEngine.Object,
                StaticWebAssets = new TaskItem[]
                {
                    CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
                    {
                        ["SourceType"] = "Discovered",
                        ["SourceId"] = "MyLibrary",
                        ["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
                        ["BasePath"] = "_content/mylibrary",
                        ["RelativePath"] = Path.Combine("js", "sample.js"),
                        ["AssetKind"] = "All",
                        ["AssetMode"] = "All",
                        ["AssetRole"] = "Primary",
                        ["RelatedAsset"] = "",
                        ["AssetTraitName"] = "",
                        ["AssetTraitValue"] = "",
                        ["CopyToOutputDirectory"] = "Never",
                        ["CopyToPublishDirectory"] = "PreserveNewest"
                    }),
                    CreateItem(Path.Combine("wwwroot","css","site.css"), new Dictionary<string,string>
                    {
                        ["SourceType"] = "Discovered",
                        ["SourceId"] = "MyLibrary2",
                        ["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
                        ["BasePath"] = "_content/mylibrary",
                        ["RelativePath"] = Path.Combine("css", "site.css"),
                        ["AssetKind"] = "All",
                        ["AssetMode"] = "All",
                        ["AssetRole"] = "Primary",
                        ["RelatedAsset"] = "",
                        ["AssetTraitName"] = "",
                        ["AssetTraitValue"] = "",
                        ["CopyToOutputDirectory"] = "Never",
                        ["CopyToPublishDirectory"] = "PreserveNewest"
                    })
                }
            };
 
            // Act
            var result = task.Execute();
 
            // Assert
            result.Should().BeFalse();
            errorMessages.Should().ContainSingle(message => message == expectedError);
        }
 
        [Fact]
        public void WritesPropsFile_WhenThereIsAtLeastOneStaticAsset()
        {
            // Arrange
            var file = Path.GetTempFileName();
            var expectedDocument = @"<Project>
  <ItemGroup>
    <StaticWebAsset Include=""$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\staticwebassets\js\sample.js'))"">
      <SourceType>Package</SourceType>
      <SourceId>MyLibrary</SourceId>
      <ContentRoot>$(MSBuildThisFileDirectory)..\staticwebassets\</ContentRoot>
      <BasePath>_content/mylibrary</BasePath>
      <RelativePath>js/sample.js</RelativePath>
      <AssetKind>All</AssetKind>
      <AssetMode>All</AssetMode>
      <AssetRole>Primary</AssetRole>
      <RelatedAsset></RelatedAsset>
      <AssetTraitName></AssetTraitName>
      <AssetTraitValue></AssetTraitValue>
      <Fingerprint>sample-fingerprint</Fingerprint>
      <Integrity>sample-integrity</Integrity>
      <CopyToOutputDirectory>Never</CopyToOutputDirectory>
      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
      <FileLength>10</FileLength>
      <LastWriteTime>Thu, 15 Nov 1990 00:00:00 GMT</LastWriteTime>
      <OriginalItemSpec>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\staticwebassets\js\sample.js'))</OriginalItemSpec>
    </StaticWebAsset>
  </ItemGroup>
</Project>";
 
            try
            {
                var buildEngine = new Mock<IBuildEngine>();
 
                var task = new GenerateStaticWebAssetsPropsFile
                {
                    BuildEngine = buildEngine.Object,
                    TargetPropsFilePath = file,
                    StaticWebAssets = new TaskItem[]
                    {
                        CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
                        {
                            ["SourceType"] = "Discovered",
                            ["SourceId"] = "MyLibrary",
                            ["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
                            ["BasePath"] = "_content/mylibrary",
                            ["RelativePath"] = Path.Combine("js", "sample.js").Replace("\\","/"),
                            ["AssetKind"] = "All",
                            ["AssetMode"] = "All",
                            ["AssetRole"] = "Primary",
                            ["RelatedAsset"] = "",
                            ["AssetTraitName"] = "",
                            ["AssetTraitValue"] = "",
                            ["Fingerprint"] = "sample-fingerprint",
                            ["Integrity"] = "sample-integrity",
                            ["OriginalItemSpec"] = Path.Combine("wwwroot","js","sample.js"),
                            ["CopyToOutputDirectory"] = "Never",
                            ["CopyToPublishDirectory"] = "PreserveNewest",
                            ["FileLength"] = "10",
                            ["LastWriteTime"] = new DateTimeOffset(new DateTime(1990, 11, 15, 0, 0, 0, 0, DateTimeKind.Utc)).ToString(StaticWebAsset.DateTimeAssetFormat)
                        }),
                    }
                };
 
                // Act
                var result = task.Execute();
 
                // Assert
                result.Should().BeTrue();
                var document = File.ReadAllText(file);
                document.Should().Contain(expectedDocument);
            }
            finally
            {
                if (File.Exists(file))
                {
                    File.Delete(file);
                }
            }
        }
 
        [Fact]
        public void WritesIndividualItems_WithTheirRespectiveBaseAndRelativePaths()
        {
            // Arrange
            var file = Path.GetTempFileName();
            var expectedDocument = @"<Project>
  <ItemGroup>
    <StaticWebAsset Include=""$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\staticwebassets\App.styles.css'))"">
      <SourceType>Package</SourceType>
      <SourceId>MyLibrary</SourceId>
      <ContentRoot>$(MSBuildThisFileDirectory)..\staticwebassets\</ContentRoot>
      <BasePath>/</BasePath>
      <RelativePath>App.styles.css</RelativePath>
      <AssetKind>All</AssetKind>
      <AssetMode>All</AssetMode>
      <AssetRole>Primary</AssetRole>
      <RelatedAsset></RelatedAsset>
      <AssetTraitName></AssetTraitName>
      <AssetTraitValue></AssetTraitValue>
      <Fingerprint>styles-fingerprint</Fingerprint>
      <Integrity>styles-integrity</Integrity>
      <CopyToOutputDirectory>Never</CopyToOutputDirectory>
      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
      <FileLength>10</FileLength>
      <LastWriteTime>Thu, 15 Nov 1990 00:00:00 GMT</LastWriteTime>
      <OriginalItemSpec>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\staticwebassets\App.styles.css'))</OriginalItemSpec>
    </StaticWebAsset>
    <StaticWebAsset Include=""$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\staticwebassets\js\sample.js'))"">
      <SourceType>Package</SourceType>
      <SourceId>MyLibrary</SourceId>
      <ContentRoot>$(MSBuildThisFileDirectory)..\staticwebassets\</ContentRoot>
      <BasePath>_content/mylibrary</BasePath>
      <RelativePath>js/sample.js</RelativePath>
      <AssetKind>All</AssetKind>
      <AssetMode>All</AssetMode>
      <AssetRole>Primary</AssetRole>
      <RelatedAsset></RelatedAsset>
      <AssetTraitName></AssetTraitName>
      <AssetTraitValue></AssetTraitValue>
      <Fingerprint>sample-fingerprint</Fingerprint>
      <Integrity>sample-integrity</Integrity>
      <CopyToOutputDirectory>Never</CopyToOutputDirectory>
      <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
      <FileLength>10</FileLength>
      <LastWriteTime>Thu, 15 Nov 1990 00:00:00 GMT</LastWriteTime>
      <OriginalItemSpec>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\staticwebassets\js\sample.js'))</OriginalItemSpec>
    </StaticWebAsset>
  </ItemGroup>
</Project>";
 
            try
            {
                var buildEngine = new Mock<IBuildEngine>();
 
                var task = new GenerateStaticWebAssetsPropsFile
                {
                    BuildEngine = buildEngine.Object,
                    TargetPropsFilePath = file,
                    StaticWebAssets = new TaskItem[]
                    {
                        CreateItem(Path.Combine("wwwroot","js","sample.js"), new Dictionary<string,string>
                        {
                            ["SourceType"] = "Discovered",
                            ["SourceId"] = "MyLibrary",
                            ["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
                            ["BasePath"] = "_content/mylibrary",
                            ["RelativePath"] = Path.Combine("js", "sample.js").Replace("\\","/"),
                            ["AssetKind"] = "All",
                            ["AssetMode"] = "All",
                            ["AssetRole"] = "Primary",
                            ["RelatedAsset"] = "",
                            ["AssetTraitName"] = "",
                            ["AssetTraitValue"] = "",
                            ["OriginalItemSpec"] = Path.Combine("wwwroot","js","sample.js"),
                            ["Fingerprint"] = "sample-fingerprint",
                            ["Integrity"] = "sample-integrity",
                            ["CopyToOutputDirectory"] = "Never",
                            ["CopyToPublishDirectory"] = "PreserveNewest",
                            ["FileLength"] = "10",
                            ["LastWriteTime"] = new DateTimeOffset(new DateTime(1990, 11, 15, 0, 0, 0, 0, DateTimeKind.Utc)).ToString(StaticWebAsset.DateTimeAssetFormat)
                        }),
                        CreateItem(Path.Combine("wwwroot","App.styles.css"), new Dictionary<string,string>
                        {
                            ["SourceType"] = "Discovered",
                            ["SourceId"] = "MyLibrary",
                            ["ContentRoot"] = @"$(MSBuildThisFileDirectory)..\staticwebassets",
                            ["BasePath"] = "/",
                            ["RelativePath"] = "App.styles.css",
                            ["AssetKind"] = "All",
                            ["AssetMode"] = "All",
                            ["AssetRole"] = "Primary",
                            ["RelatedAsset"] = "",
                            ["AssetTraitName"] = "",
                            ["AssetTraitValue"] = "",
                            ["OriginalItemSpec"] = Path.Combine("wwwroot","App.styles.css"),
                            ["Fingerprint"] = "styles-fingerprint",
                            ["Integrity"] = "styles-integrity",
                            ["CopyToOutputDirectory"] = "Never",
                            ["CopyToPublishDirectory"] = "PreserveNewest",
                            ["FileLength"] = "10",
                            ["LastWriteTime"] = new DateTimeOffset(new DateTime(1990, 11, 15, 0, 0, 0, 0, DateTimeKind.Utc)).ToString(StaticWebAsset.DateTimeAssetFormat)
                       }),
                    }
                };
 
                // Act
                var result = task.Execute();
 
                // Assert
                Assert.True(result);
                var document = File.ReadAllText(file);
                Assert.Equal(expectedDocument, document, ignoreLineEndingDifferences: true);
            }
            finally
            {
                if (File.Exists(file))
                {
                    File.Delete(file);
                }
            }
        }
 
        private static TaskItem CreateItem(
            string spec,
            IDictionary<string, string> metadata)
        {
            var result = new TaskItem(spec);
            foreach (var (key, value) in metadata)
            {
                result.SetMetadata(key, value);
            }
 
            return result;
        }
    }
}