Support advisories with several affected packages with 'pub' a… (#4176)

diff --git a/lib/src/source/hosted.dart b/lib/src/source/hosted.dart
index 0439faf..7a2c9de 100644
--- a/lib/src/source/hosted.dart
+++ b/lib/src/source/hosted.dart
@@ -665,55 +665,60 @@
         throw FormatException('affectedPackages must be a list');
       }
 
-      final affectedPkg = affectedPackages.firstWhereOrNull((element) {
-        if (element is! Map) {
+      bool matchesNameAndEcosystem(
+        dynamic affectedPackage,
+        String name,
+        String ecosystem,
+      ) {
+        if (affectedPackage is! Map) {
           throw FormatException('affectedPackage must be a map');
         }
-        final pkg = element['package'];
-        if (pkg is! Map) {
+        final package = affectedPackage['package'];
+        if (package is! Map) {
           throw FormatException('package must be a map');
         }
-        final name = pkg['name'];
-        if (name is! String) {
+        final affectedName = package['name'];
+        if (affectedName is! String) {
           throw FormatException('package name must be a String');
         }
-        if (name == packageName) {
-          final ecosystem = pkg['ecosystem'];
-          if (ecosystem is! String) {
+        if (affectedName == name) {
+          final affectedEcosystem = package['ecosystem'];
+          if (affectedEcosystem is! String) {
             throw FormatException('ecosystem must be a String');
           }
-          return ecosystem.toLowerCase() == 'pub';
+          return affectedEcosystem.toLowerCase() == ecosystem;
         }
         return false;
-      }) as Map?;
-
-      if (affectedPkg == null) {
-        throw FormatException(
-          'Advisory $id does not contain $packageName among its affected packages.',
-        );
-      }
-      final affectedVersions = <String>{};
-      final versions = affectedPkg['versions'];
-      if (versions is! List) {
-        throw FormatException('package versions must be a list');
       }
 
-      for (final v in versions) {
-        if (v is! String) {
-          throw FormatException('package version elements must be a string');
+      for (final affectedPackage in affectedPackages) {
+        if (matchesNameAndEcosystem(affectedPackage, packageName, 'pub')) {
+          final affectedVersions = <String>{};
+          final versions = affectedPackage['versions'];
+          if (versions is! List) {
+            throw FormatException('package versions must be a list');
+          }
+
+          for (final v in versions) {
+            if (v is! String) {
+              throw FormatException(
+                'package version elements must be a string',
+              );
+            }
+            affectedVersions.add(v);
+          }
+
+          advisoriesList.add(
+            Advisory(
+              id: id,
+              affectedVersions: affectedVersions,
+              aliases: aliasIDs,
+              summary: summary,
+              pubDisplayUrl: pubDisplayUrl,
+            ),
+          );
         }
-        affectedVersions.add(v);
       }
-
-      advisoriesList.add(
-        Advisory(
-          id: id,
-          affectedVersions: affectedVersions,
-          aliases: aliasIDs,
-          summary: summary,
-          pubDisplayUrl: pubDisplayUrl,
-        ),
-      );
     }
 
     return advisoriesList;
diff --git a/test/get/hosted/advisory_test.dart b/test/get/hosted/advisory_test.dart
index df50293..0392168 100644
--- a/test/get/hosted/advisory_test.dart
+++ b/test/get/hosted/advisory_test.dart
@@ -190,6 +190,35 @@
     await ctx.run(['get']);
   });
 
+  testWithGolden('show advisory - same package mentioned twice', (ctx) async {
+    final server = await servePackages();
+    server
+      ..serve('foo', '1.0.0')
+      ..serve('foo', '1.2.3')
+      ..serve('baz', '1.0.0');
+
+    await d.dir(appPath, [
+      d.pubspec({
+        'name': 'app',
+        'dependencies': {
+          'foo': '^1.0.0',
+          'baz': '^1.0.0',
+        },
+      }),
+    ]).create();
+
+    server.addAdvisory(
+      advisoryId: '123',
+      displayUrl: 'https://github.com/advisories/123',
+      affectedPackages: [
+        AffectedPackage(name: 'foo', versions: ['1.0.0']),
+        AffectedPackage(name: 'foo', versions: ['1.2.3']),
+      ],
+    );
+
+    await ctx.run(['get']);
+  });
+
   testWithGolden('show id if no display url is present', (ctx) async {
     final server = await servePackages();
     server
diff --git a/test/package_server.dart b/test/package_server.dart
index bbaf2a6..1b142b2 100644
--- a/test/package_server.dart
+++ b/test/package_server.dart
@@ -143,7 +143,7 @@
           jsonEncode({
             'advisoriesUpdated': defaultAdvisoriesUpdated.toIso8601String(),
             'advisories': [
-              for (final advisory in package.advisories)
+              for (final advisory in package.advisories.values)
                 {
                   'id': advisory.id,
                   'summary': 'Example',
@@ -345,8 +345,14 @@
     for (final package in affectedPackages) {
       _packages[package.name]!.advisoriesUpdated =
           advisoriesUpdated ?? defaultAdvisoriesUpdated;
-      _packages[package.name]!.advisories.add(
-            _ServedAdvisory(advisoryId, affectedPackages, aliases, displayUrl),
+      _packages[package.name]!.advisories.putIfAbsent(
+            advisoryId,
+            () => _ServedAdvisory(
+              advisoryId,
+              affectedPackages,
+              aliases,
+              displayUrl,
+            ),
           );
     }
   }
@@ -409,7 +415,7 @@
   bool isDiscontinued = false;
   String? discontinuedReplacementText;
   DateTime? advisoriesUpdated;
-  final advisories = <_ServedAdvisory>[];
+  final advisories = <String, _ServedAdvisory>{};
 }
 
 /// A package that's intended to be served.
diff --git a/test/testdata/goldens/get/hosted/advisory_test/show advisory - same package mentioned twice.txt b/test/testdata/goldens/get/hosted/advisory_test/show advisory - same package mentioned twice.txt
new file mode 100644
index 0000000..d8eda1a
--- /dev/null
+++ b/test/testdata/goldens/get/hosted/advisory_test/show advisory - same package mentioned twice.txt
@@ -0,0 +1,12 @@
+# GENERATED BY: test/get/hosted/advisory_test.dart
+
+## Section 0
+$ pub get
+Resolving dependencies...
+Downloading packages...
++ baz 1.0.0
++ foo 1.2.3 (affected by advisory: [^0])
+Changed 2 dependencies!
+Dependencies are affected by security advisories:
+  [^0]: https://github.com/advisories/123
+