diff --git a/src/StaticWebAssetsSdk/Tasks/Compression/DiscoverPrecompressedAssets.cs b/src/StaticWebAssetsSdk/Tasks/Compression/DiscoverPrecompressedAssets.cs index d3f4c5ee87b0..cfa53efd745f 100644 --- a/src/StaticWebAssetsSdk/Tasks/Compression/DiscoverPrecompressedAssets.cs +++ b/src/StaticWebAssetsSdk/Tasks/Compression/DiscoverPrecompressedAssets.cs @@ -32,7 +32,16 @@ public override bool Execute() var candidates = CandidateAssets.Select(StaticWebAsset.FromTaskItem).ToArray(); var assetsToUpdate = new List(); - var candidatesByIdentity = candidates.ToDictionary(asset => asset.Identity, OSPath.PathComparer); + var candidatesByIdentity = new Dictionary(OSPath.PathComparer); + + foreach (var asset in candidates) + { + // Assets might contain duplicated keys, use the first occurance + if (!candidatesByIdentity.ContainsKey(asset.Identity)) + { + candidatesByIdentity[asset.Identity] = asset; + } + } foreach (var candidate in candidates) { diff --git a/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssets/DiscoverPrecompressedAssetsTest.cs b/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssets/DiscoverPrecompressedAssetsTest.cs index 45d7dd542de8..275b13296aea 100644 --- a/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssets/DiscoverPrecompressedAssetsTest.cs +++ b/test/Microsoft.NET.Sdk.Razor.Tests/StaticWebAssets/DiscoverPrecompressedAssetsTest.cs @@ -24,6 +24,109 @@ public DiscoverPrecompressedAssetsTest() OriginalItemSpec = Path.Combine(OutputBasePath, Guid.NewGuid().ToString("N") + ".tmp"); } + [Fact] + public void HandlesDuplicateAssetsCorrectly() + { + // Arrange + var errorMessages = new List(); + var buildEngine = new Mock(); + buildEngine.Setup(e => e.LogErrorEvent(It.IsAny())) + .Callback(args => errorMessages.Add(args.Message)); + + // Create base uncompressed asset + var uncompressedCandidate = new StaticWebAsset + { + Identity = Path.Combine(Environment.CurrentDirectory, "wwwroot", "js", "site.js"), + RelativePath = "js/site#[.{fingerprint}]?.js", + BasePath = "_content/Test", + AssetMode = StaticWebAsset.AssetModes.All, + AssetKind = StaticWebAsset.AssetKinds.All, + Fingerprint = "uncompressed", + ContentRoot = Path.Combine(Environment.CurrentDirectory,"wwwroot"), + SourceType = StaticWebAsset.SourceTypes.Discovered, + CopyToPublishDirectory = StaticWebAsset.AssetCopyOptions.PreserveNewest, + SourceId = "TestSource", + OriginalItemSpec = Path.Combine("wwwroot", "js", "site.js"), + AssetRole = StaticWebAsset.AssetRoles.Primary, + AssetMergeBehavior = string.Empty, + AssetTraitValue = string.Empty, + AssetTraitName = string.Empty, + RelatedAsset = string.Empty, + Integrity = "uncompressed-integrity" + }; + + // Create duplicate asset with the same identity + var duplicateUncompressedCandidate = new StaticWebAsset + { + Identity = Path.Combine(Environment.CurrentDirectory, "wwwroot", "js", "site.js"), + RelativePath = "js/site#[.{fingerprint}]?.js", + BasePath = "_content/Test", + AssetMode = StaticWebAsset.AssetModes.All, + AssetKind = StaticWebAsset.AssetKinds.All, + Fingerprint = "duplicate-uncompressed", + ContentRoot = Path.Combine(Environment.CurrentDirectory,"wwwroot"), + SourceType = StaticWebAsset.SourceTypes.Discovered, + CopyToPublishDirectory = StaticWebAsset.AssetCopyOptions.PreserveNewest, + SourceId = "TestSource", + OriginalItemSpec = Path.Combine("wwwroot", "js", "site.js"), + AssetRole = StaticWebAsset.AssetRoles.Primary, + AssetMergeBehavior = string.Empty, + AssetTraitValue = string.Empty, + AssetTraitName = string.Empty, + RelatedAsset = string.Empty, + Integrity = "uncompressed-integrity" + }; + + // Create compressed version of the asset + var compressedCandidate = new StaticWebAsset + { + Identity = Path.Combine(Environment.CurrentDirectory, "wwwroot", "js", "site.js.gz"), + RelativePath = "js/site.js#[.{fingerprint}]?.gz", + BasePath = "_content/Test", + AssetMode = StaticWebAsset.AssetModes.All, + AssetKind = StaticWebAsset.AssetKinds.All, + Fingerprint = "compressed", + ContentRoot = Path.Combine(Environment.CurrentDirectory, "wwwroot"), + SourceType = StaticWebAsset.SourceTypes.Discovered, + CopyToPublishDirectory = StaticWebAsset.AssetCopyOptions.PreserveNewest, + SourceId = "TestSource", + OriginalItemSpec = Path.Combine("wwwroot", "js", "site.js.gz"), + AssetRole = StaticWebAsset.AssetRoles.Primary, + AssetMergeBehavior = string.Empty, + AssetTraitValue = string.Empty, + AssetTraitName = string.Empty, + RelatedAsset = string.Empty, + Integrity = "compressed-integrity" + }; + + var task = new DiscoverPrecompressedAssets + { + CandidateAssets = [ + uncompressedCandidate.ToTaskItem(), + duplicateUncompressedCandidate.ToTaskItem(), + compressedCandidate.ToTaskItem() + ], + BuildEngine = buildEngine.Object + }; + + // Act + var result = task.Execute(); + var discoveredAssets = task.DiscoveredCompressedAssets; + + // Assert + // Verify task completed successfully despite duplicate keys + result.Should().BeTrue(); + + // Verify only one compressed asset was discovered + discoveredAssets.Should().ContainSingle(); + + var discoveredAsset = discoveredAssets[0]; + + // Verify the first (non-duplicate) asset was used as related asset + discoveredAsset.GetMetadata("RelatedAsset").Should().Be(uncompressedCandidate.Identity); + discoveredAsset.GetMetadata("Fingerprint").Should().Be("compressed"); + } + [Fact] public void DiscoversPrecompressedAssetsCorrectly() {