11// Copyright (c) .NET Foundation. All rights reserved.
22// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33
4- namespace NuGet . CommandLine . Xplat . Tests ;
5-
64using System ;
75using System . Collections . Generic ;
86using System . IO ;
97using System . Linq ;
108using System . Threading ;
119using System . Threading . Tasks ;
1210using FluentAssertions ;
11+ using global ::Test . Utility ;
1312using Moq ;
1413using NuGet . CommandLine . XPlat ;
1514using NuGet . CommandLine . XPlat . Commands . Package ;
@@ -20,6 +19,8 @@ namespace NuGet.CommandLine.Xplat.Tests;
2019using NuGet . Versioning ;
2120using Xunit ;
2221
22+ namespace NuGet . CommandLine . Xplat . Tests ;
23+
2324public class PackageDownloadRunnerTests
2425{
2526 public static IEnumerable < object [ ] > PackageTestData ( )
@@ -280,4 +281,174 @@ [new PackageSource(context.PackageSource)],
280281 File . Exists ( Path . Combine ( context . WorkingDirectory , $ "{ id . ToLowerInvariant ( ) } .{ v } .nupkg") )
281282 . Should ( ) . BeFalse ( "Package does not exist in sources" ) ;
282283 }
284+
285+ public static IEnumerable < object [ ] > Cases ( )
286+ {
287+ // Parameters:
288+ // A-packages, B-packages, sourceMappings, sourcesArgs, downloadId, downloadVersion,
289+ // allowInsecureConnections, expectSuccess, expectedInstalled
290+
291+ // --source specified, mapping ignored, package only in A -> success
292+ yield return new object [ ]
293+ {
294+ new List < ( string , string ) > { ( "Contoso.Lib" , "1.0.0" ) } , // A
295+ new List < ( string , string ) > ( ) , // B
296+ new List < ( string , string ) > { ( "B" , "Contoso.*" ) } , // mapping ignored
297+ new List < string > { "A" } , // --source A
298+ "Contoso.Lib" , "1.0.0" , // downloadId, downloadVersion
299+ true , // allow insecure
300+ true , // expect success
301+ ( "Contoso.Lib" , "1.0.0" ) // expectedInstalled
302+ } ;
303+
304+ // no --source, mapping -> B, package only in B -> success
305+ yield return new object [ ]
306+ {
307+ new List < ( string , string ) > ( ) , // A
308+ new List < ( string , string ) > { ( "Contoso.Mapped" , "2.0.0" ) } , // B
309+ new List < ( string , string ) > { ( "B" , "Contoso.*" ) } , // mapping -> B
310+ null , // no --source
311+ "Contoso.Mapped" , "2.0.0" , // downloadId, downloadVersion
312+ true , // allow insecure
313+ true , // expect success
314+ ( "Contoso.Mapped" , "2.0.0" ) // expectedInstalled
315+ } ;
316+
317+ // no --source, mapping -> A, package only in B -> fail
318+ yield return new object [ ]
319+ {
320+ new List < ( string , string ) > ( ) , // A
321+ new List < ( string , string ) > { ( "Contoso.Mapped" , "2.0.0" ) } ,
322+ new List < ( string , string ) > { ( "A" , "Contoso.*" ) } , // mapped to A
323+ null ,
324+ "Contoso.Mapped" , "2.0.0" ,
325+ true ,
326+ false ,
327+ null !
328+ } ;
329+
330+ // --source specified, no source mapping with an insecure source
331+ yield return new object [ ]
332+ {
333+ new List < ( string , string ) > { ( "Contoso.Lib" , "1.0.0" ) } , // A
334+ new List < ( string , string ) > ( ) ,
335+ new List < ( string , string ) > { ( "A" , "Contoso.*" ) } ,
336+ new List < string > { "A" } , // --source
337+ "Contoso.Lib" , "1.0.0" ,
338+ false , // allow insecure connections false / not set to true
339+ false ,
340+ null !
341+ } ;
342+
343+ // no --source, mapping -> B, allow insecure not enabled -> fail
344+ yield return new object [ ]
345+ {
346+ new List < ( string , string ) > ( ) , // A
347+ new List < ( string , string ) > { ( "Contoso.Mapped" , "1.0.0" ) } ,
348+ new List < ( string , string ) > { ( "B" , "Contoso.*" ) } ,
349+ null ,
350+ "Contoso.Mapped" , "1.0.0" ,
351+ false , // allow insecure connections false / not set to true
352+ false ,
353+ null !
354+ } ;
355+ }
356+
357+ [ Theory ]
358+ [ MemberData ( nameof ( Cases ) ) ]
359+ public async Task RunAsync_WithSourceMapping_ListDriven_UsingCleanSetup (
360+ IReadOnlyList < ( string id , string version ) > sourceAPackages ,
361+ IReadOnlyList < ( string id , string version ) > sourceBPackages ,
362+ IReadOnlyList < ( string source , string pattern ) > sourceMappings ,
363+ IReadOnlyList < string > sourcesArgs ,
364+ string downloadId ,
365+ string downloadVersion ,
366+ bool allowInsecureConnections ,
367+ bool expectSuccess ,
368+ ( string id , string version ) ? expectedInstalled )
369+ {
370+ // Arrange
371+ using var context = new SimpleTestPathContext ( ) ;
372+ string srcADirectory = Path . Combine ( context . PackageSource , "SourceA" ) ;
373+ string srcBDirectory = Path . Combine ( context . PackageSource , "SourceB" ) ;
374+
375+ using var serverA = new FileSystemBackedV3MockServer ( srcADirectory ) ;
376+ using var serverB = new FileSystemBackedV3MockServer ( srcBDirectory ) ;
377+
378+ foreach ( var ( id , ver ) in sourceAPackages )
379+ {
380+ await SimpleTestPackageUtility . CreateFullPackageAsync ( srcADirectory , id , ver ) ;
381+ }
382+
383+ foreach ( var ( id , ver ) in sourceBPackages )
384+ {
385+ await SimpleTestPackageUtility . CreateFullPackageAsync ( srcBDirectory , id , ver ) ;
386+ }
387+
388+ serverA . Start ( ) ;
389+ serverB . Start ( ) ;
390+
391+ // sources
392+ context . Settings . AddSource ( "A" , serverA . ServiceIndexUri ) ;
393+ context . Settings . AddSource ( "B" , serverB . ServiceIndexUri ) ;
394+
395+ // mapping
396+ foreach ( var ( src , pattern ) in sourceMappings )
397+ {
398+ context . Settings . AddPackageSourceMapping ( src , pattern ) ;
399+ }
400+
401+ var settings = Settings . LoadSettingsGivenConfigPaths ( [ context . Settings . ConfigPath ] ) ;
402+
403+ var packageSources = new List < PackageSource >
404+ {
405+ new ( serverA . ServiceIndexUri , "A" ) ,
406+ new ( serverB . ServiceIndexUri , "B" )
407+ } ;
408+
409+ // args
410+ var args = new PackageDownloadArgs
411+ {
412+ Packages =
413+ [
414+ new PackageWithNuGetVersion
415+ {
416+ Id = downloadId ,
417+ NuGetVersion = downloadVersion is null ? null : NuGetVersion . Parse ( downloadVersion )
418+ }
419+ ] ,
420+ OutputDirectory = context . WorkingDirectory ,
421+ AllowInsecureConnections = allowInsecureConnections ,
422+ Sources = sourcesArgs == null ? [ ] : sourcesArgs . ToList ( )
423+ } ;
424+
425+ var logger = new Mock < ILoggerWithColor > ( MockBehavior . Loose ) ;
426+
427+ // Act
428+ var exit = await PackageDownloadRunner . RunAsync (
429+ args ,
430+ logger . Object ,
431+ packageSources ,
432+ settings ,
433+ CancellationToken . None ) ;
434+
435+ serverA . Stop ( ) ;
436+ serverB . Stop ( ) ;
437+
438+ // Assert
439+ if ( expectSuccess )
440+ {
441+ exit . Should ( ) . Be ( PackageDownloadRunner . ExitCodeSuccess ) ;
442+ expectedInstalled . Should ( ) . NotBeNull ( ) ;
443+
444+ var ( expId , expVer ) = expectedInstalled ! . Value ;
445+ var installDir = Path . Combine ( context . WorkingDirectory , expId . ToLowerInvariant ( ) , expVer ) ;
446+ Directory . Exists ( installDir ) . Should ( ) . BeTrue ( ) ;
447+ File . Exists ( Path . Combine ( installDir , $ "{ expId . ToLowerInvariant ( ) } .{ expVer } .nupkg") ) . Should ( ) . BeTrue ( ) ;
448+ }
449+ else
450+ {
451+ exit . Should ( ) . Be ( PackageDownloadRunner . ExitCodeError ) ;
452+ }
453+ }
283454}
0 commit comments