add tests for rust [crates] and refactor (#2462)

* add tests for rust [crates] and refactor
This commit is contained in:
chris48s
2018-12-06 20:24:46 +00:00
committed by GitHub
parent 9e95020b18
commit 8070f0bef9
8 changed files with 324 additions and 180 deletions

View File

@@ -0,0 +1,55 @@
'use strict'
const Joi = require('joi')
const BaseJsonService = require('../base-json')
const { nonNegativeInteger } = require('../validators')
const keywords = ['Rust']
const crateSchema = Joi.object({
crate: Joi.object({
downloads: nonNegativeInteger,
max_version: Joi.string().required(),
}),
versions: Joi.array()
.items(
Joi.object({
downloads: nonNegativeInteger,
license: Joi.string().required(),
})
)
.min(1)
.required(),
}).required()
const versionSchema = Joi.object({
version: Joi.object({
downloads: nonNegativeInteger,
num: Joi.string().required(),
license: Joi.string().required(),
}).required(),
}).required()
const errorSchema = Joi.object({
errors: Joi.array()
.items(Joi.object({ detail: Joi.string().required() }))
.min(1)
.required(),
}).required()
const schema = Joi.alternatives(crateSchema, versionSchema, errorSchema)
class BaseCratesService extends BaseJsonService {
async fetch({ crate, version }) {
const url = version
? `https://crates.io/api/v1/crates/${crate}/${version}`
: `https://crates.io/api/v1/crates/${crate}`
return this._requestJson({ schema, url })
}
static get defaultBadgeData() {
return { label: 'crates.io' }
}
}
module.exports = { BaseCratesService, keywords }

View File

@@ -0,0 +1,101 @@
'use strict'
const {
downloadCount: downloadCountColor,
} = require('../../lib/color-formatters')
const { metric } = require('../../lib/text-formatters')
const { BaseCratesService, keywords } = require('./crates-base')
module.exports = class CratesDownloads extends BaseCratesService {
static get category() {
return 'downloads'
}
static get route() {
return {
base: 'crates',
format: '(d|dv)/([A-Za-z0-9_-]+)(?:/([0-9.]+))?',
capture: ['which', 'crate', 'version'],
}
}
static get examples() {
return [
{
title: 'Crates.io',
pattern: 'd/:crate',
namedParams: { crate: 'rustc-serialize' },
staticExample: this.render({ downloads: 5000000 }),
keywords,
},
{
title: 'Crates.io',
pattern: 'd/:crate/:version',
namedParams: { crate: 'rustc-serialize', version: '0.3.24' },
staticExample: this.render({ downloads: 2000000, version: '0.3.24' }),
keywords,
},
{
title: 'Crates.io',
pattern: 'dv/:crate',
namedParams: { crate: 'rustc-serialize' },
staticExample: this.render({ which: 'dv', downloads: 2000000 }),
keywords,
},
{
title: 'Crates.io',
pattern: 'dv/:crate/:version',
namedParams: { crate: 'rustc-serialize', version: '0.3.24' },
staticExample: this.render({
which: 'dv',
downloads: 2000000,
version: '0.3.24',
}),
keywords,
},
]
}
static _getLabel(version, which) {
if (version) {
return `downloads@${version}`
} else {
if (which === 'dv') {
return 'downloads@latest'
} else {
return 'downloads'
}
}
}
static render({ which, downloads, version }) {
return {
label: this._getLabel(version, which),
message: metric(downloads),
color: downloadCountColor(downloads),
}
}
async handle({ which, crate, version }) {
const json = await this.fetch({ crate, version })
if (json.errors) {
/* a call like
https://crates.io/api/v1/crates/libc/0.1
or
https://crates.io/api/v1/crates/libc/0.1.76
returns a 200 OK with an errors object */
return { message: json.errors[0].detail }
}
let downloads
if (which === 'dv') {
downloads = json.version
? json.version.downloads
: json.versions[0].downloads
} else {
downloads = json.crate ? json.crate.downloads : json.version.downloads
}
return this.constructor.render({ which, downloads, version })
}
}

View File

@@ -0,0 +1,44 @@
'use strict'
const ServiceTester = require('../service-tester')
const { isMetric } = require('../test-validators')
const t = new ServiceTester({
id: 'crates',
title: 'crates.io',
pathPrefix: '/crates',
})
module.exports = t
t.create('total downloads')
.get('/d/libc.json')
.expectJSONTypes({ name: 'downloads', value: isMetric })
t.create('total downloads (with version)')
.get('/d/libc/0.2.31.json')
.expectJSONTypes({
name: 'downloads@0.2.31',
value: isMetric,
})
t.create('downloads for version')
.get('/dv/libc.json')
.expectJSONTypes({
name: 'downloads@latest',
value: isMetric,
})
t.create('downloads for version (with version)')
.get('/dv/libc/0.2.31.json')
.expectJSONTypes({
name: 'downloads@0.2.31',
value: isMetric,
})
t.create('downloads (invalid version)')
.get('/d/libc/7.json')
.expectJSON({ name: 'crates.io', value: 'invalid semver: 7' })
t.create('downloads (not found)')
.get('/d/not-a-real-package.json')
.expectJSON({ name: 'crates.io', value: 'not found' })

View File

@@ -0,0 +1,61 @@
'use strict'
const { BaseCratesService, keywords } = require('./crates-base')
module.exports = class CratesLicense extends BaseCratesService {
static get category() {
return 'license'
}
static get route() {
return {
base: 'crates/l',
format: '([A-Za-z0-9_-]+)(?:/([0-9.]+))?',
capture: ['crate', 'version'],
}
}
static get examples() {
return [
{
title: 'Crates.io',
pattern: ':crate',
namedParams: { crate: 'rustc-serialize' },
staticExample: this.render({ license: 'MIT/Apache-2.0' }),
keywords,
},
{
title: 'Crates.io',
pattern: ':crate/:version',
namedParams: { crate: 'rustc-serialize', version: '0.3.24' },
staticExample: this.render({ license: 'MIT/Apache-2.0' }),
keywords,
},
]
}
static render({ license }) {
return {
label: 'license',
message: license,
color: 'blue',
}
}
async handle({ crate, version }) {
const json = await this.fetch({ crate, version })
if (json.errors) {
/* a call like
https://crates.io/api/v1/crates/libc/0.1
or
https://crates.io/api/v1/crates/libc/0.1.76
returns a 200 OK with an errors object */
return { message: json.errors[0].detail }
}
return this.constructor.render({
license: json.version ? json.version.license : json.versions[0].license,
})
}
}

View File

@@ -14,5 +14,9 @@ t.create('license')
.expectJSON({ name: 'license', value: 'MIT OR Apache-2.0' })
t.create('license (with version)')
.get('/libc/0.2.31.json')
.get('/libc/0.2.44.json')
.expectJSON({ name: 'license', value: 'MIT OR Apache-2.0' })
t.create('license (not found)')
.get('/not-a-real-package.json')
.expectJSON({ name: 'crates.io', value: 'not found' })

View File

@@ -0,0 +1,39 @@
'use strict'
const { renderVersionBadge } = require('../../lib/version')
const { BaseCratesService, keywords } = require('./crates-base')
module.exports = class CratesVersion extends BaseCratesService {
static get category() {
return 'version'
}
static get route() {
return {
base: 'crates/v',
pattern: ':crate',
}
}
static get examples() {
return [
{
title: 'Crates.io',
namedParams: { crate: 'rustc-serialize' },
staticExample: this.render({ version: '0.3.24' }),
keywords,
},
]
}
static render({ version }) {
return renderVersionBadge({ version })
}
async handle({ crate }) {
const json = await this.fetch({ crate })
return this.constructor.render({
version: json.version ? json.version.num : json.crate.max_version,
})
}
}

View File

@@ -0,0 +1,19 @@
'use strict'
const ServiceTester = require('../service-tester')
const { isSemver } = require('../test-validators')
const t = new ServiceTester({
id: 'crates',
title: 'crates.io',
pathPrefix: '/crates/v',
})
module.exports = t
t.create('version')
.get('/libc.json')
.expectJSONTypes({ name: 'crates.io', value: isSemver })
t.create('version (not found)')
.get('/not-a-real-package.json')
.expectJSON({ name: 'crates.io', value: 'not found' })

View File

@@ -1,179 +0,0 @@
'use strict'
const LegacyService = require('../legacy-service')
const { makeBadgeData: getBadgeData } = require('../../lib/badge-data')
const {
downloadCount: downloadCountColor,
version: versionColor,
} = require('../../lib/color-formatters')
const { metric, addv: versionText } = require('../../lib/text-formatters')
class CratesDownloads extends LegacyService {
static get category() {
return 'downloads'
}
static get route() {
return {
base: 'crates',
}
}
static get examples() {
return [
{
title: 'Crates.io',
previewUrl: 'd/rustc-serialize',
keywords: ['Rust'],
},
{
title: 'Crates.io',
previewUrl: 'dv/rustc-serialize',
keywords: ['Rust'],
},
]
}
static registerLegacyRouteHandler() {}
}
class CratesVersion extends LegacyService {
static get category() {
return 'version'
}
static get route() {
return {
base: 'crates/v',
}
}
static get examples() {
return [
{
title: 'Crates.io',
previewUrl: 'rustc-serialize',
keywords: ['Rust'],
},
]
}
static registerLegacyRouteHandler() {}
}
class CratesLicense extends LegacyService {
static get category() {
return 'license'
}
static get route() {
return {
base: 'crates/l',
}
}
static get examples() {
return [
{
title: 'Crates.io',
previewUrl: 'rustc-serialize',
keywords: ['Rust'],
},
]
}
static registerLegacyRouteHandler() {}
}
class Crates extends LegacyService {
static registerLegacyRouteHandler({ camp, cache }) {
camp.route(
/^\/crates\/(d|v|dv|l)\/([A-Za-z0-9_-]+)(?:\/([0-9.]+))?\.(svg|png|gif|jpg|json)$/,
cache((data, match, sendBadge, request) => {
const mode = match[1] // d - downloads (total or for version), v - (latest) version, dv - downloads (for latest version)
const crate = match[2] // crate name, e.g. rustc-serialize
let version = match[3] // crate version in semver format, optional, e.g. 0.1.2
const format = match[4]
const modes = {
d: {
name: 'downloads',
version: true,
process: function(data, badgeData) {
const downloads = data.crate
? data.crate.downloads
: data.version.downloads
version = data.version && data.version.num
badgeData.text[1] =
metric(downloads) + (version ? ` version ${version}` : '')
badgeData.colorscheme = downloadCountColor(downloads)
},
},
dv: {
name: 'downloads',
version: true,
process: function(data, badgeData) {
const downloads = data.version
? data.version.downloads
: data.versions[0].downloads
version = data.version && data.version.num
badgeData.text[1] =
metric(downloads) +
(version ? ` version ${version}` : ' latest version')
badgeData.colorscheme = downloadCountColor(downloads)
},
},
v: {
name: 'crates.io',
version: true,
process: function(data, badgeData) {
version = data.version ? data.version.num : data.crate.max_version
badgeData.text[1] = versionText(version)
badgeData.colorscheme = versionColor(version)
},
},
l: {
name: 'license',
version: false,
process: function(data, badgeData) {
badgeData.text[1] = data.versions[0].license
badgeData.colorscheme = 'blue'
},
},
}
const behavior = modes[mode]
let apiUrl = `https://crates.io/api/v1/crates/${crate}`
if (version != null && behavior.version) {
apiUrl += `/${version}`
}
const badgeData = getBadgeData(behavior.name, data)
request(
apiUrl,
{ headers: { Accept: 'application/json' } },
(err, res, buffer) => {
if (err != null) {
badgeData.text[1] = 'inaccessible'
sendBadge(format, badgeData)
return
}
try {
const data = JSON.parse(buffer)
behavior.process(data, badgeData)
sendBadge(format, badgeData)
} catch (e) {
badgeData.text[1] = 'invalid'
sendBadge(format, badgeData)
}
}
)
})
)
}
}
module.exports = {
CratesDownloads,
CratesVersion,
CratesLicense,
Crates,
}