From 194dbc93ef7815df42097987b265e426d007fd89 Mon Sep 17 00:00:00 2001
From: Florian Greinacher <fgreinacher@users.noreply.github.com>
Date: Mon, 29 Apr 2019 22:16:47 +0200
Subject: [PATCH] fix(NuGet): handling of paginated package versions (#3613)

---
 lib/datasource/nuget/v2.js                    | 65 +++++++++++--------
 .../__snapshots__/nuget.spec.js.snap          | 14 ++++
 test/datasource/nuget.spec.js                 | 24 +++++++
 .../nuget/_fixtures/nunitV2_paginated_1.xml   | 12 ++++
 .../nuget/_fixtures/nunitV2_paginated_2.xml   | 10 +++
 5 files changed, 98 insertions(+), 27 deletions(-)
 create mode 100644 test/datasource/nuget/_fixtures/nunitV2_paginated_1.xml
 create mode 100644 test/datasource/nuget/_fixtures/nunitV2_paginated_2.xml

diff --git a/lib/datasource/nuget/v2.js b/lib/datasource/nuget/v2.js
index 5580462410..7021db5f7d 100644
--- a/lib/datasource/nuget/v2.js
+++ b/lib/datasource/nuget/v2.js
@@ -7,42 +7,53 @@ module.exports = {
 };
 
 async function getPkgReleases(feedUrl, pkgName) {
-  const pkgUrlList = `${feedUrl}/FindPackagesById()?id=%27${pkgName}%27&$select=Version,IsLatestVersion,ProjectUrl`;
   const dep = {
     pkgName,
   };
   try {
-    const pkgVersionsListRaw = await got(pkgUrlList, { platform: 'nuget' });
-    if (pkgVersionsListRaw.statusCode !== 200) {
-      logger.debug(
-        { dependency: pkgName, pkgVersionsListRaw },
-        `nuget registry failure: status code != 200`
-      );
-      return null;
-    }
-    const pkgInfoList = new XmlDocument(
-      pkgVersionsListRaw.body
-    ).children.filter(node => node.name === 'entry');
-
     dep.releases = [];
 
-    for (const pkgInfo of pkgInfoList || []) {
-      const pkgVersion = getPkgProp(pkgInfo, 'Version');
-      dep.releases.push({
-        version: pkgVersion,
-      });
-      try {
-        const pkgIsLatestVersion = getPkgProp(pkgInfo, 'IsLatestVersion');
-        if (pkgIsLatestVersion === 'true') {
-          dep.sourceUrl = parse(getPkgProp(pkgInfo, 'ProjectUrl'));
-        }
-      } catch (err) /* istanbul ignore next */ {
+    let pkgUrlList = `${feedUrl}/FindPackagesById()?id=%27${pkgName}%27&$select=Version,IsLatestVersion,ProjectUrl`;
+    do {
+      const pkgVersionsListRaw = await got(pkgUrlList, { platform: 'nuget' });
+      if (pkgVersionsListRaw.statusCode !== 200) {
         logger.debug(
-          { err, pkgName, feedUrl },
-          `nuget registry failure: can't parse pkg info for project url`
+          { dependency: pkgName, pkgVersionsListRaw },
+          `nuget registry failure: status code != 200`
         );
+        return null;
+      }
+
+      const pkgVersionsListDoc = new XmlDocument(pkgVersionsListRaw.body);
+
+      const pkgInfoList = pkgVersionsListDoc.children.filter(
+        node => node.name === 'entry'
+      );
+
+      for (const pkgInfo of pkgInfoList || []) {
+        const pkgVersion = getPkgProp(pkgInfo, 'Version');
+        dep.releases.push({
+          version: pkgVersion,
+        });
+        try {
+          const pkgIsLatestVersion = getPkgProp(pkgInfo, 'IsLatestVersion');
+          if (pkgIsLatestVersion === 'true') {
+            dep.sourceUrl = parse(getPkgProp(pkgInfo, 'ProjectUrl'));
+          }
+        } catch (err) /* istanbul ignore next */ {
+          logger.debug(
+            { err, pkgName, feedUrl },
+            `nuget registry failure: can't parse pkg info for project url`
+          );
+        }
       }
-    }
+
+      const nextPkgUrlListLink = pkgVersionsListDoc.children.find(
+        node => node.name === 'link' && node.attr.rel === 'next'
+      );
+
+      pkgUrlList = nextPkgUrlListLink ? nextPkgUrlListLink.attr.href : null;
+    } while (pkgUrlList !== null);
 
     return dep;
   } catch (err) {
diff --git a/test/datasource/__snapshots__/nuget.spec.js.snap b/test/datasource/__snapshots__/nuget.spec.js.snap
index 83950cda1a..d69907b1dd 100644
--- a/test/datasource/__snapshots__/nuget.spec.js.snap
+++ b/test/datasource/__snapshots__/nuget.spec.js.snap
@@ -1,5 +1,19 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`datasource/nuget getPkgReleases handles paginated results (v2) 1`] = `
+Object {
+  "pkgName": "nunit",
+  "releases": Array [
+    Object {
+      "version": "1.0.0",
+    },
+    Object {
+      "version": "2.0.0",
+    },
+  ],
+}
+`;
+
 exports[`datasource/nuget getPkgReleases processes real data (v2) 1`] = `
 Object {
   "pkgName": "nunit",
diff --git a/test/datasource/nuget.spec.js b/test/datasource/nuget.spec.js
index 0ef9a60c85..f50944eee8 100644
--- a/test/datasource/nuget.spec.js
+++ b/test/datasource/nuget.spec.js
@@ -27,6 +27,15 @@ const pkgListV2WithoutProjectUrl = fs.readFileSync(
   'utf8'
 );
 
+const pkgListV2Page1of2 = fs.readFileSync(
+  'test/datasource/nuget/_fixtures/nunitV2_paginated_1.xml',
+  'utf8'
+);
+const pkgListV2Page2of2 = fs.readFileSync(
+  'test/datasource/nuget/_fixtures/nunitV2_paginated_2.xml',
+  'utf8'
+);
+
 const nugetIndexV3 = fs.readFileSync(
   'test/datasource/nuget/_fixtures/indexV3.json',
   'utf8'
@@ -323,5 +332,20 @@ describe('datasource/nuget', () => {
       expect(res).toMatchSnapshot();
       expect(res.sourceUrl).not.toBeDefined();
     });
+    it('handles paginated results (v2)', async () => {
+      got.mockReturnValueOnce({
+        body: pkgListV2Page1of2,
+        statusCode: 200,
+      });
+      got.mockReturnValueOnce({
+        body: pkgListV2Page2of2,
+        statusCode: 200,
+      });
+      const res = await datasource.getPkgReleases({
+        ...configV2,
+      });
+      expect(res).not.toBeNull();
+      expect(res).toMatchSnapshot();
+    });
   });
 });
diff --git a/test/datasource/nuget/_fixtures/nunitV2_paginated_1.xml b/test/datasource/nuget/_fixtures/nunitV2_paginated_1.xml
new file mode 100644
index 0000000000..f791385720
--- /dev/null
+++ b/test/datasource/nuget/_fixtures/nunitV2_paginated_1.xml
@@ -0,0 +1,12 @@
+
+<feed xml:base="https://www.nuget.org/api/v2" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml">
+  <id>http://schemas.datacontract.org/2004/07/</id>
+  <link rel="self" href="https://www.nuget.org/api/v2/Packages"/>
+  <link rel="next" href="https://example.org"/>
+  <entry>
+    <m:properties>
+      <d:Version>1.0.0</d:Version>
+      <d:IsLatestVersion>false</d:IsLatestVersion>
+    </m:properties>
+  </entry>
+</feed>
\ No newline at end of file
diff --git a/test/datasource/nuget/_fixtures/nunitV2_paginated_2.xml b/test/datasource/nuget/_fixtures/nunitV2_paginated_2.xml
new file mode 100644
index 0000000000..c507e9c559
--- /dev/null
+++ b/test/datasource/nuget/_fixtures/nunitV2_paginated_2.xml
@@ -0,0 +1,10 @@
+<feed xml:base="https://www.nuget.org/api/v2" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns:georss="http://www.georss.org/georss" xmlns:gml="http://www.opengis.net/gml">
+  <id>http://schemas.datacontract.org/2004/07/</id>
+  <link rel="self" href="https://www.nuget.org/api/v2/Packages"/>
+  <entry>
+    <m:properties>
+      <d:Version>2.0.0</d:Version>
+      <d:IsLatestVersion>true</d:IsLatestVersion>
+    </m:properties>
+  </entry>
+</feed>
-- 
GitLab