Repository: badges/shields
Pull request #3160 (2019-03-05 05:28:00)

Refactor [librariesio]
This takes another pass over the modern services to remove unused code. I switched the shared code to use a function instead of a class and removed the indirection in the route params which led to skew between the route and examples and wasn't making things any clearer.

shields-ci: 
<!--
  0 failure: 
  0 warning: 
  1 messages
  
  DangerID: danger-id-default;
-->



<table>
  <thead>
    <tr>
      <th width="50"></th>
      <th width="100%" data-danger-table="true">Messages</th>
    </tr>
  </thead>
  <tbody><tr>
      <td>:book:</td>
      <td>:sparkles: Thanks for your contribution to Shields, @paulmelnikow!</td>
    </tr>
  </tbody>
</table>


<p align="right">
  Generated by :no_entry_sign: <a href="https://danger.systems/js">dangerJS</a> against c0199dd2e9f10489473a0b6a05b75cbdd8a079d4
</p>


Patch:
diff --git a/services/librariesio/librariesio-base.js b/services/librariesio/librariesio-base.js
deleted file mode 100644
index ccfe6be74a..0000000000
--- a/services/librariesio/librariesio-base.js
+++ /dev/null
@@ -1,48 +0,0 @@
-'use strict'
-
-const Joi = require('joi')
-const { BaseJsonService, InvalidResponse } = require('..')
-const { nonNegativeInteger, anyInteger } = require('../validators')
-
-// API doc: https://libraries.io/api#project
-// We distinguishb between packages and repos based on the presence of the
-// `platform` key.
-const packageSchema = Joi.object({
-  platform: Joi.string().required(),
-  dependents_count: nonNegativeInteger,
-  dependent_repos_count: nonNegativeInteger,
-  rank: anyInteger,
-}).required()
-
-const repoSchema = Joi.object({
-  platform: Joi.any().forbidden(),
-}).required()
-
-const packageOrRepoSchema = Joi.alternatives(repoSchema, packageSchema)
-
-class LibrariesIoBase extends BaseJsonService {
-  static buildRoute(base) {
-    return {
-      base,
-      pattern: ':platform/:packageName',
-    }
-  }
-
-  async fetch({ platform, packageName }, { allowPackages, allowRepos } = {}) {
-    const json = await this._requestJson({
-      schema: packageOrRepoSchema,
-      url: `https://libraries.io/api/${platform}/${packageName}`,
-      errorMessages: { 404: 'package not found' },
-    })
-    const isPackage = Boolean(json.platform)
-    if (isPackage && !allowPackages) {
-      throw new InvalidResponse({ prettyMessage: 'not supported for packages' })
-    }
-    if (!isPackage && !allowRepos) {
-      throw new InvalidResponse({ prettyMessage: 'not supported for repos' })
-    }
-    return json
-  }
-}
-
-module.exports = LibrariesIoBase
diff --git a/services/librariesio/librariesio-common.js b/services/librariesio/librariesio-common.js
new file mode 100644
index 0000000000..8407d06b65
--- /dev/null
+++ b/services/librariesio/librariesio-common.js
@@ -0,0 +1,24 @@
+'use strict'
+
+const Joi = require('joi')
+const { nonNegativeInteger, anyInteger } = require('../validators')
+
+// API doc: https://libraries.io/api#project
+const projectSchema = Joi.object({
+  platform: Joi.string().required(),
+  dependents_count: nonNegativeInteger,
+  dependent_repos_count: nonNegativeInteger,
+  rank: anyInteger,
+}).required()
+
+async function fetchProject(serviceInstance, { platform, packageName }) {
+  return serviceInstance._requestJson({
+    schema: projectSchema,
+    url: `https://libraries.io/api/${platform}/${packageName}`,
+    errorMessages: { 404: 'package not found' },
+  })
+}
+
+module.exports = {
+  fetchProject,
+}
diff --git a/services/librariesio/librariesio-dependencies-helpers.js b/services/librariesio/librariesio-dependencies-helpers.js
new file mode 100644
index 0000000000..f9bf0b1908
--- /dev/null
+++ b/services/librariesio/librariesio-dependencies-helpers.js
@@ -0,0 +1,31 @@
+'use strict'
+
+function transform({ dependencies }) {
+  return {
+    deprecatedCount: dependencies.filter(dep => dep.deprecated).length,
+    outdatedCount: dependencies.filter(dep => dep.outdated).length,
+  }
+}
+
+function renderDependenciesBadge({ deprecatedCount, outdatedCount }) {
+  if (deprecatedCount > 0) {
+    // Deprecated dependencies are really bad
+    return {
+      message: `${deprecatedCount} deprecated`,
+      color: 'red',
+    }
+  } else if (outdatedCount > 0) {
+    // Out of date dependencies are pretty bad
+    return {
+      message: `${outdatedCount} out of date`,
+      color: 'orange',
+    }
+  } else {
+    return {
+      message: 'up to date',
+      color: 'brightgreen',
+    }
+  }
+}
+
+module.exports = { transform, renderDependenciesBadge }
diff --git a/services/librariesio/librariesio-dependencies-helpers.spec.js b/services/librariesio/librariesio-dependencies-helpers.spec.js
new file mode 100644
index 0000000000..d3c021a1fc
--- /dev/null
+++ b/services/librariesio/librariesio-dependencies-helpers.spec.js
@@ -0,0 +1,26 @@
+'use strict'
+
+const { test, given, forCases } = require('sazerac')
+const {
+  renderDependenciesBadge,
+} = require('./librariesio-dependencies-helpers')
+
+describe('Libraries.io dependency helpers', function() {
+  test(renderDependenciesBadge, () => {
+    forCases([
+      given({ deprecatedCount: 10, outdatedCount: 0 }),
+      given({ deprecatedCount: 10, outdatedCount: 5 }),
+    ]).expect({
+      message: '10 deprecated',
+      color: 'red',
+    })
+    given({ deprecatedCount: 0, outdatedCount: 5 }).expect({
+      message: '5 out of date',
+      color: 'orange',
+    })
+    given({ deprecatedCount: 0, outdatedCount: 0 }).expect({
+      message: 'up to date',
+      color: 'brightgreen',
+    })
+  })
+})
diff --git a/services/librariesio/librariesio-dependencies.service.js b/services/librariesio/librariesio-dependencies.service.js
index e6c15c0d34..fd4e628009 100644
--- a/services/librariesio/librariesio-dependencies.service.js
+++ b/services/librariesio/librariesio-dependencies.service.js
@@ -1,24 +1,32 @@
 'use strict'
 
-const LegacyService = require('../legacy-service')
-const { makeBadgeData: getBadgeData } = require('../../lib/badge-data')
-const { checkErrorResponse } = require('../../lib/error-helper')
+const Joi = require('joi')
+const { BaseJsonService } = require('..')
+const {
+  transform,
+  renderDependenciesBadge,
+} = require('./librariesio-dependencies-helpers')
 
-// This legacy service should be rewritten to use e.g. BaseJsonService.
-//
-// Tips for rewriting:
-// https://github.com/badges/shields/blob/master/doc/rewriting-services.md
-//
-// Do not base new services on this code.
-module.exports = class LibrariesioDependencies extends LegacyService {
+const schema = Joi.object({
+  dependencies: Joi.array()
+    .items(
+      Joi.object({
+        deprecated: Joi.boolean().required(),
+        outdated: Joi.boolean().required(),
+      })
+    )
+    .default([]),
+}).required()
+
+class LibrariesIoProjectDependencies extends BaseJsonService {
   static get category() {
     return 'dependencies'
   }
 
   static get route() {
     return {
-      base: 'librariesio',
-      pattern: '',
+      base: 'librariesio/release',
+      pattern: ':platform/:packageName/:version?',
     }
   }
 
@@ -26,109 +34,87 @@ module.exports = class LibrariesioDependencies extends LegacyService {
     return [
       {
         title: 'Libraries.io dependency status for latest release',
-        pattern: 'release/:user/:repo',
+        pattern: ':platform/:packageName',
         namedParams: {
-          user: 'hex',
-          repo: 'phoenix',
-        },
-        staticPreview: {
-          label: 'dependencies',
-          message: '1 out of date',
-          color: 'orange',
+          platform: 'hex',
+          packageName: 'phoenix',
         },
+        staticPreview: renderDependenciesBadge({
+          deprecatedCount: 0,
+          outdatedCount: 1,
+        }),
       },
       {
         title: 'Libraries.io dependency status for specific release',
-        pattern: 'release/:user/:repo/:version',
+        pattern: ':platform/:packageName/:version',
         namedParams: {
-          user: 'hex',
-          repo: 'phoenix',
+          platform: 'hex',
+          packageName: 'phoenix',
           version: '1.0.3',
         },
-        staticPreview: {
-          label: 'dependencies',
-          message: '3 out of date',
-          color: 'orange',
-        },
+        staticPreview: renderDependenciesBadge({
+          deprecatedCount: 0,
+          outdatedCount: 3,
+        }),
       },
+    ]
+  }
+
+  async handle({ platform, packageName, version = 'latest' }) {
+    const url = `https://libraries.io/api/${encodeURIComponent(
+      platform
+    )}/${encodeURIComponent(packageName)}/${encodeURIComponent(
+      version
+    )}/dependencies`
+    const json = await this._requestJson({
+      url,
+      schema,
+      errorMessages: { 404: 'package or version not found' },
+    })
+    const { deprecatedCount, outdatedCount } = transform(json)
+    return renderDependenciesBadge({ deprecatedCount, outdatedCount })
+  }
+}
+
+class LibrariesIoRepoDependencies extends BaseJsonService {
+  static get category() {
+    return 'dependencies'
+  }
+
+  static get route() {
+    return {
+      base: 'librariesio/github',
+      pattern: ':user/:repo',
+    }
+  }
+
+  static get examples() {
+    return [
       {
         title: 'Libraries.io dependency status for GitHub repo',
-        pattern: 'github/:user/:repo',
         namedParams: {
           user: 'phoenixframework',
           repo: 'phoenix',
         },
-        staticPreview: {
-          label: 'dependencies',
-          message: '325 out of date',
-          color: 'orange',
-        },
+        staticPreview: renderDependenciesBadge({ outdatedCount: 325 }),
       },
     ]
   }
 
-  static registerLegacyRouteHandler({ camp, cache }) {
-    camp.route(
-      /^\/librariesio\/(github|release)\/([\w\-_]+\/[\w\-_.]+)\/?([\w\-_.]+)?\.(svg|png|gif|jpg|json)$/,
-      cache((data, match, sendBadge, request) => {
-        const resource = match[1]
-        const project = match[2]
-        const version = match[3]
-        const format = match[4]
-
-        let uri
-        switch (resource) {
-          case 'github': {
-            uri = `https://libraries.io/api/github/${project}/dependencies`
-            break
-          }
-          case 'release': {
-            const v = version || 'latest'
-            uri = `https://libraries.io/api/${project}/${v}/dependencies`
-            break
-          }
-        }
-
-        const options = { method: 'GET', json: true, uri }
-        const badgeData = getBadgeData('dependencies', data)
-
-        request(options, (err, res, json) => {
-          if (
-            checkErrorResponse(badgeData, err, res, { 404: 'not available' })
-          ) {
-            sendBadge(format, badgeData)
-            return
-          }
-
-          try {
-            const deprecated = json.dependencies.filter(dep => dep.deprecated)
-
-            const outofdate = json.dependencies.filter(dep => dep.outdated)
-
-            // Deprecated dependencies are really bad
-            if (deprecated.length > 0) {
-              badgeData.colorscheme = 'red'
-              badgeData.text[1] = `${deprecated.length} deprecated`
-              return sendBadge(format, badgeData)
-            }
-
-            // Out of date dependencies are pretty bad
-            if (outofdate.length > 0) {
-              badgeData.colorscheme = 'orange'
-              badgeData.text[1] = `${outofdate.length} out of date`
-              return sendBadge(format, badgeData)
-            }
-
-            // Up to date dependencies are good!
-            badgeData.colorscheme = 'brightgreen'
-            badgeData.text[1] = 'up to date'
-            return sendBadge(format, badgeData)
-          } catch (e) {
-            badgeData.text[1] = 'invalid'
-            sendBadge(format, badgeData)
-          }
-        })
-      })
-    )
+  async handle({ user, repo }) {
+    const url = `https://libraries.io/api/github/${encodeURIComponent(
+      user
+    )}/${encodeURIComponent(repo)}/dependencies`
+    const json = await this._requestJson({
+      url,
+      schema,
+      errorMessages: {
+        404: 'repo not found',
+      },
+    })
+    const { deprecatedCount, outdatedCount } = transform(json)
+    return renderDependenciesBadge({ deprecatedCount, outdatedCount })
   }
 }
+
+module.exports = [LibrariesIoProjectDependencies, LibrariesIoRepoDependencies]
diff --git a/services/librariesio/librariesio-dependent-repos.service.js b/services/librariesio/librariesio-dependent-repos.service.js
index 82537fec70..235c0b9ce5 100644
--- a/services/librariesio/librariesio-dependent-repos.service.js
+++ b/services/librariesio/librariesio-dependent-repos.service.js
@@ -1,10 +1,11 @@
 'use strict'
 
+const { BaseJsonService } = require('..')
 const { metric } = require('../text-formatters')
-const LibrariesIoBase = require('./librariesio-base')
+const { fetchProject } = require('./librariesio-common')
 
 // https://libraries.io/api#project-dependent-repositories
-class LibrariesIoDependentRepos extends LibrariesIoBase {
+module.exports = class LibrariesIoDependentRepos extends BaseJsonService {
   static get category() {
     return 'other'
   }
@@ -16,17 +17,19 @@ class LibrariesIoDependentRepos extends LibrariesIoBase {
   }
 
   static get route() {
-    return this.buildRoute('librariesio/dependent-repos')
+    return {
+      base: 'librariesio/dependent-repos',
+      pattern: ':platform/:packageName',
+    }
   }
 
   static get examples() {
     return [
       {
         title: 'Dependent repos (via libraries.io)',
-        pattern: ':platform/:library',
         namedParams: {
           platform: 'npm',
-          library: 'got',
+          packageName: 'got',
         },
         staticPreview: this.render({ dependentReposCount: 84000 }),
       },
@@ -41,15 +44,13 @@ class LibrariesIoDependentRepos extends LibrariesIoBase {
   }
 
   async handle({ platform, packageName }) {
-    const { dependent_repos_count: dependentReposCount } = await this.fetch(
+    const { dependent_repos_count: dependentReposCount } = await fetchProject(
+      this,
       {
         platform,
         packageName,
-      },
-      { allowPackages: true }
+      }
     )
     return this.constructor.render({ dependentReposCount })
   }
 }
-
-module.exports = LibrariesIoDependentRepos
diff --git a/services/librariesio/librariesio-dependents.service.js b/services/librariesio/librariesio-dependents.service.js
index 49805f84d6..ef34ce3dba 100644
--- a/services/librariesio/librariesio-dependents.service.js
+++ b/services/librariesio/librariesio-dependents.service.js
@@ -1,10 +1,11 @@
 'use strict'
 
+const { BaseJsonService } = require('..')
 const { metric } = require('../text-formatters')
-const LibrariesIoBase = require('./librariesio-base')
+const { fetchProject } = require('./librariesio-common')
 
 // https://libraries.io/api#project-dependents
-class LibrariesIoDependents extends LibrariesIoBase {
+module.exports = class LibrariesIoDependents extends BaseJsonService {
   static get category() {
     return 'other'
   }
@@ -16,17 +17,19 @@ class LibrariesIoDependents extends LibrariesIoBase {
   }
 
   static get route() {
-    return this.buildRoute('librariesio/dependents')
+    return {
+      base: 'librariesio/dependents',
+      pattern: ':platform/:packageName',
+    }
   }
 
   static get examples() {
     return [
       {
         title: 'Dependents (via libraries.io)',
-        pattern: ':platform/:library',
         namedParams: {
           platform: 'npm',
-          library: 'got',
+          packageName: 'got',
         },
         staticPreview: this.render({ dependentCount: 2000 }),
       },
@@ -41,15 +44,10 @@ class LibrariesIoDependents extends LibrariesIoBase {
   }
 
   async handle({ platform, packageName }) {
-    const { dependents_count: dependentCount } = await this.fetch(
-      {
-        platform,
-        packageName,
-      },
-      { allowPackages: true }
-    )
+    const { dependents_count: dependentCount } = await fetchProject(this, {
+      platform,
+      packageName,
+    })
     return this.constructor.render({ dependentCount })
   }
 }
-
-module.exports = LibrariesIoDependents
diff --git a/services/librariesio/librariesio-sourcerank.service.js b/services/librariesio/librariesio-sourcerank.service.js
index 46009baf79..b84a395a75 100644
--- a/services/librariesio/librariesio-sourcerank.service.js
+++ b/services/librariesio/librariesio-sourcerank.service.js
@@ -1,11 +1,12 @@
 'use strict'
 
+const { BaseJsonService } = require('..')
 const { colorScale } = require('../color-formatters')
-const LibrariesIoBase = require('./librariesio-base')
+const { fetchProject } = require('./librariesio-common')
 
 const sourceRankColor = colorScale([10, 15, 20, 25, 30])
 
-class LibrariesIoSourcerank extends LibrariesIoBase {
+module.exports = class LibrariesIoSourcerank extends BaseJsonService {
   static get category() {
     return 'rating'
   }
@@ -17,17 +18,19 @@ class LibrariesIoSourcerank extends LibrariesIoBase {
   }
 
   static get route() {
-    return this.buildRoute('librariesio/sourcerank')
+    return {
+      base: 'librariesio/sourcerank',
+      pattern: ':platform/:packageName',
+    }
   }
 
   static get examples() {
     return [
       {
         title: 'Libraries.io SourceRank',
-        pattern: ':platform/:library',
         namedParams: {
           platform: 'npm',
-          library: 'got',
+          packageName: 'got',
         },
         staticPreview: this.render({ rank: 25 }),
       },
@@ -42,15 +45,10 @@ class LibrariesIoSourcerank extends LibrariesIoBase {
   }
 
   async handle({ platform, packageName }) {
-    const { rank } = await this.fetch(
-      {
-        platform,
-        packageName,
-      },
-      { allowPackages: true }
-    )
+    const { rank } = await fetchProject(this, {
+      platform,
+      packageName,
+    })
     return this.constructor.render({ rank })
   }
 }
-
-module.exports = LibrariesIoSourcerank


Test patch:
diff --git a/services/librariesio/librariesio-dependencies.tester.js b/services/librariesio/librariesio-dependencies.tester.js
index e02981a279..4843f361c2 100644
--- a/services/librariesio/librariesio-dependencies.tester.js
+++ b/services/librariesio/librariesio-dependencies.tester.js
@@ -1,44 +1,51 @@
 'use strict'
 
+const { ServiceTester } = require('../tester')
 const { isDependencyState } = require('../test-validators')
-const t = (module.exports = require('../tester').createServiceTester())
+const t = (module.exports = new ServiceTester({
+  id: 'LibrariesIoDependencies',
+  title: 'LibrariesIoDependencies',
+  pathPrefix: '/librariesio',
+}))
 
-t.create('dependencies for releases')
-  .get('/release/hex/phoenix/1.0.3.json')
+t.create('dependencies for package (project name contains dot)')
+  .get('/release/nuget/Newtonsoft.Json.json')
   .expectBadge({
     label: 'dependencies',
     message: isDependencyState,
   })
 
-t.create('dependencies for releases (project name contains dot)')
-  .get('/release/nuget/Newtonsoft.Json.json')
+t.create('dependencies for package with version')
+  .get('/release/hex/phoenix/1.0.3.json')
   .expectBadge({
     label: 'dependencies',
     message: isDependencyState,
   })
 
-t.create('dependencies for github')
-  .get('/github/pyvesb/notepad4e.json')
+t.create('version not found')
+  .get('/release/hex/phoenix/9.9.99.json')
   .expectBadge({
     label: 'dependencies',
-    message: isDependencyState,
+    message: 'package or version not found',
   })
 
-t.create('release not found')
+t.create('package not found')
   .get('/release/hex/invalid/4.0.4.json')
   .expectBadge({
     label: 'dependencies',
-    message: 'not available',
+    message: 'package or version not found',
+  })
+
+t.create('dependencies for repo')
+  .get('/github/pyvesb/notepad4e.json')
+  .expectBadge({
+    label: 'dependencies',
+    message: isDependencyState,
   })
 
-t.create('no response data')
-  .get('/github/phoenixframework/phoenix.json')
-  .intercept(nock =>
-    nock('https://libraries.io')
-      .get('/api/github/phoenixframework/phoenix/dependencies')
-      .reply(200)
-  )
+t.create('repo not found')
+  .get('/github/foobar/is-not-a-repo.json')
   .expectBadge({
     label: 'dependencies',
-    message: 'invalid',
+    message: 'repo not found',
   })
diff --git a/services/librariesio/librariesio-dependents.tester.js b/services/librariesio/librariesio-dependents.tester.js
index 6e4a038ee0..deeed21740 100644
--- a/services/librariesio/librariesio-dependents.tester.js
+++ b/services/librariesio/librariesio-dependents.tester.js
@@ -17,10 +17,3 @@ t.create('dependent count (nonexistent package)')
     label: 'dependents',
     message: 'package not found',
   })
-
-t.create('dependent count (repo)')
-  .get('/github/sindresorhus/got.json')
-  .expectBadge({
-    label: 'dependents',
-    message: 'not supported for repos',
-  })