From ac15e23739968cc8b9de8a9133e3b071f0638dc8 Mon Sep 17 00:00:00 2001 From: Chris van der Pennen Date: Sun, 15 Oct 2023 18:42:02 +1030 Subject: [PATCH] fix(nuget): Sort api response before picking projectUrl (#23090) Co-authored-by: Rhys Arkins Co-authored-by: Michael Kriese --- .../azure_devops/nunit/nuspec.xml | 31 ++++++++++ .../azure_devops/nunit/v3_registration.json | 56 +++++++++++++++++++ .../__fixtures__/azure_devops/v3_index.json | 14 +++++ lib/modules/datasource/nuget/index.spec.ts | 54 ++++++++++++++++++ lib/modules/datasource/nuget/v3.spec.ts | 19 +++++++ lib/modules/datasource/nuget/v3.ts | 27 ++++++++- 6 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 lib/modules/datasource/nuget/__fixtures__/azure_devops/nunit/nuspec.xml create mode 100644 lib/modules/datasource/nuget/__fixtures__/azure_devops/nunit/v3_registration.json create mode 100644 lib/modules/datasource/nuget/__fixtures__/azure_devops/v3_index.json create mode 100644 lib/modules/datasource/nuget/v3.spec.ts diff --git a/lib/modules/datasource/nuget/__fixtures__/azure_devops/nunit/nuspec.xml b/lib/modules/datasource/nuget/__fixtures__/azure_devops/nunit/nuspec.xml new file mode 100644 index 00000000000000..03af655703e8e1 --- /dev/null +++ b/lib/modules/datasource/nuget/__fixtures__/azure_devops/nunit/nuspec.xml @@ -0,0 +1,31 @@ + + + + NUnit + 3.13.2 + NUnit + Charlie Poole, Rob Prouse + Charlie Poole, Rob Prouse + false + LICENSE.txt + https://aka.ms/deprecateLicenseUrl + icon.png + https://nunit.org/ + https://cdn.rawgit.com/nunit/resources/master/images/icon/nunit_256.png + + NUnit is a unit-testing framework for all .NET languages with a strong TDD focus. + + Copyright (c) 2021 Charlie Poole, Rob Prouse + en-US + nunit test testing tdd framework fluent assert theory plugin addin + + + + + + + + + + + diff --git a/lib/modules/datasource/nuget/__fixtures__/azure_devops/nunit/v3_registration.json b/lib/modules/datasource/nuget/__fixtures__/azure_devops/nunit/v3_registration.json new file mode 100644 index 00000000000000..44996721626000 --- /dev/null +++ b/lib/modules/datasource/nuget/__fixtures__/azure_devops/nunit/v3_registration.json @@ -0,0 +1,56 @@ +{ + "count": 1, + "items": [ + { + "count": 4, + "items": [ + { + "@id": "https://pkgs.dev.azure.com/organisationName/_packaging/2745c5e9-610a-4537-9032-978c66527b51/nuget/v3/registrations2/nunit/3.13.2.json", + "@type": "Package", + "catalogEntry": { + "listed": true, + "projectUrl": "https://nunit.org/", + "published": "2021-12-03T03:20:52Z", + "version": "3.13.2" + }, + "packageContent": "https://pkgs.dev.azure.com/organisationName/_packaging/2745c5e9-610a-4537-9032-978c66527b51/nuget/v3/flat2/nunit/3.13.2/nunit.3.13.2.nupkg" + }, + { + "@id": "https://pkgs.dev.azure.com/organisationName/_packaging/2745c5e9-610a-4537-9032-978c66527b51/nuget/v3/registrations2/nunit/2.7.1.json", + "@type": "Package", + "catalogEntry": { + "listed": true, + "projectUrl": "http://nunitsoftware.com/nunitv2", + "published": "2021-12-03T03:20:52Z", + "version": "2.7.1" + }, + "packageContent": "https://pkgs.dev.azure.com/organisationName/_packaging/2745c5e9-610a-4537-9032-978c66527b51/nuget/v3/flat2/nunit/2.7.1/nunit.2.7.1.nupkg" + }, + { + "@id": "https://pkgs.dev.azure.com/organisationName/_packaging/2745c5e9-610a-4537-9032-978c66527b51/nuget/v3/registrations2/nunit/2.6.5.json", + "@type": "Package", + "catalogEntry": { + "listed": true, + "projectUrl": "http://nunit.org/", + "published": "2021-12-03T03:20:52Z", + "version": "2.6.5" + }, + "packageContent": "https://pkgs.dev.azure.com/organisationName/_packaging/2745c5e9-610a-4537-9032-978c66527b51/nuget/v3/flat2/nunit/2.6.5/nunit.2.6.5.nupkg" + }, + { + "@id": "https://pkgs.dev.azure.com/organisationName/_packaging/2745c5e9-610a-4537-9032-978c66527b51/nuget/v3/registrations2/nunit/2.5.7.10213.json", + "@type": "Package", + "catalogEntry": { + "listed": true, + "projectUrl": "", + "published": "2021-12-03T03:20:52Z", + "version": "2.5.7.10213" + }, + "packageContent": "https://pkgs.dev.azure.com/organisationName/_packaging/2745c5e9-610a-4537-9032-978c66527b51/nuget/v3/flat2/nunit/2.5.7.10213/nunit.2.5.7.10213.nupkg" + } + ], + "lower": "2.5.7.10213", + "upper": "3.13.2" + } + ] +} diff --git a/lib/modules/datasource/nuget/__fixtures__/azure_devops/v3_index.json b/lib/modules/datasource/nuget/__fixtures__/azure_devops/v3_index.json new file mode 100644 index 00000000000000..af67f8fa187823 --- /dev/null +++ b/lib/modules/datasource/nuget/__fixtures__/azure_devops/v3_index.json @@ -0,0 +1,14 @@ +{ + "resources": [ + { + "@id": "https://pkgs.dev.azure.com/organisationName/_packaging/2745c5e9-610a-4537-9032-978c66527b51/nuget/v3/registrations2-semver2/", + "@type": "RegistrationsBaseUrl/3.6.0", + "comment": "This base URL includes SemVer 2.0.0 packages." + }, + { + "@id": "https://pkgs.dev.azure.com/organisationName/_packaging/2745c5e9-610a-4537-9032-978c66527b51/nuget/v3/flat2/", + "@type": "PackageBaseAddress/3.0.0" + } + ], + "version": "3.0.0-beta" +} diff --git a/lib/modules/datasource/nuget/index.spec.ts b/lib/modules/datasource/nuget/index.spec.ts index b0b56b29ec276a..816bbd16bb8942 100644 --- a/lib/modules/datasource/nuget/index.spec.ts +++ b/lib/modules/datasource/nuget/index.spec.ts @@ -94,6 +94,15 @@ const configV3Multiple = { ], }; +const configV3AzureDevOps = { + datasource, + versioning, + packageName: 'nunit', + registryUrls: [ + 'https://pkgs.dev.azure.com/organisationName/_packaging/2745c5e9-610a-4537-9032-978c66527b51/nuget/v3/index.json', + ], +}; + describe('modules/datasource/nuget/index', () => { describe('parseRegistryUrl', () => { it('extracts feed version from registry URL hash (v3)', () => { @@ -374,6 +383,51 @@ describe('modules/datasource/nuget/index', () => { expect(res?.sourceUrl).toBeDefined(); }); + it('processes real data (v3) feed is azure devops', async () => { + httpMock + .scope('https://pkgs.dev.azure.com') + .get( + '/organisationName/_packaging/2745c5e9-610a-4537-9032-978c66527b51/nuget/v3/index.json' + ) + .twice() + .reply(200, Fixtures.get('azure_devops/v3_index.json')) + .get( + '/organisationName/_packaging/2745c5e9-610a-4537-9032-978c66527b51/nuget/v3/registrations2-semver2/nunit/index.json' + ) + .reply(200, Fixtures.get('azure_devops/nunit/v3_registration.json')) + .get( + '/organisationName/_packaging/2745c5e9-610a-4537-9032-978c66527b51/nuget/v3/flat2/nunit/3.13.2/nunit.nuspec' + ) + .reply(200, Fixtures.get('azure_devops/nunit/nuspec.xml')); + const res = await getPkgReleases({ + ...configV3AzureDevOps, + }); + expect(res).toMatchObject({ + homepage: 'https://nunit.org/', + registryUrl: + 'https://pkgs.dev.azure.com/organisationName/_packaging/2745c5e9-610a-4537-9032-978c66527b51/nuget/v3/index.json', + releases: [ + { + releaseTimestamp: '2021-12-03T03:20:52.000Z', + version: '2.5.7.10213', + }, + { + releaseTimestamp: '2021-12-03T03:20:52.000Z', + version: '2.6.5', + }, + { + releaseTimestamp: '2021-12-03T03:20:52.000Z', + version: '2.7.1', + }, + { + releaseTimestamp: '2021-12-03T03:20:52.000Z', + version: '3.13.2', + }, + ], + sourceUrl: 'https://github.com/nunit/nunit', + }); + }); + it('processes real data (v3) for several catalog pages', async () => { const scope = httpMock .scope('https://api.nuget.org') diff --git a/lib/modules/datasource/nuget/v3.spec.ts b/lib/modules/datasource/nuget/v3.spec.ts new file mode 100644 index 00000000000000..61ae82b9838a68 --- /dev/null +++ b/lib/modules/datasource/nuget/v3.spec.ts @@ -0,0 +1,19 @@ +import { sortNugetVersions } from './v3'; + +describe('modules/datasource/nuget/v3', () => { + it.each<{ version: string; other: string; result: number }>` + version | other | result + ${'invalid1'} | ${'invalid2'} | ${0} + ${'invalid'} | ${'1.0.0'} | ${-1} + ${'1.0.0'} | ${'invalid'} | ${1} + ${'1.0.0-rc.1'} | ${'1.0.0'} | ${-1} + ${'1.0.0'} | ${'1.0.0-rc.1'} | ${1} + ${'1.0.0'} | ${'1.0.0'} | ${0} + `( + 'sortNugetVersions("$version", "$other") === $result', + ({ version, other, result }) => { + const res = sortNugetVersions(version, other); + expect(res).toBe(result); + } + ); +}); diff --git a/lib/modules/datasource/nuget/v3.ts b/lib/modules/datasource/nuget/v3.ts index 2c760a9f444aae..eb5e088c11462a 100644 --- a/lib/modules/datasource/nuget/v3.ts +++ b/lib/modules/datasource/nuget/v3.ts @@ -8,6 +8,7 @@ import { Http, HttpError } from '../../../util/http'; import * as p from '../../../util/promises'; import { regEx } from '../../../util/regex'; import { ensureTrailingSlash } from '../../../util/url'; +import { api as versioning } from '../../versioning/nuget'; import type { Release, ReleaseResult } from '../types'; import { massageUrl, removeBuildMeta } from './common'; import type { @@ -110,6 +111,26 @@ async function getCatalogEntry( return items.map(({ catalogEntry }) => catalogEntry); } +/** + * Compare two versions. Return: + * - `1` if `a > b` or `b` is invalid + * - `-1` if `a < b` or `a` is invalid + * - `0` if `a == b` or both `a` and `b` are invalid + */ +export function sortNugetVersions(a: string, b: string): number { + if (versioning.isValid(a)) { + if (versioning.isValid(b)) { + return versioning.sortVersions(a, b); + } else { + return 1; + } + } else if (versioning.isValid(b)) { + return -1; + } else { + return 0; + } +} + export async function getReleases( http: Http, registryUrl: string, @@ -123,7 +144,9 @@ export async function getReleases( const catalogPagesQueue = catalogPages.map( (page) => (): Promise => getCatalogEntry(http, page) ); - const catalogEntries = (await p.all(catalogPagesQueue)).flat(); + const catalogEntries = (await p.all(catalogPagesQueue)) + .flat() + .sort((a, b) => sortNugetVersions(a.version, b.version)); let homepage: string | null = null; let latestStable: string | null = null; @@ -133,7 +156,7 @@ export async function getReleases( if (releaseTimestamp) { release.releaseTimestamp = releaseTimestamp; } - if (semver.valid(version) && !semver.prerelease(version)) { + if (versioning.isValid(version) && versioning.isStable(version)) { latestStable = removeBuildMeta(version); homepage = projectUrl ? massageUrl(projectUrl) : homepage; }