From d88f6a4b0d6eb7125171787989a57b329f9a4ff6 Mon Sep 17 00:00:00 2001
From: Sergei Zharinov <zharinov@users.noreply.github.com>
Date: Fri, 5 Jul 2024 12:37:43 -0300
Subject: [PATCH] refactor: Lookup filtering of unstable releases (#30054)

---
 .../repository/process/lookup/filter.spec.ts  | 78 ++++++++++++-------
 .../repository/process/lookup/filter.ts       | 30 ++++---
 2 files changed, 70 insertions(+), 38 deletions(-)

diff --git a/lib/workers/repository/process/lookup/filter.spec.ts b/lib/workers/repository/process/lookup/filter.spec.ts
index 0d75534e35..5fe0a6a9ac 100644
--- a/lib/workers/repository/process/lookup/filter.spec.ts
+++ b/lib/workers/repository/process/lookup/filter.spec.ts
@@ -1,37 +1,38 @@
 import { partial } from '../../../../../test/util';
+import type { Release } from '../../../../modules/datasource/types';
 import * as allVersioning from '../../../../modules/versioning';
 import { filterVersions } from './filter';
 import type { FilterConfig } from './types';
 
 const versioning = allVersioning.get('semver');
 
-const releases = [
-  {
-    version: '1.0.1',
-    releaseTimestamp: '2021-01-01T00:00:01.000Z',
-  },
-  {
-    version: '1.2.0',
-    releaseTimestamp: '2021-01-03T00:00:00.000Z',
-  },
-  {
-    version: '2.0.0',
-    releaseTimestamp: '2021-01-05T00:00:00.000Z',
-  },
-  {
-    version: '2.1.0',
-    releaseTimestamp: '2021-01-07T00:00:00.000Z',
-  },
-  // for coverage
-  {
-    version: 'invalid.version',
-    releaseTimestamp: '2021-01-07T00:00:00.000Z',
-  },
-];
-
 describe('workers/repository/process/lookup/filter', () => {
   describe('.filterVersions()', () => {
     it('should filter versions allowed by semver syntax when allowedVersions is not valid version, range or pypi syntax', () => {
+      const releases = [
+        {
+          version: '1.0.1',
+          releaseTimestamp: '2021-01-01T00:00:01.000Z',
+        },
+        {
+          version: '1.2.0',
+          releaseTimestamp: '2021-01-03T00:00:00.000Z',
+        },
+        {
+          version: '2.0.0',
+          releaseTimestamp: '2021-01-05T00:00:00.000Z',
+        },
+        {
+          version: '2.1.0',
+          releaseTimestamp: '2021-01-07T00:00:00.000Z',
+        },
+        // for coverage
+        {
+          version: 'invalid.version',
+          releaseTimestamp: '2021-01-07T00:00:00.000Z',
+        },
+      ] satisfies Release[];
+
       const config = partial<FilterConfig>({
         ignoreUnstable: false,
         ignoreDeprecated: false,
@@ -41,9 +42,6 @@ describe('workers/repository/process/lookup/filter', () => {
       const currentVersion = '1.0.0';
       const latestVersion = '2.0.0';
 
-      jest.spyOn(versioning, 'isVersion').mockReturnValue(true);
-      jest.spyOn(versioning, 'isGreaterThan').mockReturnValue(true);
-
       const filteredVersions = filterVersions(
         config,
         currentVersion,
@@ -57,5 +55,31 @@ describe('workers/repository/process/lookup/filter', () => {
         { version: '2.1.0', releaseTimestamp: '2021-01-07T00:00:00.000Z' },
       ]);
     });
+
+    it('allows unstable major upgrades', () => {
+      const nodeVersioning = allVersioning.get('node');
+
+      const releases = [
+        { version: '1.0.0-alpha' },
+        { version: '1.2.3-beta' },
+      ] satisfies Release[];
+
+      const config = partial<FilterConfig>({
+        ignoreUnstable: true,
+        ignoreDeprecated: true,
+      });
+      const currentVersion = '1.0.0-alpha';
+      const latestVersion = '1.2.3-beta';
+
+      const filteredVersions = filterVersions(
+        config,
+        currentVersion,
+        latestVersion,
+        releases,
+        nodeVersioning,
+      );
+
+      expect(filteredVersions).toEqual([{ version: '1.2.3-beta' }]);
+    });
   });
 });
diff --git a/lib/workers/repository/process/lookup/filter.ts b/lib/workers/repository/process/lookup/filter.ts
index dbaed9dcd0..71e85ec3d1 100644
--- a/lib/workers/repository/process/lookup/filter.ts
+++ b/lib/workers/repository/process/lookup/filter.ts
@@ -78,7 +78,11 @@ export function filterVersions(
       );
       filteredReleases = filteredReleases.filter((r) =>
         semver.satisfies(
-          semver.valid(r.version) ? r.version : semver.coerce(r.version)!,
+          semver.valid(r.version)
+            ? r.version
+            : /* istanbul ignore next: not reachable, but it's safer to preserve it */ semver.coerce(
+                r.version,
+              )!,
           allowedVersions,
         ),
       );
@@ -126,24 +130,28 @@ export function filterVersions(
     return filteredReleases.filter((r) => isReleaseStable(r, versioning));
   }
 
-  // if current is unstable then allow unstable in the current major only
-  // Allow unstable only in current major
+  const currentMajor = versioning.getMajor(currentVersion);
+  const currentMinor = versioning.getMinor(currentVersion);
+  const currentPatch = versioning.getPatch(currentVersion);
+
   return filteredReleases.filter((r) => {
     if (isReleaseStable(r, versioning)) {
       return true;
     }
-    if (
-      versioning.getMajor(r.version) !== versioning.getMajor(currentVersion)
-    ) {
+
+    const major = versioning.getMajor(r.version);
+
+    if (major !== currentMajor) {
       return false;
     }
-    // istanbul ignore if: test passes without touching this
+
     if (versioning.allowUnstableMajorUpgrades) {
       return true;
     }
-    return (
-      versioning.getMinor(r.version) === versioning.getMinor(currentVersion) &&
-      versioning.getPatch(r.version) === versioning.getPatch(currentVersion)
-    );
+
+    const minor = versioning.getMinor(r.version);
+    const patch = versioning.getPatch(r.version);
+
+    return minor === currentMinor && patch === currentPatch;
   });
 }
-- 
GitLab