11using System . Net . Http . Headers ;
2+ using Polly ;
3+ using Polly . Contrib . WaitAndRetry ;
4+ using Polly . Retry ;
25using StabilityMatrix . Core . Models . Packages ;
36
47namespace StabilityMatrix . Tests . Models . Packages ;
@@ -7,6 +10,7 @@ namespace StabilityMatrix.Tests.Models.Packages;
710/// Tests that URL links on Packages should be valid. Requires internet connection.
811/// </summary>
912[ TestClass ]
13+ [ TestCategory ( "Http" ) ]
1014public sealed class PackageLinkTests
1115{
1216 private static HttpClient HttpClient { get ; } =
@@ -15,14 +19,30 @@ public sealed class PackageLinkTests
1519 private static IEnumerable < object [ ] > PackagesData =>
1620 PackageHelper . GetPackages ( ) . Select ( p => new object [ ] { p } ) ;
1721
22+ private static readonly AsyncRetryPolicy < HttpResponseMessage > RetryPolicy = Policy < HttpResponseMessage >
23+ . HandleResult ( response => response . StatusCode == System . Net . HttpStatusCode . TooManyRequests )
24+ . WaitAndRetryAsync (
25+ Backoff . DecorrelatedJitterBackoffV2 ( TimeSpan . FromMilliseconds ( 200 ) , 3 ) ,
26+ onRetry : ( outcome , timespan , retryAttempt , context ) =>
27+ {
28+ // Log retry attempt if needed
29+ Console . WriteLine ( $ "Retry attempt { retryAttempt } , waiting { timespan . TotalSeconds } seconds") ;
30+ }
31+ ) ;
32+
1833 [ TestMethod ]
1934 [ DynamicData ( nameof ( PackagesData ) ) ]
2035 public async Task TestPreviewImageUri ( BasePackage package )
2136 {
2237 var imageUri = package . PreviewImageUri ;
2338
24- // Test http head is successful
25- var response = await HttpClient . SendAsync ( new HttpRequestMessage ( HttpMethod . Head , imageUri ) ) ;
39+ // If is GitHub Uri, use jsdelivr instead due to rate limiting
40+ imageUri = GitHubToJsDelivr ( imageUri ) ;
41+
42+ // Test http head is successful with retry policy
43+ var response = await RetryPolicy . ExecuteAsync ( async ( ) =>
44+ await HttpClient . SendAsync ( new HttpRequestMessage ( HttpMethod . Head , imageUri ) )
45+ ) ;
2646
2747 Assert . IsTrue (
2848 response . IsSuccessStatusCode ,
@@ -38,13 +58,18 @@ public async Task TestLicenseUrl(BasePackage package)
3858 {
3959 if ( string . IsNullOrEmpty ( package . LicenseUrl ) )
4060 {
41- Assert . Inconclusive ( ) ;
61+ Assert . Inconclusive ( $ "No LicenseUrl for package { package . GetType ( ) . Name } ' { package . Name } '" ) ;
4262 }
4363
44- var licenseUri = package . LicenseUrl ;
64+ var licenseUri = new Uri ( package . LicenseUrl ) ;
65+
66+ // If is GitHub Uri, use jsdelivr instead due to rate limiting
67+ licenseUri = GitHubToJsDelivr ( licenseUri ) ;
4568
46- // Test http head is successful
47- var response = await HttpClient . SendAsync ( new HttpRequestMessage ( HttpMethod . Head , licenseUri ) ) ;
69+ // Test http head is successful with retry policy
70+ var response = await RetryPolicy . ExecuteAsync ( async ( ) =>
71+ await HttpClient . SendAsync ( new HttpRequestMessage ( HttpMethod . Head , licenseUri ) )
72+ ) ;
4873
4974 Assert . IsTrue (
5075 response . IsSuccessStatusCode ,
@@ -53,4 +78,21 @@ public async Task TestLicenseUrl(BasePackage package)
5378 response
5479 ) ;
5580 }
81+
82+ private static Uri GitHubToJsDelivr ( Uri uri )
83+ {
84+ // Like https://github.com/user/Repo/blob/main/LICENSE
85+ // becomes: https://cdn.jsdelivr.net/gh/user/Repo@main/LICENSE
86+ if ( uri . Host . Equals ( "github.com" , StringComparison . OrdinalIgnoreCase ) )
87+ {
88+ var segments = uri . AbsolutePath . Split ( '/' , StringSplitOptions . RemoveEmptyEntries ) ;
89+ if ( segments is [ var user , var repo , "blob" , var branch , ..] )
90+ {
91+ var path = string . Join ( "/" , segments . Skip ( 4 ) ) ;
92+ return new Uri ( $ "https://cdn.jsdelivr.net/gh/{ user } /{ repo } @{ branch } /{ path } ") ;
93+ }
94+ }
95+
96+ return uri ;
97+ }
5698}
0 commit comments