@@ -428,4 +428,136 @@ public void GetAllDependencyGraphs_ReturnedGraphsAreImmutable()
428428 Action attemptedAdd = ( ) => asCollection . Add ( "should't work" ) ;
429429 attemptedAdd . Should ( ) . Throw < NotSupportedException > ( ) ;
430430 }
431+
432+ [ TestMethod ]
433+ public void GetDetectedComponents_BareAndRichAcrossFiles_BareSubsumedIntoRich ( )
434+ {
435+ var recorder1 = this . componentRecorder . CreateSingleFileComponentRecorder ( "package.json" ) ;
436+ var recorder2 = this . componentRecorder . CreateSingleFileComponentRecorder ( "package-lock.json" ) ;
437+
438+ var bareComponent = new DetectedComponent ( new NpmComponent ( "lodash" , "4.17.23" ) ) ;
439+ var richComponent = new DetectedComponent ( new NpmComponent ( "lodash" , "4.17.23" ) { DownloadUrl = new Uri ( "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz" ) } ) ;
440+
441+ recorder1 . RegisterUsage ( bareComponent ) ;
442+ recorder2 . RegisterUsage ( richComponent ) ;
443+
444+ var results = this . componentRecorder . GetDetectedComponents ( ) . ToList ( ) ;
445+
446+ // Only the rich entry should remain
447+ results . Should ( ) . ContainSingle ( ) ;
448+ results [ 0 ] . Component . Id . Should ( ) . Be ( richComponent . Component . Id ) ;
449+ results [ 0 ] . Component . Id . Should ( ) . NotBe ( bareComponent . Component . Id ) ;
450+ }
451+
452+ [ TestMethod ]
453+ public void GetDetectedComponents_BareAndMultipleRichAcrossFiles_BareMergesIntoAllRich ( )
454+ {
455+ var recorder1 = this . componentRecorder . CreateSingleFileComponentRecorder ( "package.json" ) ;
456+ var recorder2 = this . componentRecorder . CreateSingleFileComponentRecorder ( "lockfile-a.json" ) ;
457+ var recorder3 = this . componentRecorder . CreateSingleFileComponentRecorder ( "lockfile-b.json" ) ;
458+
459+ var bareComponent = new DetectedComponent ( new NpmComponent ( "lodash" , "4.17.23" ) )
460+ {
461+ LicensesConcluded = [ "MIT" ] ,
462+ } ;
463+
464+ var richA = new DetectedComponent ( new NpmComponent ( "lodash" , "4.17.23" ) { DownloadUrl = new Uri ( "https://registry-a.example.com/lodash-4.17.23.tgz" ) } ) ;
465+ var richB = new DetectedComponent ( new NpmComponent ( "lodash" , "4.17.23" ) { DownloadUrl = new Uri ( "https://registry-b.example.com/lodash-4.17.23.tgz" ) } ) ;
466+
467+ recorder1 . RegisterUsage ( bareComponent ) ;
468+ recorder2 . RegisterUsage ( richA ) ;
469+ recorder3 . RegisterUsage ( richB ) ;
470+
471+ var results = this . componentRecorder . GetDetectedComponents ( ) . ToList ( ) ;
472+
473+ // Two rich entries, bare dropped
474+ results . Should ( ) . HaveCount ( 2 ) ;
475+ results . Should ( ) . NotContain ( c => c . Component . Id == bareComponent . Component . Id ) ;
476+
477+ // Bare's license merged into both rich entries
478+ results . Should ( ) . OnlyContain ( c => c . LicensesConcluded != null && c . LicensesConcluded . Contains ( "MIT" ) ) ;
479+ }
480+
481+ [ TestMethod ]
482+ public void GetDetectedComponents_TwoRichDifferentUrls_BothKeptSeparate ( )
483+ {
484+ var recorder1 = this . componentRecorder . CreateSingleFileComponentRecorder ( "lockfile-a.json" ) ;
485+ var recorder2 = this . componentRecorder . CreateSingleFileComponentRecorder ( "lockfile-b.json" ) ;
486+
487+ var richA = new DetectedComponent ( new NpmComponent ( "lodash" , "4.17.23" ) { DownloadUrl = new Uri ( "https://registry-a.example.com/lodash-4.17.23.tgz" ) } ) ;
488+ var richB = new DetectedComponent ( new NpmComponent ( "lodash" , "4.17.23" ) { DownloadUrl = new Uri ( "https://registry-b.example.com/lodash-4.17.23.tgz" ) } ) ;
489+
490+ recorder1 . RegisterUsage ( richA ) ;
491+ recorder2 . RegisterUsage ( richB ) ;
492+
493+ var results = this . componentRecorder . GetDetectedComponents ( ) . ToList ( ) ;
494+
495+ results . Should ( ) . HaveCount ( 2 ) ;
496+ results . Should ( ) . Contain ( c => c . Component . Id == richA . Component . Id ) ;
497+ results . Should ( ) . Contain ( c => c . Component . Id == richB . Component . Id ) ;
498+ }
499+
500+ [ TestMethod ]
501+ public void GetDetectedComponents_BareOnlyAcrossFiles_MergesIntoSingleBare ( )
502+ {
503+ var recorder1 = this . componentRecorder . CreateSingleFileComponentRecorder ( "package.json" ) ;
504+ var recorder2 = this . componentRecorder . CreateSingleFileComponentRecorder ( "other-package.json" ) ;
505+
506+ var bare1 = new DetectedComponent ( new NpmComponent ( "lodash" , "4.17.23" ) )
507+ {
508+ LicensesConcluded = [ "MIT" ] ,
509+ } ;
510+
511+ var bare2 = new DetectedComponent ( new NpmComponent ( "lodash" , "4.17.23" ) )
512+ {
513+ LicensesConcluded = [ "Apache-2.0" ] ,
514+ } ;
515+
516+ recorder1 . RegisterUsage ( bare1 ) ;
517+ recorder2 . RegisterUsage ( bare2 ) ;
518+
519+ var results = this . componentRecorder . GetDetectedComponents ( ) . ToList ( ) ;
520+
521+ // Same Id → merged into one
522+ results . Should ( ) . ContainSingle ( ) ;
523+ results [ 0 ] . LicensesConcluded . Should ( ) . Contain ( "MIT" ) ;
524+ results [ 0 ] . LicensesConcluded . Should ( ) . Contain ( "Apache-2.0" ) ;
525+ }
526+
527+ [ TestMethod ]
528+ public void GetDetectedComponents_BareAndRich_MetadataMergedCorrectly ( )
529+ {
530+ var recorder1 = this . componentRecorder . CreateSingleFileComponentRecorder ( "package.json" ) ;
531+ var recorder2 = this . componentRecorder . CreateSingleFileComponentRecorder ( "package-lock.json" ) ;
532+
533+ var bareComponent = new DetectedComponent ( new NpmComponent ( "lodash" , "4.17.23" ) )
534+ {
535+ LicensesConcluded = [ "MIT" ] ,
536+ Suppliers = [ new ActorInfo { Name = "Lodash Team" , Type = "Organization" } ] ,
537+ } ;
538+
539+ var richComponent = new DetectedComponent ( new NpmComponent ( "lodash" , "4.17.23" ) { DownloadUrl = new Uri ( "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz" ) } )
540+ {
541+ LicensesConcluded = [ "Apache-2.0" ] ,
542+ Suppliers = [ new ActorInfo { Name = "Contoso" , Type = "Organization" } ] ,
543+ } ;
544+
545+ recorder1 . RegisterUsage ( bareComponent ) ;
546+ recorder2 . RegisterUsage ( richComponent ) ;
547+
548+ var results = this . componentRecorder . GetDetectedComponents ( ) . ToList ( ) ;
549+
550+ results . Should ( ) . ContainSingle ( ) ;
551+ var result = results [ 0 ] ;
552+
553+ // Licenses from both bare and rich should be merged
554+ result . LicensesConcluded . Should ( ) . HaveCount ( 2 ) ;
555+ result . LicensesConcluded . Should ( ) . Contain ( "Apache-2.0" ) ;
556+ result . LicensesConcluded . Should ( ) . Contain ( "MIT" ) ;
557+
558+ // Suppliers from both should be merged
559+ result . Suppliers . Should ( ) . HaveCount ( 2 ) ;
560+ result . Suppliers . Should ( ) . Contain ( s => s . Name == "Lodash Team" ) ;
561+ result . Suppliers . Should ( ) . Contain ( s => s . Name == "Contoso" ) ;
562+ }
431563}
0 commit comments