File: StaticWebAssets\FilterStaticWebAssetEndpointsTest.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;
 
namespace Microsoft.NET.Sdk.StaticWebAssets.Tests.StaticWebAssets;
public class FilterStaticWebAssetEndpointsTest
{
    [Fact]
    public void CanFilterEndpoints_ByAssetFile()
    {
        var assets = new[] {
            CreateAsset("index.html", relativePath: "index#[.{fingerprint}]?.html"),
            CreateAsset("index.js", relativePath: "index#[.{fingerprint}]?.js"),
            CreateAsset("index.css", relativePath: "index#[.{fingerprint}]?.css"),
            CreateAsset("other.html", relativePath: "other#[.{fingerprint}]?.html"),
            CreateAsset("other.js", relativePath: "other#[.{fingerprint}]?.js"),
            CreateAsset("other.css", relativePath: "other#[.{fingerprint}]?.css"),
        };
        Array.Sort(assets, (l, r) => string.Compare(l.Identity, r.Identity, StringComparison.Ordinal));
 
        var endpoints = CreateEndpoints(assets);
        var expectedEndpoints = new[] {
            endpoints[0], //index.css
            endpoints[1], //index.fingerprint.css
            endpoints[8], // other.html
            endpoints[10] // other.fingerprint.html
        };
        Array.Sort(expectedEndpoints);
        var filterEndpointsTask = new FilterStaticWebAssetEndpoints()
        {
            Endpoints = endpoints.Select(endpoints => endpoints.ToTaskItem()).ToArray(),
            Assets =
            [
                // index.css
                assets[0].ToTaskItem(),
                // other.html
                assets[4].ToTaskItem(),
            ],
        };
 
        // Act
        var result = filterEndpointsTask.Execute();
 
        // Assert
        result.Should().BeTrue();
        var filteredEndpoints = StaticWebAssetEndpoint.FromItemGroup(filterEndpointsTask.FilteredEndpoints);
        Array.Sort(filteredEndpoints);
        filteredEndpoints.Should().HaveCount(4);
        filteredEndpoints.Should().BeEquivalentTo(expectedEndpoints);
    }
 
    [Fact]
    public void CanFilterEndpoints_ByProperty()
    {
        var assets = new[] {
            CreateAsset("index.html", relativePath: "index#[.{fingerprint}]?.html"),
            CreateAsset("index.js", relativePath: "index#[.{fingerprint}]?.js"),
            CreateAsset("index.css", relativePath: "index#[.{fingerprint}]?.css"),
            CreateAsset("other.html", relativePath: "other#[.{fingerprint}]?.html"),
            CreateAsset("other.js", relativePath: "other#[.{fingerprint}]?.js"),
            CreateAsset("other.css", relativePath: "other#[.{fingerprint}]?.css"),
        };
        Array.Sort(assets, (l, r) => string.Compare(l.Identity, r.Identity, StringComparison.Ordinal));
 
        var endpoints = CreateEndpoints(assets);
        var filterEndpointsTask = new FilterStaticWebAssetEndpoints()
        {
            Endpoints = endpoints.Select(endpoints => endpoints.ToTaskItem()).ToArray(),
            Filters = [
                new TaskItem("Property", new Dictionary<string,string>{
                    ["Name"] = "fingerprint"
                })
            ],
            BuildEngine = Mock.Of<IBuildEngine>()
        };
 
        // Act
        var result = filterEndpointsTask.Execute();
 
        // Assert
        result.Should().BeTrue();
        var filteredEndpoints = StaticWebAssetEndpoint.FromItemGroup(filterEndpointsTask.FilteredEndpoints);
        Array.Sort(filteredEndpoints);
        filteredEndpoints.Should().HaveCount(6);
        filteredEndpoints.Should().AllSatisfy(e => e.EndpointProperties.Should().ContainSingle(p => p.Name == "fingerprint"));
    }
 
    [Fact]
    public void CanFilterEndpoints_ByResponseHeader()
    {
        var assets = new[] {
            CreateAsset("index.html", relativePath: "index#[.{fingerprint}]?.html"),
            CreateAsset("index.js", relativePath: "index#[.{fingerprint}]?.js"),
            CreateAsset("index.css", relativePath: "index#[.{fingerprint}]?.css"),
            CreateAsset("other.html", relativePath: "other#[.{fingerprint}]?.html"),
            CreateAsset("other.js", relativePath: "other#[.{fingerprint}]?.js"),
            CreateAsset("other.css", relativePath: "other#[.{fingerprint}]?.css"),
        };
        Array.Sort(assets, (l, r) => string.Compare(l.Identity, r.Identity, StringComparison.Ordinal));
 
        var endpoints = CreateEndpoints(assets);
        var filterEndpointsTask = new FilterStaticWebAssetEndpoints()
        {
            Endpoints = endpoints.Select(endpoints => endpoints.ToTaskItem()).ToArray(),
            Filters = [
                new TaskItem("Header", new Dictionary<string,string>{
                    ["Name"] = "Content-Type",
                    ["Value"] = "text/html"
                })
            ],
            BuildEngine = Mock.Of<IBuildEngine>()
        };
 
        // Act
        var result = filterEndpointsTask.Execute();
 
        // Assert
        result.Should().BeTrue();
        var filteredEndpoints = StaticWebAssetEndpoint.FromItemGroup(filterEndpointsTask.FilteredEndpoints);
        Array.Sort(filteredEndpoints);
        filteredEndpoints.Should().HaveCount(4);
        filteredEndpoints.Should().AllSatisfy(e => e.ResponseHeaders.Should().ContainSingle(p => p.Name == "Content-Type" && p.Value == "text/html"));
    }
 
    [Fact]
    public void CanFilterEndpoints_Standalone()
    {
        var assets = new[] {
            CreateAsset("index.html", relativePath: "index#[.{fingerprint}]?.html"),
            CreateAsset("other.js", relativePath: "other#[.{fingerprint}]!.js"),
        };
        Array.Sort(assets, (l, r) => string.Compare(l.Identity, r.Identity, StringComparison.Ordinal));
 
        var endpoints = CreateEndpoints(assets);
        var filterEndpointsTask = new FilterStaticWebAssetEndpoints()
        {
            Endpoints = endpoints.Select(endpoints => endpoints.ToTaskItem()).ToArray(),
            Assets = [.. assets.Select(a => a.ToTaskItem())],
            Filters = [
                new TaskItem("Standalone", new Dictionary<string,string>{ })
            ],
            BuildEngine = Mock.Of<IBuildEngine>()
        };
 
        // Act
        var result = filterEndpointsTask.Execute();
 
        // Assert
        result.Should().BeTrue();
        var filteredEndpoints = StaticWebAssetEndpoint.FromItemGroup(filterEndpointsTask.FilteredEndpoints);
        Array.Sort(filteredEndpoints);
        filteredEndpoints.Should().HaveCount(2);
        filteredEndpoints.Where(e => e.Route == "index.html").Should().ContainSingle();
        filteredEndpoints.Where(e => e.Route == "other.fingerprint.js").Should().ContainSingle();
    }
 
    [Fact]
    public void CanFilterEndpoints_BySelector()
    {
        var assets = new[] {
            CreateAsset("index.html", relativePath: "index#[.{fingerprint}]?.html"),
            CreateAsset("index.js", relativePath: "index#[.{fingerprint}]?.js"),
            CreateAsset("index.css", relativePath: "index#[.{fingerprint}]?.css"),
            CreateAsset("other.html", relativePath: "other#[.{fingerprint}]?.html"),
            CreateAsset("other.js", relativePath: "other#[.{fingerprint}]?.js"),
            CreateAsset("other.css", relativePath: "other#[.{fingerprint}]?.css"),
        };
        Array.Sort(assets, (l, r) => string.Compare(l.Identity, r.Identity, StringComparison.Ordinal));
 
        var endpoints = CreateEndpoints(assets);
        endpoints[0].Selectors = [new StaticWebAssetEndpointSelector { Name = "Content-Encoding", Value = "gzip" }];
        var filterEndpointsTask = new FilterStaticWebAssetEndpoints()
        {
            Endpoints = endpoints.Select(endpoints => endpoints.ToTaskItem()).ToArray(),
            Filters = [
                new TaskItem("Selector", new Dictionary<string,string>{
                    ["Name"] = "Content-Encoding",
                    ["Value"] = "gzip"
                })
            ],
            BuildEngine = Mock.Of<IBuildEngine>()
        };
 
        // Act
        var result = filterEndpointsTask.Execute();
 
        // Assert
        result.Should().BeTrue();
        var filteredEndpoints = StaticWebAssetEndpoint.FromItemGroup(filterEndpointsTask.FilteredEndpoints);
        Array.Sort(filteredEndpoints);
        filteredEndpoints.Should().ContainSingle();
        filteredEndpoints[0].Route.Should().Be(endpoints[0].Route);
    }
 
    [Fact]
    public void CanFilterEndpoints_ByMultipleCriteria()
    {
        var assets = new[] {
            CreateAsset("index.html", relativePath: "index#[.{fingerprint}]?.html"),
            CreateAsset("index.js", relativePath: "index#[.{fingerprint}]?.js"),
            CreateAsset("index.css", relativePath: "index#[.{fingerprint}]?.css"),
            CreateAsset("other.html", relativePath: "other#[.{fingerprint}]?.html"),
            CreateAsset("other.js", relativePath: "other#[.{fingerprint}]?.js"),
            CreateAsset("other.css", relativePath: "other#[.{fingerprint}]?.css"),
        };
        Array.Sort(assets, (l, r) => string.Compare(l.Identity, r.Identity, StringComparison.Ordinal));
 
        var endpoints = CreateEndpoints(assets);
        var filterEndpointsTask = new FilterStaticWebAssetEndpoints()
        {
            Endpoints = endpoints.Select(endpoints => endpoints.ToTaskItem()).ToArray(),
            Filters = [
                new TaskItem("Header", new Dictionary<string,string>{
                    ["Name"] = "Content-Type",
                    ["Value"] = "text/html"
                }),
                new TaskItem("Property", new Dictionary<string,string>{
                    ["Name"] = "fingerprint"
                })
            ],
            BuildEngine = Mock.Of<IBuildEngine>()
        };
 
        // Act
        var result = filterEndpointsTask.Execute();
 
        // Assert
        result.Should().BeTrue();
        var filteredEndpoints = StaticWebAssetEndpoint.FromItemGroup(filterEndpointsTask.FilteredEndpoints);
        Array.Sort(filteredEndpoints);
        filteredEndpoints.Should().HaveCount(2);
        filteredEndpoints.Should().AllSatisfy(e => e.ResponseHeaders.Should().ContainSingle(p => p.Name == "Content-Type" && p.Value == "text/html"));
        filteredEndpoints.Should().AllSatisfy(e => e.EndpointProperties.Should().ContainSingle(p => p.Name == "fingerprint"));
    }
 
    private StaticWebAssetEndpoint[] CreateEndpoints(StaticWebAsset[] assets)
    {
        var defineStaticWebAssetEndpoints = new DefineStaticWebAssetEndpoints
        {
            CandidateAssets = assets.Select(a => a.ToTaskItem()).ToArray(),
            ExistingEndpoints = [],
            ContentTypeMappings =
            [
                CreateContentMapping("*.html", "text/html"),
                CreateContentMapping("*.js", "application/javascript"),
                CreateContentMapping("*.css", "text/css"),
            ]
        };
        defineStaticWebAssetEndpoints.BuildEngine = Mock.Of<IBuildEngine>();
 
        defineStaticWebAssetEndpoints.Execute();
        return StaticWebAssetEndpoint.FromItemGroup(defineStaticWebAssetEndpoints.Endpoints);
    }
 
    private static TaskItem CreateContentMapping(string pattern, string contentType)
    {
        return new TaskItem(contentType, new Dictionary<string, string>
        {
            { "Pattern", pattern },
            { "Priority", "0" }
        });
    }
 
    private static StaticWebAsset CreateAsset(
        string itemSpec,
        string sourceId = "MyApp",
        string sourceType = "Discovered",
        string relativePath = null,
        string assetKind = "All",
        string assetMode = "All",
        string basePath = "base",
        string assetRole = "Primary",
        string relatedAsset = "",
        string assetTraitName = "",
        string assetTraitValue = "",
        string copyToOutputDirectory = "Never",
        string copytToPublishDirectory = "PreserveNewest")
    {
        var result = new StaticWebAsset()
        {
            Identity = Path.GetFullPath(itemSpec),
            SourceId = sourceId,
            SourceType = sourceType,
            ContentRoot = Directory.GetCurrentDirectory(),
            BasePath = basePath,
            RelativePath = relativePath ?? itemSpec,
            AssetKind = assetKind,
            AssetMode = assetMode,
            AssetRole = assetRole,
            AssetMergeBehavior = StaticWebAsset.MergeBehaviors.PreferTarget,
            AssetMergeSource = "",
            RelatedAsset = relatedAsset,
            AssetTraitName = assetTraitName,
            AssetTraitValue = assetTraitValue,
            CopyToOutputDirectory = copyToOutputDirectory,
            CopyToPublishDirectory = copytToPublishDirectory,
            OriginalItemSpec = itemSpec,
            // Add these to avoid accessing the disk to compute them
            Integrity = "integrity",
            Fingerprint = "fingerprint",
            FileLength = 10,
            LastWriteTime = DateTime.UtcNow,
        };
 
        result.ApplyDefaults();
        result.Normalize();
 
        return result;
    }
}