diff --git a/services/chocolatey/chocolatey.service.js b/services/chocolatey/chocolatey.service.js index 495c494a6b..c03f3d1a7c 100644 --- a/services/chocolatey/chocolatey.service.js +++ b/services/chocolatey/chocolatey.service.js @@ -6,6 +6,7 @@ module.exports = createServiceFamily({ defaultLabel: 'chocolatey', serviceBaseUrl: 'chocolatey', apiBaseUrl: 'https://www.chocolatey.org/api/v2', + odataFormat: 'json', title: 'Chocolatey', examplePackageName: 'git', exampleVersion: '2.19.2', diff --git a/services/chocolatey/chocolatey.tester.js b/services/chocolatey/chocolatey.tester.js index 693fba22a8..981924470d 100644 --- a/services/chocolatey/chocolatey.tester.js +++ b/services/chocolatey/chocolatey.tester.js @@ -6,11 +6,6 @@ const { isVPlusDottedVersionNClauses, isVPlusDottedVersionNClausesWithOptionalSuffix, } = require('../test-validators') -const { - nuGetV2VersionJsonWithDash, - nuGetV2VersionJsonFirstCharZero, - nuGetV2VersionJsonFirstCharNotZero, -} = require('../nuget-fixtures') const { ServiceTester } = require('../tester') const t = (module.exports = new ServiceTester({ @@ -44,51 +39,6 @@ t.create('version (valid)') }) ) -t.create('version (mocked, yellow badge)') - .get('/v/scriptcs.json?style=_shields_test') - .intercept(nock => - nock('https://www.chocolatey.org') - .get( - '/api/v2/Packages()?%24filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true' - ) - .reply(200, nuGetV2VersionJsonWithDash) - ) - .expectJSON({ - name: 'chocolatey', - value: 'v1.2-beta', - color: 'yellow', - }) - -t.create('version (mocked, orange badge)') - .get('/v/scriptcs.json?style=_shields_test') - .intercept(nock => - nock('https://www.chocolatey.org') - .get( - '/api/v2/Packages()?%24filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true' - ) - .reply(200, nuGetV2VersionJsonFirstCharZero) - ) - .expectJSON({ - name: 'chocolatey', - value: 'v0.35', - color: 'orange', - }) - -t.create('version (mocked, blue badge)') - .get('/v/scriptcs.json?style=_shields_test') - .intercept(nock => - nock('https://www.chocolatey.org') - .get( - '/api/v2/Packages()?%24filter=Id%20eq%20%27scriptcs%27%20and%20IsLatestVersion%20eq%20true' - ) - .reply(200, nuGetV2VersionJsonFirstCharNotZero) - ) - .expectJSON({ - name: 'chocolatey', - value: 'v1.2.7', - color: 'blue', - }) - t.create('version (not found)') .get('/v/not-a-real-package.json') .expectJSON({ name: 'chocolatey', value: 'not found' }) @@ -104,51 +54,6 @@ t.create('version (pre) (valid)') }) ) -t.create('version (pre) (mocked, yellow badge)') - .get('/vpre/scriptcs.json?style=_shields_test') - .intercept(nock => - nock('https://www.chocolatey.org') - .get( - '/api/v2/Packages()?%24filter=Id%20eq%20%27scriptcs%27%20and%20IsAbsoluteLatestVersion%20eq%20true' - ) - .reply(200, nuGetV2VersionJsonWithDash) - ) - .expectJSON({ - name: 'chocolatey', - value: 'v1.2-beta', - color: 'yellow', - }) - -t.create('version (pre) (mocked, orange badge)') - .get('/vpre/scriptcs.json?style=_shields_test') - .intercept(nock => - nock('https://www.chocolatey.org') - .get( - '/api/v2/Packages()?%24filter=Id%20eq%20%27scriptcs%27%20and%20IsAbsoluteLatestVersion%20eq%20true' - ) - .reply(200, nuGetV2VersionJsonFirstCharZero) - ) - .expectJSON({ - name: 'chocolatey', - value: 'v0.35', - color: 'orange', - }) - -t.create('version (pre) (mocked, blue badge)') - .get('/vpre/scriptcs.json?style=_shields_test') - .intercept(nock => - nock('https://www.chocolatey.org') - .get( - '/api/v2/Packages()?%24filter=Id%20eq%20%27scriptcs%27%20and%20IsAbsoluteLatestVersion%20eq%20true' - ) - .reply(200, nuGetV2VersionJsonFirstCharNotZero) - ) - .expectJSON({ - name: 'chocolatey', - value: 'v1.2.7', - color: 'blue', - }) - t.create('version (pre) (not found)') .get('/vpre/not-a-real-package.json') .expectJSON({ name: 'chocolatey', value: 'not found' }) diff --git a/services/nuget-fixtures.js b/services/nuget-fixtures.js index d5842ea5c7..ece84319c9 100644 --- a/services/nuget-fixtures.js +++ b/services/nuget-fixtures.js @@ -9,24 +9,6 @@ const queryIndex = JSON.stringify({ ], }) -const nuGetV2VersionJsonWithDash = JSON.stringify({ - d: { - results: [ - { NormalizedVersion: '1.2-beta', Version: 'xxx', DownloadCount: 0 }, - ], - }, -}) -const nuGetV2VersionJsonFirstCharZero = JSON.stringify({ - d: { - results: [{ NormalizedVersion: '0.35', Version: 'xxx', DownloadCount: 0 }], - }, -}) -const nuGetV2VersionJsonFirstCharNotZero = JSON.stringify({ - d: { - results: [{ NormalizedVersion: '1.2.7', Version: 'xxx', DownloadCount: 0 }], - }, -}) - const nuGetV3VersionJsonWithDash = JSON.stringify({ data: [ { @@ -54,9 +36,6 @@ const nuGetV3VersionJsonFirstCharNotZero = JSON.stringify({ module.exports = { queryIndex, - nuGetV2VersionJsonWithDash, - nuGetV2VersionJsonFirstCharZero, - nuGetV2VersionJsonFirstCharNotZero, nuGetV3VersionJsonWithDash, nuGetV3VersionJsonFirstCharZero, nuGetV3VersionJsonFirstCharNotZero, diff --git a/services/nuget/nuget-helpers.js b/services/nuget/nuget-helpers.js index 59731824bf..8fefd7291c 100644 --- a/services/nuget/nuget-helpers.js +++ b/services/nuget/nuget-helpers.js @@ -29,7 +29,21 @@ function renderDownloadBadge({ downloads }) { } } +function odataToObject(odata) { + if (odata === undefined) { + return undefined + } + + const result = {} + Object.entries(odata['m:properties']).forEach(([key, value]) => { + const newKey = key.replace(/^d:/, '') + result[newKey] = value + }) + return result +} + module.exports = { renderVersionBadge, renderDownloadBadge, + odataToObject, } diff --git a/services/nuget/nuget-helpers.spec.js b/services/nuget/nuget-helpers.spec.js new file mode 100644 index 0000000000..0026e6b51d --- /dev/null +++ b/services/nuget/nuget-helpers.spec.js @@ -0,0 +1,31 @@ +'use strict' + +const { renderVersionBadge, odataToObject } = require('./nuget-helpers') +const { test, given } = require('sazerac') + +describe('NuGet helpers', function() { + test(renderVersionBadge, () => { + given({ version: '1.2-beta' }).expect({ + label: undefined, + message: 'v1.2-beta', + color: 'yellow', + }) + given({ version: '0.35' }).expect({ + label: undefined, + message: 'v0.35', + color: 'orange', + }) + given({ version: '1.2.7' }).expect({ + label: undefined, + message: 'v1.2.7', + color: 'blue', + }) + }) + + test(odataToObject, () => { + given({ 'm:properties': { 'd:Version': '1.2.3' } }).expect({ + Version: '1.2.3', + }) + given(undefined).expect(undefined) + }) +}) diff --git a/services/nuget/nuget-v2-service-family.js b/services/nuget/nuget-v2-service-family.js index 523837e0af..1d7704c193 100644 --- a/services/nuget/nuget-v2-service-family.js +++ b/services/nuget/nuget-v2-service-family.js @@ -1,9 +1,13 @@ 'use strict' const Joi = require('joi') -const { BaseJsonService, NotFound } = require('..') +const { BaseJsonService, BaseXmlService, NotFound } = require('..') const { nonNegativeInteger } = require('../validators') -const { renderVersionBadge, renderDownloadBadge } = require('./nuget-helpers') +const { + renderVersionBadge, + renderDownloadBadge, + odataToObject, +} = require('./nuget-helpers') function createFilter({ packageName, includePrereleases }) { const releaseTypeFilter = includePrereleases @@ -12,7 +16,7 @@ function createFilter({ packageName, includePrereleases }) { return `Id eq '${packageName}' and ${releaseTypeFilter}` } -const schema = Joi.object({ +const jsonSchema = Joi.object({ d: Joi.object({ results: Joi.array() .items( @@ -27,25 +31,53 @@ const schema = Joi.object({ }).required(), }).required() +const xmlSchema = Joi.object({ + feed: Joi.object({ + entry: Joi.object({ + 'm:properties': Joi.object({ + 'd:Version': Joi.string(), + 'd:NormalizedVersion': Joi.string(), + 'd:DownloadCount': nonNegativeInteger, + 'd:Tags': Joi.string(), + }), + }), + }).required(), +}).required() + async function fetch( serviceInstance, - { baseUrl, packageName, includePrereleases = false } + { odataFormat, baseUrl, packageName, includePrereleases = false } ) { - const data = await serviceInstance._requestJson({ - schema, - url: `${baseUrl}/Packages()`, - options: { - headers: { Accept: 'application/atom+json,application/json' }, - qs: { $filter: createFilter({ packageName, includePrereleases }) }, - }, - }) + const url = `${baseUrl}/Packages()` + const qs = { $filter: createFilter({ packageName, includePrereleases }) } - const packageData = data.d.results[0] + let packageData + if (odataFormat === 'xml') { + const data = await serviceInstance._requestXml({ + schema: xmlSchema, + url, + options: { qs }, + }) + packageData = odataToObject(data.feed.entry) + } else if (odataFormat === 'json') { + const data = await serviceInstance._requestJson({ + schema: jsonSchema, + url, + options: { + headers: { Accept: 'application/atom+json,application/json' }, + qs, + }, + }) + packageData = data.d.results[0] + } else { + throw Error(`Unsupported Atom OData format: ${odataFormat}`) + } if (packageData) { return packageData } else if (!includePrereleases) { return fetch(serviceInstance, { + odataFormat, baseUrl, packageName, includePrereleases: true, @@ -67,13 +99,23 @@ function createServiceFamily({ defaultLabel, serviceBaseUrl, apiBaseUrl, + odataFormat, title, examplePackageName, exampleVersion, examplePrereleaseVersion, exampleDownloadCount, }) { - class NugetVersionService extends BaseJsonService { + let Base + if (odataFormat === 'xml') { + Base = BaseXmlService + } else if (odataFormat === 'json') { + Base = BaseJsonService + } else { + throw Error(`Unsupported Atom OData format: ${odataFormat}`) + } + + class NugetVersionService extends Base { static get category() { return 'version' } @@ -116,6 +158,7 @@ function createServiceFamily({ async handle({ which, packageName }) { const packageData = await fetch(this, { + odataFormat, baseUrl: apiBaseUrl, packageName, includePrereleases: which === 'vpre', @@ -125,7 +168,7 @@ function createServiceFamily({ } } - class NugetDownloadService extends BaseJsonService { + class NugetDownloadService extends Base { static get category() { return 'downloads' } @@ -155,6 +198,7 @@ function createServiceFamily({ async handle({ packageName }) { const packageData = await fetch(this, { + odataFormat, baseUrl: apiBaseUrl, packageName, }) @@ -168,5 +212,6 @@ function createServiceFamily({ module.exports = { createFilter, + fetch, createServiceFamily, } diff --git a/services/powershellgallery/powershellgallery.service.js b/services/powershellgallery/powershellgallery.service.js index 2b3ba59409..d053bd6bf7 100644 --- a/services/powershellgallery/powershellgallery.service.js +++ b/services/powershellgallery/powershellgallery.service.js @@ -1,142 +1,31 @@ 'use strict' -const Joi = require('joi') -const { createFilter } = require('../nuget/nuget-v2-service-family') const { - renderVersionBadge, - renderDownloadBadge, -} = require('../nuget/nuget-helpers') -const { BaseXmlService, NotFound } = require('..') -const { nonNegativeInteger } = require('../validators') + fetch, + createServiceFamily, +} = require('../nuget/nuget-v2-service-family') +const { BaseXmlService } = require('..') const WINDOWS_TAG_NAME = 'windows' const MACOS_TAG_NAME = 'macos' const LINUX_TAG_NAME = 'linux' -const schema = Joi.object({ - feed: Joi.object({ - entry: Joi.object({ - 'm:properties': Joi.object({ - 'd:Version': Joi.string(), - 'd:NormalizedVersion': Joi.string(), - 'd:DownloadCount': nonNegativeInteger, - 'd:Tags': Joi.string(), - }), - }), - }).required(), -}).required() +const apiBaseUrl = 'https://www.powershellgallery.com/api/v2' -async function fetch( - serviceInstance, - { packageName, includePrereleases = false } -) { - const data = await serviceInstance._requestXml({ - schema, - url: `https://www.powershellgallery.com/api/v2/Search()`, - options: { - qs: { $filter: createFilter({ packageName, includePrereleases }) }, - }, - }) - - const packageData = - 'entry' in data.feed ? data.feed.entry['m:properties'] : undefined - - if (packageData) { - return packageData - } else if (!includePrereleases) { - return fetch(serviceInstance, { - packageName, - includePrereleases: true, - }) - } else { - throw new NotFound() - } -} - -class PowershellGalleryVersion extends BaseXmlService { - static get category() { - return 'version' - } - - static get route() { - return { - base: 'powershellgallery', - pattern: ':which(v|vpre)/:packageName', - } - } - - static get examples() { - return [ - { - title: 'PowerShell Gallery', - pattern: 'v/:packageName', - namedParams: { packageName: 'Azure.Storage' }, - staticPreview: this.render({ version: '4.4.0' }), - }, - { - title: 'PowerShell Gallery (with prereleases)', - pattern: 'vpre/:packageName', - namedParams: { packageName: 'Azure.Storage' }, - staticPreview: this.render({ version: '4.4.1-preview' }), - }, - ] - } - - static get defaultBadgeData() { - return { - label: 'powershell gallery', - } - } - - static render(props) { - return renderVersionBadge(props) - } - - async handle({ which, packageName }) { - const packageData = await fetch(this, { - packageName, - includePrereleases: which === 'vpre', - }) - const version = - packageData['d:NormalizedVersion'] || packageData['d:Version'] - return this.constructor.render({ version }) - } -} - -class PowershellGalleryDownloads extends BaseXmlService { - static get category() { - return 'downloads' - } - - static get route() { - return { - base: 'powershellgallery/dt', - pattern: ':packageName', - } - } - - static get examples() { - return [ - { - title: 'PowerShell Gallery', - namedParams: { packageName: 'Azure.Storage' }, - staticPreview: this.render({ downloads: 1.2e7 }), - }, - ] - } - - static render(props) { - return renderDownloadBadge(props) - } - - async handle({ packageName }) { - const packageData = await fetch(this, { - packageName, - }) - const { 'd:DownloadCount': downloads } = packageData - return this.constructor.render({ downloads }) - } -} +const { + NugetVersionService: PowershellGalleryVersion, + NugetDownloadService: PowershellGalleryDownloads, +} = createServiceFamily({ + defaultLabel: 'powershell gallery', + serviceBaseUrl: 'powershellgallery', + apiBaseUrl, + odataFormat: 'xml', + title: 'PowerShell Gallery', + examplePackageName: 'Azure.Storage', + exampleVersion: '4.4.0', + examplePrereleaseVersion: '4.4.1-preview', + exampleDownloadCount: 1.2e7, +}) class PowershellGalleryPlatformSupport extends BaseXmlService { static get category() { @@ -175,10 +64,11 @@ class PowershellGalleryPlatformSupport extends BaseXmlService { } async handle({ packageName }) { - const packageData = await fetch(this, { + const { Tags: tagStr } = await fetch(this, { + baseUrl: apiBaseUrl, + odataFormat: 'xml', packageName, }) - const { 'd:Tags': tagStr } = packageData const platforms = new Set() const tagArr = tagStr.split(' ') diff --git a/services/resharper/resharper.service.js b/services/resharper/resharper.service.js index 0bd3f2117d..86fb7a96f0 100644 --- a/services/resharper/resharper.service.js +++ b/services/resharper/resharper.service.js @@ -6,6 +6,7 @@ module.exports = createServiceFamily({ defaultLabel: 'resharper', serviceBaseUrl: 'resharper', apiBaseUrl: 'https://resharper-plugins.jetbrains.com/api/v2', + odataFormat: 'xml', title: 'JetBrains ReSharper plugins', examplePackageName: 'StyleCop.StyleCop', exampleVersion: '2017.2.0', diff --git a/services/resharper/resharper.tester.js b/services/resharper/resharper.tester.js index 2ff327a186..9f0d446187 100644 --- a/services/resharper/resharper.tester.js +++ b/services/resharper/resharper.tester.js @@ -7,14 +7,11 @@ const { isVPlusDottedVersionNClauses, isVPlusDottedVersionNClausesWithOptionalSuffix, } = require('../test-validators') -const { - nuGetV2VersionJsonWithDash, - nuGetV2VersionJsonFirstCharZero, - nuGetV2VersionJsonFirstCharNotZero, -} = require('../nuget-fixtures') -const t = new ServiceTester({ id: 'resharper', title: 'ReSharper' }) -module.exports = t +const t = (module.exports = new ServiceTester({ + id: 'resharper', + title: 'ReSharper', +})) // downloads @@ -42,51 +39,6 @@ t.create('version (valid)') }) ) -t.create('version (mocked, yellow badge)') - .get('/v/ReSharper.Nuke.json?style=_shields_test') - .intercept(nock => - nock('https://resharper-plugins.jetbrains.com') - .get( - '/api/v2/Packages()?%24filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true' - ) - .reply(200, nuGetV2VersionJsonWithDash) - ) - .expectJSON({ - name: 'resharper', - value: 'v1.2-beta', - color: 'yellow', - }) - -t.create('version (mocked, orange badge)') - .get('/v/ReSharper.Nuke.json?style=_shields_test') - .intercept(nock => - nock('https://resharper-plugins.jetbrains.com') - .get( - '/api/v2/Packages()?%24filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true' - ) - .reply(200, nuGetV2VersionJsonFirstCharZero) - ) - .expectJSON({ - name: 'resharper', - value: 'v0.35', - color: 'orange', - }) - -t.create('version (mocked, blue badge)') - .get('/v/ReSharper.Nuke.json?style=_shields_test') - .intercept(nock => - nock('https://resharper-plugins.jetbrains.com') - .get( - '/api/v2/Packages()?%24filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsLatestVersion%20eq%20true' - ) - .reply(200, nuGetV2VersionJsonFirstCharNotZero) - ) - .expectJSON({ - name: 'resharper', - value: 'v1.2.7', - color: 'blue', - }) - t.create('version (not found)') .get('/v/not-a-real-package.json') .expectJSON({ name: 'resharper', value: 'not found' }) @@ -102,51 +54,6 @@ t.create('version (pre) (valid)') }) ) -t.create('version (pre) (mocked, yellow badge)') - .get('/vpre/ReSharper.Nuke.json?style=_shields_test') - .intercept(nock => - nock('https://resharper-plugins.jetbrains.com') - .get( - '/api/v2/Packages()?%24filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsAbsoluteLatestVersion%20eq%20true' - ) - .reply(200, nuGetV2VersionJsonWithDash) - ) - .expectJSON({ - name: 'resharper', - value: 'v1.2-beta', - color: 'yellow', - }) - -t.create('version (pre) (mocked, orange badge)') - .get('/vpre/ReSharper.Nuke.json?style=_shields_test') - .intercept(nock => - nock('https://resharper-plugins.jetbrains.com') - .get( - '/api/v2/Packages()?%24filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsAbsoluteLatestVersion%20eq%20true' - ) - .reply(200, nuGetV2VersionJsonFirstCharZero) - ) - .expectJSON({ - name: 'resharper', - value: 'v0.35', - color: 'orange', - }) - -t.create('version (pre) (mocked, blue badge)') - .get('/vpre/ReSharper.Nuke.json?style=_shields_test') - .intercept(nock => - nock('https://resharper-plugins.jetbrains.com') - .get( - '/api/v2/Packages()?%24filter=Id%20eq%20%27ReSharper.Nuke%27%20and%20IsAbsoluteLatestVersion%20eq%20true' - ) - .reply(200, nuGetV2VersionJsonFirstCharNotZero) - ) - .expectJSON({ - name: 'resharper', - value: 'v1.2.7', - color: 'blue', - }) - t.create('version (pre) (not found)') .get('/vpre/not-a-real-package.json') .expectJSON({ name: 'resharper', value: 'not found' })