From a039fffe79cdf73dda073c3ba71ffed6ff52dac1 Mon Sep 17 00:00:00 2001 From: Marcin Mielnicki Date: Sat, 10 Nov 2018 16:05:00 +0100 Subject: [PATCH] Refactor [Jetbrains] service; affects [eclipse-marketplace] (#2232) * Refactor Jetbrains * Test that custom parser options are provided * Code refactoring * Code refactoring * url -> route * package-lock.json updated --- lib/all-badge-examples.js | 9 -- package-lock.json | 42 ++++----- services/base-xml.js | 10 +- services/base-xml.spec.js | 29 ++++++ services/hackage/1.json | 1 + services/jetbrains/jetbrains-base.js | 37 ++++++++ .../jetbrains/jetbrains-downloads.service.js | 60 ++++++++++++ .../jetbrains/jetbrains-version.service.js | 57 ++++++++++++ services/jetbrains/jetbrains.service.js | 77 ---------------- services/jetbrains/jetbrains.tester.js | 92 ++++++++++++++++--- 10 files changed, 294 insertions(+), 120 deletions(-) create mode 100644 services/hackage/1.json create mode 100644 services/jetbrains/jetbrains-base.js create mode 100644 services/jetbrains/jetbrains-downloads.service.js create mode 100644 services/jetbrains/jetbrains-version.service.js delete mode 100644 services/jetbrains/jetbrains.service.js diff --git a/lib/all-badge-examples.js b/lib/all-badge-examples.js index 6f85ea358e..6372ca840b 100644 --- a/lib/all-badge-examples.js +++ b/lib/all-badge-examples.js @@ -141,11 +141,6 @@ const allBadgeExamples = [ previewUrl: '/vscode-marketplace/d/ritwickdey.LiveServer.svg', keywords: ['vscode-marketplace'], }, - { - title: 'JetBrains IntelliJ plugins', - previewUrl: '/jetbrains/plugin/d/1347-scala.svg', - keywords: ['jetbrains', 'plugin'], - }, { title: 'JetBrains ReSharper plugins', previewUrl: '/resharper/dt/ReSharper.Nuke.svg', @@ -590,10 +585,6 @@ const allBadgeExamples = [ keywords: ['vscode-marketplace'], }, - { - title: 'JetBrains IntelliJ Plugins', - previewUrl: '/jetbrains/plugin/v/9630-a8translate.svg', - }, { title: 'JetBrains ReSharper Plugins', previewUrl: '/resharper/v/ReSharper.Nuke.svg', diff --git a/package-lock.json b/package-lock.json index 06b7e8a250..6a34f591ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "gh-badges", - "version": "2.0.0-beta1", + "version": "2.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1762,13 +1762,13 @@ }, "babel-plugin-react-require": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-react-require/-/babel-plugin-react-require-3.0.0.tgz", + "resolved": "http://registry.npmjs.org/babel-plugin-react-require/-/babel-plugin-react-require-3.0.0.tgz", "integrity": "sha1-Lk57RJa5OmVKHIAEInbeTk7rIOM=", "dev": true }, "babel-plugin-syntax-jsx": { "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "resolved": "http://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=", "dev": true }, @@ -2143,7 +2143,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { @@ -2202,7 +2202,7 @@ }, "buffer": { "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { @@ -3500,7 +3500,7 @@ }, "d": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "resolved": "http://registry.npmjs.org/d/-/d-1.0.0.tgz", "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "dev": true, "requires": { @@ -4849,7 +4849,7 @@ }, "events": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", "dev": true }, @@ -5184,7 +5184,7 @@ "dependencies": { "core-js": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "resolved": "http://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=", "dev": true }, @@ -5475,7 +5475,7 @@ "dependencies": { "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -6606,7 +6606,7 @@ }, "htmlescape": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", + "resolved": "http://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=", "dev": true }, @@ -7683,7 +7683,7 @@ }, "json5": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "resolved": "http://registry.npmjs.org/json5/-/json5-0.5.1.tgz", "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", "dev": true }, @@ -9916,7 +9916,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -10519,7 +10519,7 @@ }, "convert-source-map": { "version": "1.6.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", "dev": true, "requires": { @@ -10538,7 +10538,7 @@ }, "debug": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "resolved": false, "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { @@ -10659,7 +10659,7 @@ }, "glob": { "version": "7.1.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "requires": { @@ -10796,7 +10796,7 @@ }, "istanbul-lib-report": { "version": "2.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.2.tgz", "integrity": "sha512-rJ8uR3peeIrwAxoDEbK4dJ7cqqtxBisZKCuwkMtMv0xYzaAnsAi3AHrHPAAtNXzG/bcCgZZ3OJVqm1DTi9ap2Q==", "dev": true, "requires": { @@ -10828,7 +10828,7 @@ }, "istanbul-reports": { "version": "2.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.0.1.tgz", "integrity": "sha512-CT0QgMBJqs6NJLF678ZHcquUAZIoBIUNzdJrRJfpkI9OnzG6MkUfHxbJC3ln981dMswC7/B1mfX3LNkhgJxsuw==", "dev": true, "requires": { @@ -11000,7 +11000,7 @@ }, "ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "resolved": false, "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, @@ -11242,7 +11242,7 @@ }, "safe-buffer": { "version": "5.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, @@ -15541,7 +15541,7 @@ "dependencies": { "yargs": { "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", "dev": true, "requires": { @@ -15782,7 +15782,7 @@ "dependencies": { "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { diff --git a/services/base-xml.js b/services/base-xml.js index a4df36821e..f52bfe9eea 100644 --- a/services/base-xml.js +++ b/services/base-xml.js @@ -8,7 +8,13 @@ const trace = require('./trace') const { InvalidResponse } = require('./errors') class BaseXmlService extends BaseService { - async _requestXml({ schema, url, options = {}, errorMessages = {} }) { + async _requestXml({ + schema, + url, + options = {}, + errorMessages = {}, + parserOptions = {}, + }) { const logTrace = (...args) => trace.logTrace('fetch', ...args) const mergedOptions = { ...{ headers: { Accept: 'application/xml, text/xml' } }, @@ -26,7 +32,7 @@ class BaseXmlService extends BaseService { underlyingError: validateResult.err, }) } - const xml = fastXmlParser.parse(buffer) + const xml = fastXmlParser.parse(buffer, parserOptions) logTrace(emojic.dart, 'Response XML (before validation)', xml, { deep: true, }) diff --git a/services/base-xml.spec.js b/services/base-xml.spec.js index f05f13594d..8ba4231ae2 100644 --- a/services/base-xml.spec.js +++ b/services/base-xml.spec.js @@ -101,6 +101,35 @@ describe('BaseXmlService', function() { }) }) + it('parses XML response with custom parser options', async function() { + const customParserOption = { trimValues: false } + class DummyXmlServiceWithParserOption extends DummyXmlService { + async handle() { + const { requiredString } = await this._requestXml({ + schema: dummySchema, + url: 'http://example.com/foo.xml', + parserOptions: customParserOption, + }) + return { message: requiredString } + } + } + const sendAndCacheRequest = async () => ({ + buffer: + 'some-string with trailing whitespace ', + res: { statusCode: 200 }, + }) + const serviceInstance = new DummyXmlServiceWithParserOption( + { sendAndCacheRequest }, + { handleInternalErrors: false } + ) + + const serviceData = await serviceInstance.invokeHandler({}, {}) + + expect(serviceData).to.deep.equal({ + message: 'some-string with trailing whitespace ', + }) + }) + it('handles xml responses which do not match the schema', async function() { const sendAndCacheRequest = async () => ({ buffer: 'some-string', diff --git a/services/hackage/1.json b/services/hackage/1.json new file mode 100644 index 0000000000..e4c010bd3e --- /dev/null +++ b/services/hackage/1.json @@ -0,0 +1 @@ +{ "plugin-repository": "" } \ No newline at end of file diff --git a/services/jetbrains/jetbrains-base.js b/services/jetbrains/jetbrains-base.js new file mode 100644 index 0000000000..9f4871e474 --- /dev/null +++ b/services/jetbrains/jetbrains-base.js @@ -0,0 +1,37 @@ +'use strict' + +const BaseXmlService = require('../base-xml') +const { NotFound } = require('../errors') + +module.exports = class JetbrainsBase extends BaseXmlService { + static buildUrl(base) { + return { + base, + format: '(.+)', + capture: ['pluginId'], + } + } + + static _validate(data, schema) { + if (data['plugin-repository'] === '') { + // Note the 'not found' response from JetBrains Plugins Repository is: + // status code = 200, + // body = + // which is parsed to object = { 'plugin-repository': '' } + throw new NotFound() + } + return super._validate(data, schema) + } + + async fetchPackageData({ pluginId, schema }) { + const parserOptions = { + parseNodeValue: false, + ignoreAttributes: false, + } + return this._requestXml({ + schema, + url: `https://plugins.jetbrains.com/plugins/list?pluginId=${pluginId}`, + parserOptions, + }) + } +} diff --git a/services/jetbrains/jetbrains-downloads.service.js b/services/jetbrains/jetbrains-downloads.service.js new file mode 100644 index 0000000000..8486c46fe8 --- /dev/null +++ b/services/jetbrains/jetbrains-downloads.service.js @@ -0,0 +1,60 @@ +'use strict' + +const Joi = require('joi') +const JetbrainsBase = require('./jetbrains-base') +const { metric } = require('../../lib/text-formatters') +const { + downloadCount: downloadCountColor, +} = require('../../lib/color-formatters') +const { nonNegativeInteger } = require('../validators') + +const schema = Joi.object({ + 'plugin-repository': Joi.object({ + category: Joi.object({ + 'idea-plugin': Joi.array() + .min(1) + .items( + Joi.object({ + '@_downloads': nonNegativeInteger, + }) + ) + .single() + .required(), + }), + }).required(), +}).required() + +module.exports = class JetbrainsDownloads extends JetbrainsBase { + static get category() { + return 'downloads' + } + + static get examples() { + return [ + { + title: 'JetBrains IntelliJ plugins', + exampleUrl: '1347-scala', + urlPattern: ':pluginId', + staticExample: this.render({ downloads: 10200000 }), + }, + ] + } + + static get route() { + return this.buildUrl('jetbrains/plugin/d') + } + + static render({ downloads }) { + return { + message: `${metric(downloads)}`, + color: downloadCountColor(downloads), + } + } + + async handle({ pluginId }) { + const pluginData = await this.fetchPackageData({ pluginId, schema }) + const downloads = + pluginData['plugin-repository'].category['idea-plugin'][0]['@_downloads'] + return this.constructor.render({ downloads }) + } +} diff --git a/services/jetbrains/jetbrains-version.service.js b/services/jetbrains/jetbrains-version.service.js new file mode 100644 index 0000000000..6eb001c728 --- /dev/null +++ b/services/jetbrains/jetbrains-version.service.js @@ -0,0 +1,57 @@ +'use strict' + +const Joi = require('joi') +const JetbrainsBase = require('./jetbrains-base') +const { renderVersionBadge } = require('../../lib/version') + +const schema = Joi.object({ + 'plugin-repository': Joi.object({ + category: Joi.object({ + 'idea-plugin': Joi.array() + .min(1) + .items( + Joi.object({ + version: Joi.string().required(), + }) + ) + .single() + .required(), + }), + }).required(), +}).required() + +module.exports = class JetbrainsDownloads extends JetbrainsBase { + static get category() { + return 'version' + } + + static get defaultBadgeData() { + return { label: 'jetbrains plugin' } + } + + static get examples() { + return [ + { + title: 'JetBrains IntelliJ Plugins', + exampleUrl: '9630-a8translate', + urlPattern: ':pluginId', + staticExample: this.render({ version: 'v1.7' }), + }, + ] + } + + static get route() { + return this.buildUrl('jetbrains/plugin/v') + } + + static render({ version }) { + return renderVersionBadge({ version }) + } + + async handle({ pluginId }) { + const pluginData = await this.fetchPackageData({ pluginId, schema }) + const version = + pluginData['plugin-repository'].category['idea-plugin'][0]['version'] + return this.constructor.render({ version }) + } +} diff --git a/services/jetbrains/jetbrains.service.js b/services/jetbrains/jetbrains.service.js deleted file mode 100644 index a72b5c35dc..0000000000 --- a/services/jetbrains/jetbrains.service.js +++ /dev/null @@ -1,77 +0,0 @@ -'use strict' - -const xml2js = require('xml2js') -const LegacyService = require('../legacy-service') -const { makeBadgeData: getBadgeData } = require('../../lib/badge-data') -const { metric } = require('../../lib/text-formatters') -const { - downloadCount: downloadCountColor, -} = require('../../lib/color-formatters') -const { addv: versionText } = require('../../lib/text-formatters') -const { version: versionColor } = require('../../lib/color-formatters') - -// JetBrains Plugins repository integration -module.exports = class JetBrains extends LegacyService { - static registerLegacyRouteHandler({ camp, cache }) { - camp.route( - /^\/jetbrains\/plugin\/(d|v)\/([^/]+)\.(svg|png|gif|jpg|json)$/, - cache((data, match, sendBadge, request) => { - const pluginId = match[2] - const type = match[1] - const format = match[3] - const leftText = type === 'v' ? 'jetbrains plugin' : 'downloads' - const badgeData = getBadgeData(leftText, data) - const url = `https://plugins.jetbrains.com/plugins/list?pluginId=${pluginId}` - - request(url, (err, res, buffer) => { - if (err || res.statusCode !== 200) { - badgeData.text[1] = 'inaccessible' - return sendBadge(format, badgeData) - } - - xml2js.parseString(buffer.toString(), (err, data) => { - if (err) { - badgeData.text[1] = 'invalid' - return sendBadge(format, badgeData) - } - - try { - const plugin = data['plugin-repository'].category - if (!plugin) { - badgeData.text[1] = 'not found' - return sendBadge(format, badgeData) - } - switch (type) { - case 'd': { - const downloads = parseInt( - data['plugin-repository'].category[0]['idea-plugin'][0]['$'] - .downloads, - 10 - ) - if (isNaN(downloads)) { - badgeData.text[1] = 'invalid' - return sendBadge(format, badgeData) - } - badgeData.text[1] = metric(downloads) - badgeData.colorscheme = downloadCountColor(downloads) - return sendBadge(format, badgeData) - } - case 'v': { - const version = - data['plugin-repository'].category[0]['idea-plugin'][0] - .version[0] - badgeData.text[1] = versionText(version) - badgeData.colorscheme = versionColor(version) - return sendBadge(format, badgeData) - } - } - } catch (err) { - badgeData.text[1] = 'invalid' - return sendBadge(format, badgeData) - } - }) - }) - }) - ) - } -} diff --git a/services/jetbrains/jetbrains.tester.js b/services/jetbrains/jetbrains.tester.js index e4ea320a76..860b1b1f6d 100644 --- a/services/jetbrains/jetbrains.tester.js +++ b/services/jetbrains/jetbrains.tester.js @@ -19,6 +19,27 @@ t.create('downloads (user friendly plugin id)') .get('/plugin/d/1347-scala.json') .expectJSONTypes(Joi.object().keys({ name: 'downloads', value: isMetric })) +t.create('downloads (mocked)') + .get('/plugin/d/9435.json') + .intercept( + nock => + nock('https://plugins.jetbrains.com') + .get('/plugins/list?pluginId=9435') + .reply( + 200, + ` + + + + + ` + ), + { + 'Content-Type': 'text/xml;charset=UTF-8', + } + ) + .expectJSON({ name: 'downloads', value: '2' }) + t.create('unknown plugin') .get('/plugin/d/unknown-plugin.json') .expectJSON({ name: 'downloads', value: 'not found' }) @@ -44,7 +65,7 @@ t.create('empty response') .get('/plugins/list?pluginId=7495') .reply(200, '') ) - .expectJSON({ name: 'downloads', value: 'invalid' }) + .expectJSON({ name: 'downloads', value: 'unparseable xml response' }) t.create('incorrect response format (JSON instead of XML)') .get('/plugin/d/7495.json') @@ -53,7 +74,7 @@ t.create('incorrect response format (JSON instead of XML)') .get('/plugins/list?pluginId=7495') .reply(200, { downloads: 2 }) ) - .expectJSON({ name: 'downloads', value: 'invalid' }) + .expectJSON({ name: 'downloads', value: 'unparseable xml response' }) t.create('missing required XML element') .get('/plugin/d/9435.json') @@ -75,7 +96,7 @@ t.create('missing required XML element') 'Content-Type': 'text/xml;charset=UTF-8', } ) - .expectJSON({ name: 'downloads', value: 'invalid' }) + .expectJSON({ name: 'downloads', value: 'invalid response data' }) t.create('missing required XML attribute') .get('/plugin/d/9435.json') @@ -108,7 +129,7 @@ t.create('missing required XML attribute') 'Content-Type': 'text/xml;charset=UTF-8', } ) - .expectJSON({ name: 'downloads', value: 'invalid' }) + .expectJSON({ name: 'downloads', value: 'invalid response data' }) t.create('empty XML') .get('/plugin/d/9435.json') @@ -121,7 +142,20 @@ t.create('empty XML') 'Content-Type': 'text/xml;charset=UTF-8', } ) - .expectJSON({ name: 'downloads', value: 'invalid' }) + .expectJSON({ name: 'downloads', value: 'unparseable xml response' }) + +t.create('XML with unknown root') + .get('/plugin/d/9435.json') + .intercept( + nock => + nock('https://plugins.jetbrains.com') + .get('/plugins/list?pluginId=9435') + .reply(200, ''), + { + 'Content-Type': 'text/xml;charset=UTF-8', + } + ) + .expectJSON({ name: 'downloads', value: 'invalid response data' }) t.create('404 status code') .get('/plugin/d/7495.json') @@ -130,7 +164,7 @@ t.create('404 status code') .get('/plugins/list?pluginId=7495') .reply(404) ) - .expectJSON({ name: 'downloads', value: 'inaccessible' }) + .expectJSON({ name: 'downloads', value: 'not found' }) t.create('empty XML(v)') .get('/plugin/v/9435.json') @@ -143,7 +177,7 @@ t.create('empty XML(v)') 'Content-Type': 'text/xml;charset=UTF-8', } ) - .expectJSON({ name: 'jetbrains plugin', value: 'invalid' }) + .expectJSON({ name: 'jetbrains plugin', value: 'unparseable xml response' }) t.create('404 status code(v)') .get('/plugin/v/7495.json') @@ -152,7 +186,7 @@ t.create('404 status code(v)') .get('/plugins/list?pluginId=7495') .reply(404) ) - .expectJSON({ name: 'jetbrains plugin', value: 'inaccessible' }) + .expectJSON({ name: 'jetbrains plugin', value: 'not found' }) t.create('missing required XML element(v)') .get('/plugin/v/9435.json') @@ -174,7 +208,7 @@ t.create('missing required XML element(v)') 'Content-Type': 'text/xml;charset=UTF-8', } ) - .expectJSON({ name: 'jetbrains plugin', value: 'invalid' }) + .expectJSON({ name: 'jetbrains plugin', value: 'invalid response data' }) t.create('incorrect response format (JSON instead of XML)(v)') .get('/plugin/v/7495.json') @@ -183,7 +217,7 @@ t.create('incorrect response format (JSON instead of XML)(v)') .get('/plugins/list?pluginId=7495') .reply(200, { version: 2.0 }) ) - .expectJSON({ name: 'jetbrains plugin', value: 'invalid' }) + .expectJSON({ name: 'jetbrains plugin', value: 'unparseable xml response' }) t.create('empty response(v)') .get('/plugin/v/7495.json') @@ -192,7 +226,7 @@ t.create('empty response(v)') .get('/plugins/list?pluginId=7495') .reply(200, '') ) - .expectJSON({ name: 'jetbrains plugin', value: 'invalid' }) + .expectJSON({ name: 'jetbrains plugin', value: 'unparseable xml response' }) t.create('server error(v)') .get('/plugin/v/7495.json') @@ -238,3 +272,39 @@ t.create('version (number as a plugin id)') value: isVPlusDottedVersionNClauses, }) ) + +t.create('version (mocked)') + .get('/plugin/v/9435.json') + .intercept( + nock => + nock('https://plugins.jetbrains.com') + .get('/plugins/list?pluginId=9435') + .reply( + 200, + ` + + + + 1.0 + + + ` + ), + { + 'Content-Type': 'text/xml;charset=UTF-8', + } + ) + .expectJSON({ name: 'jetbrains plugin', value: 'v1.0' }) + +t.create('XML with unknown root (v)') + .get('/plugin/v/9435.json') + .intercept( + nock => + nock('https://plugins.jetbrains.com') + .get('/plugins/list?pluginId=9435') + .reply(200, ''), + { + 'Content-Type': 'text/xml;charset=UTF-8', + } + ) + .expectJSON({ name: 'jetbrains plugin', value: 'invalid response data' })