diff --git a/services/crates/crates-base.js b/services/crates/crates-base.js index e87fe5cea875f..63699a581e44d 100644 --- a/services/crates/crates-base.js +++ b/services/crates/crates-base.js @@ -14,7 +14,7 @@ const crateSchema = Joi.object({ .items( Joi.object({ downloads: nonNegativeInteger, - license: Joi.string().required(), + license: Joi.string().required().allow(null), }) ) .min(1) @@ -25,7 +25,7 @@ const versionSchema = Joi.object({ version: Joi.object({ downloads: nonNegativeInteger, num: Joi.string().required(), - license: Joi.string().required(), + license: Joi.string().required().allow(null), }).required(), }).required() diff --git a/services/crates/crates-downloads.tester.js b/services/crates/crates-downloads.tester.js index 9e2e13ccf47aa..629cd05ed0033 100644 --- a/services/crates/crates-downloads.tester.js +++ b/services/crates/crates-downloads.tester.js @@ -1,11 +1,6 @@ -import { ServiceTester } from '../tester.js' import { isMetric } from '../test-validators.js' - -export const t = new ServiceTester({ - id: 'crates', - title: 'crates.io', - pathPrefix: '/crates', -}) +import { createServiceTester } from '../tester.js' +export const t = await createServiceTester() t.create('total downloads') .get('/d/libc.json') diff --git a/services/crates/crates-license.service.js b/services/crates/crates-license.service.js index c32ab9517bd8f..562f44841cabe 100644 --- a/services/crates/crates-license.service.js +++ b/services/crates/crates-license.service.js @@ -1,3 +1,4 @@ +import { InvalidResponse } from '../index.js' import { BaseCratesService, keywords } from './crates-base.js' export default class CratesLicense extends BaseCratesService { @@ -21,28 +22,30 @@ export default class CratesLicense extends BaseCratesService { }, ] - static render({ license }) { - return { - label: 'license', - message: license, - color: 'blue', - } + static defaultBadgeData = { label: 'license', color: 'blue' } + + static render({ license: message }) { + return { message } } - async handle({ crate, version }) { - const json = await this.fetch({ crate, version }) + static transform({ errors, version, versions }) { + // crates.io returns a 200 response with an errors object in + // error scenarios, e.g. https://crates.io/api/v1/crates/libc/0.1 + if (errors) { + throw new InvalidResponse({ prettyMessage: errors[0].detail }) + } - 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 } + const license = version ? version.license : versions[0].license + if (!license) { + throw new InvalidResponse({ prettyMessage: 'invalid null license' }) } - return this.constructor.render({ - license: json.version ? json.version.license : json.versions[0].license, - }) + return { license } + } + + async handle({ crate, version }) { + const json = await this.fetch({ crate, version }) + const { license } = this.constructor.transform(json) + return this.constructor.render({ license }) } } diff --git a/services/crates/crates-license.spec.js b/services/crates/crates-license.spec.js new file mode 100644 index 0000000000000..f6454094dc3d2 --- /dev/null +++ b/services/crates/crates-license.spec.js @@ -0,0 +1,38 @@ +import { expect } from 'chai' +import { test, given } from 'sazerac' +import { InvalidResponse } from '../index.js' +import CratesLicense from './crates-license.service.js' + +describe('CratesLicense', function () { + test(CratesLicense.transform, () => { + given({ + version: { num: '1.0.0', license: 'MIT' }, + versions: [{ license: 'MIT/Apache 2.0' }], + }).expect({ license: 'MIT' }) + given({ + versions: [{ license: 'MIT/Apache 2.0' }], + }).expect({ license: 'MIT/Apache 2.0' }) + }) + + it('throws InvalidResponse on error response', function () { + expect(() => + CratesLicense.transform({ errors: [{ detail: 'invalid semver' }] }) + ) + .to.throw(InvalidResponse) + .with.property('prettyMessage', 'invalid semver') + }) + + it('throws InvalidResponse on null license with specific version', function () { + expect(() => + CratesLicense.transform({ version: { num: '1.2.3', license: null } }) + ) + .to.throw(InvalidResponse) + .with.property('prettyMessage', 'invalid null license') + }) + + it('throws InvalidResponse on null license with latest version', function () { + expect(() => CratesLicense.transform({ versions: [{ license: null }] })) + .to.throw(InvalidResponse) + .with.property('prettyMessage', 'invalid null license') + }) +}) diff --git a/services/crates/crates-license.tester.js b/services/crates/crates-license.tester.js index 390295433b0bd..6351c14f85635 100644 --- a/services/crates/crates-license.tester.js +++ b/services/crates/crates-license.tester.js @@ -1,10 +1,5 @@ -import { ServiceTester } from '../tester.js' - -export const t = new ServiceTester({ - id: 'crates', - title: 'crates.io', - pathPrefix: '/crates/l', -}) +import { createServiceTester } from '../tester.js' +export const t = await createServiceTester() t.create('license') .get('/libc.json') @@ -16,4 +11,13 @@ t.create('license (with version)') t.create('license (not found)') .get('/not-a-real-package.json') - .expectBadge({ label: 'crates.io', message: 'not found' }) + .expectBadge({ label: 'license', message: 'not found' }) + +// https://github.com/badges/shields/issues/7073 +t.create('license (null licenses in history)') + .get('/stun.json') + .expectBadge({ label: 'license', message: 'MIT/Apache-2.0' }) + +t.create('license (version with null license)') + .get('/stun/0.0.1.json') + .expectBadge({ label: 'license', message: 'invalid null license' })