/
githubmirror
/
aspnetcore
Обзор
Документация
Войти
/
githubmirror
/
aspnetcore
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Components/Endpoints/test/Builder/ResourceCollectionUrlEndpointTest.cs
247 строк
9 KB
Copilot
[Blazor] Fix resource collection gzip footer (#66242)
09 апр 2026, 19:38
Не верифицирован
09 апр 2026, 19:38
094a982
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.IO.Compression; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; namespace Microsoft.AspNetCore.Components.Endpoints; public class ResourceCollectionUrlEndpointTest { [Fact] public void ComputeFingerprintSuffix_IncludesIntegrityForNonFingerprintedAssets() { // Arrange - Create a collection with non-fingerprinted assets that have integrity hashes ResourceAsset[] resources = [ // Non-fingerprinted asset with integrity (simulates WasmFingerprintAssets=false scenario) new("/_framework/MyApp.dll", [ new("integrity", "sha256-ABC123") ]), new("/_framework/System.dll", [ new("integrity", "sha256-XYZ789") ]) ]; var collection = new ResourceAssetCollection(resources); // Act var fingerprint1 = ResourceCollectionUrlEndpoint.ComputeFingerprintSuffix(collection); // Arrange - Change the integrity of one asset (simulates content change between builds) resources = [ new("/_framework/MyApp.dll", [ new("integrity", "sha256-CHANGED") ]), new("/_framework/System.dll", [ new("integrity", "sha256-XYZ789") ]) ]; collection = new ResourceAssetCollection(resources); // Act var fingerprint2 = ResourceCollectionUrlEndpoint.ComputeFingerprintSuffix(collection); // Assert - Fingerprints should be different because integrity changed Assert.NotEqual(fingerprint1, fingerprint2); } [Fact] public void ComputeFingerprintSuffix_DoesNotIncludeIntegrityForFingerprintedAssets() { // Arrange - Create a collection with fingerprinted assets (have label property) ResourceAsset[] resources = [ // Fingerprinted asset with label (simulates WasmFingerprintAssets=true scenario) new("/_framework/MyApp.ABC123.dll", [ new("label", "MyApp.dll"), new("integrity", "sha256-ABC123") ]), new("/_framework/System.XYZ789.dll", [ new("label", "System.dll"), new("integrity", "sha256-XYZ789") ]) ]; var collection = new ResourceAssetCollection(resources); // Act var fingerprint1 = ResourceCollectionUrlEndpoint.ComputeFingerprintSuffix(collection); // Arrange - Change the integrity (but not the URL) of one asset // For fingerprinted assets, the URL already contains the hash, so we don't need to include integrity resources = [ new("/_framework/MyApp.ABC123.dll", [ new("label", "MyApp.dll"), new("integrity", "sha256-CHANGED") ]), new("/_framework/System.XYZ789.dll", [ new("label", "System.dll"), new("integrity", "sha256-XYZ789") ]) ]; collection = new ResourceAssetCollection(resources); // Act var fingerprint2 = ResourceCollectionUrlEndpoint.ComputeFingerprintSuffix(collection); // Assert - Fingerprints should be the same because for fingerprinted assets, // the URL (not integrity) is what matters, and the URL didn't change Assert.Equal(fingerprint1, fingerprint2); } [Fact] public void ComputeFingerprintSuffix_HandlesMixedAssets() { // Arrange - Mix of fingerprinted and non-fingerprinted assets ResourceAsset[] resources = [ // Fingerprinted asset new("/_framework/MyApp.ABC123.dll", [ new("label", "MyApp.dll"), new("integrity", "sha256-ABC123") ]), // Non-fingerprinted asset new("/_framework/custom.js", [ new("integrity", "sha256-CUSTOM") ]) ]; var collection = new ResourceAssetCollection(resources); // Act var fingerprint1 = ResourceCollectionUrlEndpoint.ComputeFingerprintSuffix(collection); // Arrange - Change only the non-fingerprinted asset's integrity resources = [ // Fingerprinted asset (same as before) new("/_framework/MyApp.ABC123.dll", [ new("label", "MyApp.dll"), new("integrity", "sha256-ABC123") ]), // Non-fingerprinted asset with changed integrity new("/_framework/custom.js", [ new("integrity", "sha256-MODIFIED") ]) ]; collection = new ResourceAssetCollection(resources); // Act var fingerprint2 = ResourceCollectionUrlEndpoint.ComputeFingerprintSuffix(collection); // Assert - Fingerprints should be different because non-fingerprinted asset's integrity changed Assert.NotEqual(fingerprint1, fingerprint2); } [Fact] public void ComputeFingerprintSuffix_HandlesAssetsWithNoProperties() { // Arrange ResourceAsset[] resources = [ new("/_framework/file1.dll", null), new("/_framework/file2.dll", []) ]; var collection = new ResourceAssetCollection(resources); // Act & Assert - Should not throw var fingerprint = ResourceCollectionUrlEndpoint.ComputeFingerprintSuffix(collection); Assert.NotNull(fingerprint); Assert.StartsWith(".", fingerprint); } [Fact] public void ComputeFingerprintSuffix_ChangesWhenNonFingerprintedAssetIntegrityChanges() { // This test simulates the original bug scenario: // When WasmFingerprintAssets=false, changing a file (e.g., Counter.razor or wwwroot/styles.css) // updates its integrity hash but not its URL. The resource-collection fingerprint must change // to prevent serving stale cached resource-collection.js with outdated integrity values. // Arrange - Initial state: non-fingerprinted asset with original integrity var collection1 = new ResourceAssetCollection( [ new("/_framework/app.styles.css", [ new("integrity", "sha256-OriginalHash123456") ]) ]); // Act - Compute initial fingerprint var fingerprintSuffix1 = ResourceCollectionUrlEndpoint.ComputeFingerprintSuffix(collection1); // Arrange - Simulate content change: same URL, different integrity (e.g., after modifying styles.css) var collection2 = new ResourceAssetCollection( [ new("/_framework/app.styles.css", [ new("integrity", "sha256-ModifiedHash789012") ]) ]); // Act - Compute fingerprint after content change var fingerprintSuffix2 = ResourceCollectionUrlEndpoint.ComputeFingerprintSuffix(collection2); // Assert - Fingerprint must be different to ensure browser fetches updated resource-collection.js Assert.NotEqual(fingerprintSuffix1, fingerprintSuffix2); } [Fact] public async Task MapResourceCollectionEndpoints_GzipEndpointReturnsCompleteGzipPayload() { var endpoints = new List<Endpoint>(); var collection = new ResourceAssetCollection( [ new("/_framework/app.dll", [ new("integrity", "sha256-OriginalHash123456") ]) ]); ResourceCollectionUrlEndpoint.MapResourceCollectionEndpoints( endpoints, "_framework/resource-collection{0}.js{1}", collection); var gzipEndpoint = Assert.Single(endpoints.OfType<RouteEndpoint>(), endpoint => endpoint.RoutePattern.RawText == "_framework/resource-collection.js.gz"); var contentEndpoint = Assert.Single( endpoints.OfType<RouteEndpoint>(), endpoint => endpoint.RoutePattern.RawText == "_framework/resource-collection.js" && endpoint.Metadata.GetMetadata<ContentEncodingMetadata>() is null); var gzipContext = new DefaultHttpContext(); await using var gzipResponseBody = new MemoryStream(); gzipContext.Response.Body = gzipResponseBody; await gzipEndpoint.RequestDelegate!(gzipContext); gzipResponseBody.Position = 0; await using var decompressedBody = new MemoryStream(); await using (var gzipStream = new GZipStream(gzipResponseBody, CompressionMode.Decompress, leaveOpen: true)) { await gzipStream.CopyToAsync(decompressedBody); } var contentContext = new DefaultHttpContext(); await using var contentResponseBody = new MemoryStream(); contentContext.Response.Body = contentResponseBody; await contentEndpoint.RequestDelegate!(contentContext); Assert.Equal("gzip", gzipContext.Response.Headers.ContentEncoding); Assert.Equal(contentResponseBody.ToArray(), decompressedBody.ToArray()); } }