-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Fix resource-collection.js fingerprint for non-fingerprinted assets #64857
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9b9ec1b
7827aaf
00e5cf2
9c8b9c1
0a8a76e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| 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); | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @copilot Add a unit test that simulates the original scenario, which is the integrity of a non-fingerprinted asset changes. First we compute the fingerprint suffix, then we change the integrity on a non-fingerprinted asset and verify the suffix is different.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the only test we need for this. All other behavior will be well covered by existing tests
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added |
||
|
|
||
| [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); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Look at the microsoftdocs and use modern C# (collection initializers, target type, etc.) where possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated to use collection expressions and target-typed
new()throughout the tests for modern C# style. Commit: 9c8b9c1