@@ -601,25 +601,19 @@ public async Task DiscoverUsingRangeStrategy_CalculatesPartCount()
601601 public async Task StartDownloadsAsync_SinglePart_ReturnsImmediately ( )
602602 {
603603 // Arrange
604- var mockClient = MultipartDownloadTestHelpers . CreateMockS3Client ( ) ;
604+ var mockResponse = MultipartDownloadTestHelpers . CreateSinglePartResponse ( 1024 ) ;
605+ var mockClient = MultipartDownloadTestHelpers . CreateMockS3Client (
606+ ( req , ct ) => Task . FromResult ( mockResponse ) ) ;
605607 var request = MultipartDownloadTestHelpers . CreateOpenStreamRequest ( ) ;
606608 var config = MultipartDownloadTestHelpers . CreateBufferedDownloadConfiguration ( ) ;
607609 var coordinator = new MultipartDownloadManager ( mockClient . Object , request , config , CreateMockDataHandler ( ) . Object ) ;
608610
609- var discoveryResult = new DownloadDiscoveryResult
610- {
611- TotalParts = 1 ,
612- ObjectSize = 1024 ,
613- InitialResponse = new GetObjectResponse ( )
614- } ;
615-
616- var mockBufferManager = new Mock < IPartBufferManager > ( ) ;
617-
618- // Act
611+ // Act - Call DiscoverDownloadStrategyAsync first to properly acquire HTTP semaphore
612+ var discoveryResult = await coordinator . DiscoverDownloadStrategyAsync ( CancellationToken . None ) ;
619613 await coordinator . StartDownloadsAsync ( discoveryResult , null , CancellationToken . None ) ;
620614
621- // Assert - should complete without any downloads
622- mockClient . Verify ( x => x . GetObjectAsync ( It . IsAny < GetObjectRequest > ( ) , It . IsAny < CancellationToken > ( ) ) , Times . Never ) ;
615+ // Assert - should complete without any additional downloads (discovery already made the call)
616+ mockClient . Verify ( x => x . GetObjectAsync ( It . IsAny < GetObjectRequest > ( ) , It . IsAny < CancellationToken > ( ) ) , Times . Once ) ;
623617 }
624618
625619 [ TestMethod ]
@@ -1230,22 +1224,35 @@ public async Task StartDownloadsAsync_EarlyError_DisposesCancellationTokenSource
12301224 // Arrange - Test CancellationTokenSource disposal when error occurs before background task starts
12311225 var mockDataHandler = new Mock < IPartDataHandler > ( ) ;
12321226
1227+ // WaitForCapacityAsync succeeds (needed for discovery)
1228+ mockDataHandler
1229+ . Setup ( x => x . WaitForCapacityAsync ( It . IsAny < CancellationToken > ( ) ) )
1230+ . Returns ( Task . CompletedTask ) ;
1231+
1232+ // ProcessPartAsync succeeds for Part 1 (discovery)
1233+ mockDataHandler
1234+ . Setup ( x => x . ProcessPartAsync ( 1 , It . IsAny < GetObjectResponse > ( ) , It . IsAny < CancellationToken > ( ) ) )
1235+ . Returns ( Task . CompletedTask ) ;
1236+
12331237 // Simulate error during PrepareAsync (before background task is created)
12341238 mockDataHandler
12351239 . Setup ( x => x . PrepareAsync ( It . IsAny < DownloadDiscoveryResult > ( ) , It . IsAny < CancellationToken > ( ) ) )
12361240 . ThrowsAsync ( new InvalidOperationException ( "Simulated prepare failure" ) ) ;
12371241
1238- var mockClient = MultipartDownloadTestHelpers . CreateMockS3Client ( ) ;
1239- var request = MultipartDownloadTestHelpers . CreateOpenStreamRequest ( ) ;
1242+ var totalParts = 2 ;
1243+ var partSize = 8 * 1024 * 1024 ;
1244+ var totalObjectSize = totalParts * partSize ;
1245+
1246+ var mockClient = MultipartDownloadTestHelpers . CreateMockS3ClientForMultipart (
1247+ totalParts , partSize , totalObjectSize , "test-etag" , usePartStrategy : true ) ;
1248+
1249+ var request = MultipartDownloadTestHelpers . CreateOpenStreamRequest (
1250+ downloadType : MultipartDownloadType . PART ) ;
12401251 var config = MultipartDownloadTestHelpers . CreateBufferedDownloadConfiguration ( ) ;
12411252 var coordinator = new MultipartDownloadManager ( mockClient . Object , request , config , mockDataHandler . Object ) ;
12421253
1243- var discoveryResult = new DownloadDiscoveryResult
1244- {
1245- TotalParts = 2 ,
1246- ObjectSize = 16 * 1024 * 1024 ,
1247- InitialResponse = new GetObjectResponse ( )
1248- } ;
1254+ // Call DiscoverDownloadStrategyAsync first to properly acquire HTTP semaphore
1255+ var discoveryResult = await coordinator . DiscoverDownloadStrategyAsync ( CancellationToken . None ) ;
12491256
12501257 // Act & Assert
12511258 try
@@ -1599,26 +1606,25 @@ public async Task StartDownloadsAsync_PassesCancellationTokenToBufferManager()
15991606 public async Task StartDownloadsAsync_SinglePart_DoesNotThrowOnCancellation ( )
16001607 {
16011608 // Arrange - Single part download should return immediately without using cancellation token
1602- var mockClient = MultipartDownloadTestHelpers . CreateMockS3Client ( ) ;
1609+ var mockResponse = MultipartDownloadTestHelpers . CreateSinglePartResponse ( 1024 ) ;
1610+ var mockClient = MultipartDownloadTestHelpers . CreateMockS3Client (
1611+ ( req , ct ) => Task . FromResult ( mockResponse ) ) ;
1612+
16031613 var request = MultipartDownloadTestHelpers . CreateOpenStreamRequest ( ) ;
16041614 var config = MultipartDownloadTestHelpers . CreateBufferedDownloadConfiguration ( ) ;
16051615 var coordinator = new MultipartDownloadManager ( mockClient . Object , request , config , CreateMockDataHandler ( ) . Object ) ;
16061616
1607- var discoveryResult = new DownloadDiscoveryResult
1608- {
1609- TotalParts = 1 ,
1610- ObjectSize = 1024 ,
1611- InitialResponse = new GetObjectResponse ( )
1612- } ;
1617+ // Call DiscoverDownloadStrategyAsync first to properly acquire HTTP semaphore
1618+ var discoveryResult = await coordinator . DiscoverDownloadStrategyAsync ( CancellationToken . None ) ;
16131619
16141620 var cts = new CancellationTokenSource ( ) ;
16151621 cts . Cancel ( ) ;
16161622
16171623 // Act - should complete without throwing even though token is cancelled
16181624 await coordinator . StartDownloadsAsync ( discoveryResult , null , cts . Token ) ;
16191625
1620- // Assert - no exception thrown, no S3 calls made
1621- mockClient . Verify ( x => x . GetObjectAsync ( It . IsAny < GetObjectRequest > ( ) , It . IsAny < CancellationToken > ( ) ) , Times . Never ) ;
1626+ // Assert - discovery already made the S3 call, StartDownloadsAsync doesn't make additional calls for single-part
1627+ mockClient . Verify ( x => x . GetObjectAsync ( It . IsAny < GetObjectRequest > ( ) , It . IsAny < CancellationToken > ( ) ) , Times . Once ) ;
16221628 }
16231629
16241630 [ TestMethod ]
@@ -1831,19 +1837,17 @@ public async Task StartDownloadsAsync_ReturnsImmediately_PreventsDeadlock()
18311837 public async Task StartDownloadsAsync_SinglePart_ReturnsImmediatelyWithoutBackgroundTask ( )
18321838 {
18331839 // Arrange - Single-part downloads should not create background tasks
1834- var mockClient = MultipartDownloadTestHelpers . CreateMockS3Client ( ) ;
1840+ var mockResponse = MultipartDownloadTestHelpers . CreateSinglePartResponse ( 1024 ) ;
1841+ var mockClient = MultipartDownloadTestHelpers . CreateMockS3Client (
1842+ ( req , ct ) => Task . FromResult ( mockResponse ) ) ;
18351843 var request = MultipartDownloadTestHelpers . CreateOpenStreamRequest ( ) ;
18361844 var config = MultipartDownloadTestHelpers . CreateBufferedDownloadConfiguration ( ) ;
18371845
18381846 var mockDataHandler = CreateMockDataHandler ( ) ;
18391847 var coordinator = new MultipartDownloadManager ( mockClient . Object , request , config , mockDataHandler . Object ) ;
18401848
1841- var discoveryResult = new DownloadDiscoveryResult
1842- {
1843- TotalParts = 1 ,
1844- ObjectSize = 1024 ,
1845- InitialResponse = new GetObjectResponse ( )
1846- } ;
1849+ // Call DiscoverDownloadStrategyAsync first to properly acquire HTTP semaphore
1850+ var discoveryResult = await coordinator . DiscoverDownloadStrategyAsync ( CancellationToken . None ) ;
18471851
18481852 // Act
18491853 var stopwatch = System . Diagnostics . Stopwatch . StartNew ( ) ;
0 commit comments