File: StaticWebAssets\ResolveFingerprintedStaticWebAssetEndpointsForAssetsTest.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 ResolveFingerprintedStaticWebAssetEndpointsForAssetsTest
{
    [Theory]
    [InlineData("candidate#[.{fingerprint}]?.js", "candidate.js")]
    [InlineData("candidate#[.{fingerprint}]!.js", "candidate.asdf1234.js")]
    public void Standalone_Selects_EndpointMatching_FilePath(string pattern, string expectedRoute)
    {
        var now = DateTime.Now;
        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));
 
        List<ITaskItem> candidateAssets = [
            CreateCandidate(
                Path.Combine("wwwroot", "candidate.js"),
                "MyPackage",
                "Discovered",
                pattern,
                "All",
                "All",
                "asdf1234",
                "integrity"
            )
        ];
 
        var endpoints = CreateEndpoints(candidateAssets.Select(a => StaticWebAsset.FromTaskItem(a)).ToArray());
 
        var resolvedEndpoints = new ResolveFingerprintedStaticWebAssetEndpointsForAssets
        {
            CandidateAssets = [.. candidateAssets],
            CandidateEndpoints = [..endpoints.Select(e => e.ToTaskItem())],
            IsStandalone = true,
            BuildEngine = buildEngine.Object
        };
 
        // Act
        var result = resolvedEndpoints.Execute();
        result.Should().BeTrue();
 
        // Assert
        resolvedEndpoints.ResolvedEndpoints.Should().HaveCount(1);
        var endpoint = StaticWebAssetEndpoint.FromTaskItem(resolvedEndpoints.ResolvedEndpoints[0]);
 
        endpoint.Route.Should().Be(expectedRoute);
    }
 
    [Fact]
    public void StandaloneFails_MatchingEndpointNotFound()
    {
        var now = DateTime.Now;
        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));
 
        List<ITaskItem> candidateAssets = [
            CreateCandidate(
                Path.Combine("wwwroot", "candidate.js"),
                "MyPackage",
                "Discovered",
                "candidate#[.{fingerprint}]!.js",
                "All",
                "All",
                "asdf1234",
                "integrity"
            )
        ];
 
        var endpoints = CreateEndpoints(candidateAssets.Select(a => StaticWebAsset.FromTaskItem(a)).ToArray());
        endpoints = endpoints.Where(e => !e.Route.Contains("asdf1234")).ToArray();
 
        var resolvedEndpoints = new ResolveFingerprintedStaticWebAssetEndpointsForAssets
        {
            CandidateAssets = [.. candidateAssets],
            CandidateEndpoints = [.. endpoints.Select(e => e.ToTaskItem())],
            IsStandalone = true,
            BuildEngine = buildEngine.Object
        };
 
        // Act
        var result = resolvedEndpoints.Execute();
        result.Should().BeFalse();
    }
 
    [Theory]
    [InlineData("candidate#[.{fingerprint}]?.js", "candidate.asdf1234.js")]
    [InlineData("candidate#[.{fingerprint}]!.js", "candidate.asdf1234.js")]
    public void Hosted_AlwaysPrefers_FingerprintedEndpoint(string pattern, string expectedRoute)
    {
        var now = DateTime.Now;
        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));
 
        List<ITaskItem> candidateAssets = [
            CreateCandidate(
                Path.Combine("wwwroot", "candidate.js"),
                "MyPackage",
                "Discovered",
                pattern,
                "All",
                "All",
                "asdf1234",
                "integrity"
            )
        ];
 
        var endpoints = CreateEndpoints(candidateAssets.Select(a => StaticWebAsset.FromTaskItem(a)).ToArray());
 
        var resolvedEndpoints = new ResolveFingerprintedStaticWebAssetEndpointsForAssets
        {
            CandidateAssets = [.. candidateAssets],
            CandidateEndpoints = [.. endpoints.Select(e => e.ToTaskItem())],
            IsStandalone = false,
            BuildEngine = buildEngine.Object
        };
 
        // Act
        var result = resolvedEndpoints.Execute();
        result.Should().BeTrue();
 
        // Assert
        resolvedEndpoints.ResolvedEndpoints.Should().HaveCount(1);
        var endpoint = StaticWebAssetEndpoint.FromTaskItem(resolvedEndpoints.ResolvedEndpoints[0]);
 
        endpoint.Route.Should().Be(expectedRoute);
    }
 
    [Fact]
    public void Hosted_FallsBackToNonFingerprintedEndpoint_WhenFingerprintedVersionNotAvailable()
    {
        var now = DateTime.Now;
        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));
 
        List<ITaskItem> candidateAssets = [
            CreateCandidate(
                Path.Combine("wwwroot", "candidate.js"),
                "MyPackage",
                "Discovered",
                "candidate.js",
                "All",
                "All",
                "asdf1234",
                "integrity"
            )
        ];
 
        var endpoints = CreateEndpoints(candidateAssets.Select(a => StaticWebAsset.FromTaskItem(a)).ToArray());
 
        var resolvedEndpoints = new ResolveFingerprintedStaticWebAssetEndpointsForAssets
        {
            CandidateAssets = [.. candidateAssets],
            CandidateEndpoints = [.. endpoints.Select(e => e.ToTaskItem())],
            IsStandalone = false,
            BuildEngine = buildEngine.Object
        };
 
        // Act
        var result = resolvedEndpoints.Execute();
        result.Should().BeTrue();
 
        // Assert
        resolvedEndpoints.ResolvedEndpoints.Should().HaveCount(1);
        var endpoint = StaticWebAssetEndpoint.FromTaskItem(resolvedEndpoints.ResolvedEndpoints[0]);
 
        endpoint.Route.Should().Be("candidate.js");
    }
 
    [Fact]
    public void Hosted_FailsWhen_DoesnotFindMatchingEndpoint()
    {
        var now = DateTime.Now;
        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));
 
        List<ITaskItem> candidateAssets = [
            CreateCandidate(
                Path.Combine("wwwroot", "candidate.js"),
                "MyPackage",
                "Discovered",
                "candidate.js",
                "All",
                "All",
                "asdf1234",
                "integrity"
            )
        ];
 
        var endpoints = CreateEndpoints(candidateAssets.Select(a => StaticWebAsset.FromTaskItem(a)).ToArray());
        endpoints = endpoints.Where(e => !e.Route.Contains("asdf1234")).ToArray();
        endpoints[0].AssetFile = Path.GetFullPath("other.js");
 
        var resolvedEndpoints = new ResolveFingerprintedStaticWebAssetEndpointsForAssets
        {
            CandidateAssets = [.. candidateAssets],
            CandidateEndpoints = [.. endpoints.Select(e => e.ToTaskItem())],
            IsStandalone = false,
            BuildEngine = buildEngine.Object
        };
 
        // Act
        var result = resolvedEndpoints.Execute();
        result.Should().BeFalse();
    }
 
    private static ITaskItem CreateCandidate(
        string itemSpec,
        string sourceId,
        string sourceType,
        string relativePath,
        string assetKind,
        string assetMode,
        string fingerprint = "",
        string integrity = "",
        string relatedAsset = "",
        string assetTraitName = "",
        string assetTraitValue = "")
    {
        var result = new StaticWebAsset()
        {
            Identity = Path.GetFullPath(itemSpec),
            SourceId = sourceId,
            SourceType = sourceType,
            ContentRoot = Directory.GetCurrentDirectory(),
            BasePath = "base",
            RelativePath = relativePath,
            AssetKind = assetKind,
            AssetMode = assetMode,
            AssetRole = "Primary",
            RelatedAsset = relatedAsset,
            AssetTraitName = assetTraitName,
            AssetTraitValue = assetTraitValue,
            CopyToOutputDirectory = "",
            CopyToPublishDirectory = "",
            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.ToTaskItem();
    }
 
    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" }
        });
    }
}