Repository: badges/shields (js)

Issue:
[CratesVersion] TypeError: Cannot read property 'max_version' of undefined
Sentry Issue: [SHIELDS-2X](https://sentry.io/organizations/shields/issues/1125402359/?referrer=github_integration)

```
TypeError: Cannot read property 'max_version' of undefined
  File "/home/m/shields/services/crates/crates-version.service.js", line 36, in CratesVersion.handle
    version: json.version ? json.version.num : json.crate.max_version,
  ?, in null.<anonymous>
```

Pull request description:
fix runtime type error in [crates] version
Fixes #3782, also some more detail here https://github.com/badges/shields/issues/3782#issuecomment-516630473 

Patch:
diff --git a/services/crates/crates-base.js b/services/crates/crates-base.js
index 021ca43bff..2552ccc8b4 100644
--- a/services/crates/crates-base.js
+++ b/services/crates/crates-base.js
@@ -10,7 +10,7 @@ const crateSchema = Joi.object({
   crate: Joi.object({
     downloads: nonNegativeInteger,
     max_version: Joi.string().required(),
-  }),
+  }).required(),
   versions: Joi.array()
     .items(
       Joi.object({
diff --git a/services/crates/crates-version.service.js b/services/crates/crates-version.service.js
index 43119c1614..e036dd7cf0 100644
--- a/services/crates/crates-version.service.js
+++ b/services/crates/crates-version.service.js
@@ -2,6 +2,7 @@
 
 const { renderVersionBadge } = require('../version')
 const { BaseCratesService, keywords } = require('./crates-base')
+const { InvalidResponse } = require('..')
 
 module.exports = class CratesVersion extends BaseCratesService {
   static get category() {
@@ -20,20 +21,22 @@ module.exports = class CratesVersion extends BaseCratesService {
       {
         title: 'Crates.io',
         namedParams: { crate: 'rustc-serialize' },
-        staticPreview: this.render({ version: '0.3.24' }),
+        staticPreview: renderVersionBadge({ version: '0.3.24' }),
         keywords,
       },
     ]
   }
 
-  static render({ version }) {
-    return renderVersionBadge({ version })
+  transform(json) {
+    if (json.errors) {
+      throw new InvalidResponse({ prettyMessage: json.errors[0].detail })
+    }
+    return { version: json.version ? json.version.num : json.crate.max_version }
   }
 
   async handle({ crate }) {
     const json = await this.fetch({ crate })
-    return this.constructor.render({
-      version: json.version ? json.version.num : json.crate.max_version,
-    })
+    const { version } = this.transform(json)
+    return renderVersionBadge({ version })
   }
 }
diff --git a/services/crates/crates-version.spec.js b/services/crates/crates-version.spec.js
new file mode 100644
index 0000000000..de50a0f130
--- /dev/null
+++ b/services/crates/crates-version.spec.js
@@ -0,0 +1,19 @@
+'use strict'
+
+const { test, given } = require('sazerac')
+const { expect } = require('chai')
+const CratesVersion = require('./crates-version.service')
+const { InvalidResponse } = require('..')
+
+describe('CratesVersion', function() {
+  test(CratesVersion.prototype.transform, () => {
+    given({ version: { num: '1.0.0' } }).expect({ version: '1.0.0' })
+    given({ crate: { max_version: '1.1.0' } }).expect({ version: '1.1.0' })
+  })
+
+  it('throws InvalidResponse on error response', function() {
+    expect(() =>
+      CratesVersion.prototype.transform({ errors: [{ detail: 'idk how...' }] })
+    ).to.throw(InvalidResponse)
+  })
+})


Test patch:
diff --git a/services/crates/crates-version.tester.js b/services/crates/crates-version.tester.js
index c4c8b9b4b4..4fc19952d8 100644
--- a/services/crates/crates-version.tester.js
+++ b/services/crates/crates-version.tester.js
@@ -1,13 +1,7 @@
 'use strict'
 
-const { ServiceTester } = require('../tester')
 const { isSemver } = require('../test-validators')
-
-const t = (module.exports = new ServiceTester({
-  id: 'crates',
-  title: 'crates.io',
-  pathPrefix: '/crates/v',
-}))
+const t = (module.exports = require('../tester').createServiceTester())
 
 t.create('version')
   .get('/libc.json')