From 66c7f13e38c76bfdbdb96107bc92e607b3dd5acc Mon Sep 17 00:00:00 2001 From: Paul Melnikow <github@paulmelnikow.com> Date: Sat, 6 Jul 2019 16:41:46 -0400 Subject: [PATCH] Drop gif + png, and redirect png to raster.shields.io (#3644) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Remove rasterization support from the server. This responsibility is delegated to a raster server which proxies the SVG badges and renders them. 2. When a raster server URL is configured, 301 redirect all .png badges to the identical URL on the raster server. `https://img.shields.io/npm/v/express.png?style=flat-square` ↪️`https://raster.shields.io/npm/v/express.png?style=flat-square` 3. For configured redirects, redirect to the canonical URL on the raster server. `https://img.shields.io/vso/build/totodem/8cf3ec0e-d0c2-4fcd-8206-ad204f254a96/2.png?style=flat-square` ↪️`https://img.shields.io/azure-devops/build/totodem/8cf3ec0e-d0c2-4fcd-8206-ad204f254a96/2.png?style=flat-square` 4. Redirect the "legacy badge old version" to the appropriate URL on the raster server. 5. When no raster server is configured (e.g. PRs), render an SVG containing **404 | raster badges not available** for all `.png` badges. (Note that the raster server can be self-hosted; however, this is deferred to a later PR.) 5. Drop support for jpg and gif which are very infrequently used (see #3112). Render an SVG containing **410 | jpg no longer available**. 7. ~~Remove raster dependencies.~~ Remove the raster cache (which is only used in the CLI, and therefore pointless). 8. Move the LRUCache code out of the npm package. 8. A wee bit of refactoring in `server.js`. Ref #3112 Close #3631 --- Dockerfile | 2 - config/custom-environment-variables.yml | 2 + config/shields-io-production.yml | 2 + config/test.yml | 2 + core/badge-urls/make-badge-url.js | 11 ++ core/base-service/base.spec.js | 2 +- core/base-service/legacy-request-handler.js | 2 +- core/base-service/legacy-result-sender.js | 26 +--- .../lib => core/base-service}/lru-cache.js | 0 .../base-service}/lru-cache.spec.js | 0 core/base-service/redirector.js | 11 +- core/base-service/redirector.spec.js | 11 +- core/base-service/route.js | 14 +-- core/base-service/route.spec.js | 9 -- core/server/server.js | 111 +++++++++++++----- core/server/server.spec.js | 52 +++----- doc/TUTORIAL.md | 5 - doc/production-hosting.md | 2 - doc/self-hosting.md | 21 ++++ gh-badges/lib/svg-to-img.js | 16 +-- gh-badges/lib/svg-to-img.spec.js | 19 --- 21 files changed, 166 insertions(+), 154 deletions(-) rename {gh-badges/lib => core/base-service}/lru-cache.js (100%) rename {gh-badges/lib => core/base-service}/lru-cache.spec.js (100%) diff --git a/Dockerfile b/Dockerfile index 68b03be407..30d3ef6a09 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,5 @@ FROM node:8-alpine -RUN apk add --no-cache gettext imagemagick librsvg git - RUN mkdir -p /usr/src/app RUN mkdir /usr/src/app/private WORKDIR /usr/src/app diff --git a/config/custom-environment-variables.yml b/config/custom-environment-variables.yml index 87b75a1589..10908f7ad0 100644 --- a/config/custom-environment-variables.yml +++ b/config/custom-environment-variables.yml @@ -14,6 +14,8 @@ public: redirectUri: 'REDIRECT_URI' + rasterUrl: 'RASTER_URL' + cors: allowedOrigin: __name: 'ALLOWED_ORIGIN' diff --git a/config/shields-io-production.yml b/config/shields-io-production.yml index f0f0c88f60..1ca9d5805a 100644 --- a/config/shields-io-production.yml +++ b/config/shields-io-production.yml @@ -11,6 +11,8 @@ public: redirectUrl: 'https://shields.io/' + rasterUrl: 'https://raster.shields.io' + private: # These are not really private; they should be moved to `public`. shields_ips: ['192.99.59.72', '51.254.114.150', '149.56.96.133'] diff --git a/config/test.yml b/config/test.yml index a09fada736..a330d56b14 100644 --- a/config/test.yml +++ b/config/test.yml @@ -7,4 +7,6 @@ public: redirectUrl: 'http://badge-server.example.com' + rasterUrl: 'http://raster.example.com' + handleInternalErrors: false diff --git a/core/badge-urls/make-badge-url.js b/core/badge-urls/make-badge-url.js index 7dd9a3a85c..f272f5ec5f 100644 --- a/core/badge-urls/make-badge-url.js +++ b/core/badge-urls/make-badge-url.js @@ -1,5 +1,6 @@ 'use strict' +const { URL } = require('url') const queryString = require('query-string') const pathToRegexp = require('path-to-regexp') @@ -134,6 +135,15 @@ function dynamicBadgeUrl({ return `${baseUrl}/badge/dynamic/${datatype}.${format}?${outQueryString}` } +function rasterRedirectUrl({ rasterUrl }, badgeUrl) { + // Ensure we're always using the `rasterUrl` by using just the path from + // the request URL. + const { pathname, search } = new URL(badgeUrl, 'https://bogus.test') + const result = new URL(pathname, rasterUrl) + result.search = search + return result +} + module.exports = { badgeUrlFromPath, badgeUrlFromPattern, @@ -141,4 +151,5 @@ module.exports = { staticBadgeUrl, queryStringStaticBadgeUrl, dynamicBadgeUrl, + rasterRedirectUrl, } diff --git a/core/base-service/base.spec.js b/core/base-service/base.spec.js index 4c2f99a4ba..0a9297fca5 100644 --- a/core/base-service/base.spec.js +++ b/core/base-service/base.spec.js @@ -316,7 +316,7 @@ describe('BaseService', function() { }) describe('ScoutCamp integration', function() { - const expectedRouteRegex = /^\/foo\/([^/]+?)\.(svg|png|gif|jpg|json)$/ + const expectedRouteRegex = /^\/foo\/([^/]+?)\.(svg|json)$/ let mockCamp let mockHandleRequest diff --git a/core/base-service/legacy-request-handler.js b/core/base-service/legacy-request-handler.js index 30980f7708..0639c098d5 100644 --- a/core/base-service/legacy-request-handler.js +++ b/core/base-service/legacy-request-handler.js @@ -4,7 +4,6 @@ const domain = require('domain') const request = require('request') const queryString = require('query-string') -const LruCache = require('../../gh-badges/lib/lru-cache') const makeBadge = require('../../gh-badges/lib/make-badge') const log = require('../server/log') const { setCacheHeaders } = require('./cache-headers') @@ -14,6 +13,7 @@ const { ShieldsRuntimeError, } = require('./errors') const { makeSend } = require('./legacy-result-sender') +const LruCache = require('./lru-cache') const coalesceBadge = require('./coalesce-badge') // We avoid calling the vendor's server for computation of the information in a diff --git a/core/base-service/legacy-result-sender.js b/core/base-service/legacy-result-sender.js index 8b733eb0fa..01ffc800cc 100644 --- a/core/base-service/legacy-result-sender.js +++ b/core/base-service/legacy-result-sender.js @@ -1,15 +1,6 @@ 'use strict' -const fs = require('fs') -const path = require('path') const stream = require('stream') -const svg2img = require('../../gh-badges/lib/svg-to-img') -const log = require('../server/log') - -const internalError = fs.readFileSync( - path.resolve(__dirname, '..', 'server', 'error-pages', '500.html'), - 'utf-8' -) function streamFromString(str) { const newStream = new stream.Readable() @@ -25,21 +16,6 @@ function sendSVG(res, askres, end) { end(null, { template: streamFromString(res) }) } -function sendOther(format, res, askres, end) { - askres.setHeader('Content-Type', `image/${format}`) - svg2img(res, format) - // This interacts with callback code and can't use async/await. - // eslint-disable-next-line promise/prefer-await-to-then - .then(data => { - end(null, { template: streamFromString(data) }) - }) - .catch(err => { - // This emits status code 200, though 500 would be preferable. - log.error('svg2img error', err) - end(internalError) - }) -} - function sendJSON(res, askres, end) { askres.setHeader('Content-Type', 'application/json') askres.setHeader('Access-Control-Allow-Origin', '*') @@ -52,7 +28,7 @@ function makeSend(format, askres, end) { } else if (format === 'json') { return res => sendJSON(res, askres, end) } else { - return res => sendOther(format, res, askres, end) + throw Error(`Unrecognized format: ${format}`) } } diff --git a/gh-badges/lib/lru-cache.js b/core/base-service/lru-cache.js similarity index 100% rename from gh-badges/lib/lru-cache.js rename to core/base-service/lru-cache.js diff --git a/gh-badges/lib/lru-cache.spec.js b/core/base-service/lru-cache.spec.js similarity index 100% rename from gh-badges/lib/lru-cache.spec.js rename to core/base-service/lru-cache.spec.js diff --git a/core/base-service/redirector.js b/core/base-service/redirector.js index ab4879faa2..d0a5d15c51 100644 --- a/core/base-service/redirector.js +++ b/core/base-service/redirector.js @@ -62,8 +62,11 @@ module.exports = function redirector(attrs) { return route } - static register({ camp, requestCounter }) { - const { regex, captureNames } = prepareRoute(this.route) + static register({ camp, requestCounter }, { rasterUrl }) { + const { regex, captureNames } = prepareRoute({ + ...this.route, + withPng: Boolean(rasterUrl), + }) const serviceRequestCounter = this._createServiceRequestCounter({ requestCounter, @@ -104,7 +107,9 @@ module.exports = function redirector(attrs) { // The final capture group is the extension. const format = match.slice(-1)[0] - const redirectUrl = `${targetPath}.${format}${urlSuffix}` + const redirectUrl = `${ + format === 'png' ? rasterUrl : '' + }${targetPath}.${format}${urlSuffix}` trace.logTrace('outbound', emojic.shield, 'Redirect URL', redirectUrl) ask.res.statusCode = 301 diff --git a/core/base-service/redirector.spec.js b/core/base-service/redirector.spec.js index 1bd4ff9d7f..6c83f8b54c 100644 --- a/core/base-service/redirector.spec.js +++ b/core/base-service/redirector.spec.js @@ -75,7 +75,10 @@ describe('Redirector', function() { transformPath, dateAdded, }) - ServiceClass.register({ camp }, {}) + ServiceClass.register( + { camp }, + { rasterUrl: 'http://raster.example.test' } + ) }) it('should redirect as configured', async function() { @@ -90,7 +93,7 @@ describe('Redirector', function() { expect(headers.location).to.equal('/new/service/hello-world.svg') }) - it('should preserve the extension', async function() { + it('should redirect raster extensions to the canonical path as configured', async function() { const { statusCode, headers } = await got( `${baseUrl}/very/old/service/hello-world.png`, { @@ -99,7 +102,9 @@ describe('Redirector', function() { ) expect(statusCode).to.equal(301) - expect(headers.location).to.equal('/new/service/hello-world.png') + expect(headers.location).to.equal( + 'http://raster.example.test/new/service/hello-world.png' + ) }) it('should forward the query params', async function() { diff --git a/core/base-service/route.js b/core/base-service/route.js index 0192243017..3bdf15de7c 100644 --- a/core/base-service/route.js +++ b/core/base-service/route.js @@ -26,18 +26,16 @@ function assertValidRoute(route, message = undefined) { Joi.assert(route, isValidRoute, message) } -function prepareRoute({ base, pattern, format, capture }) { +function prepareRoute({ base, pattern, format, capture, withPng }) { + const extensionRegex = ['svg', 'json'] + .concat(withPng ? ['png'] : []) + .join('|') let regex, captureNames if (pattern === undefined) { - regex = new RegExp( - `^${makeFullUrl(base, format)}\\.(svg|png|gif|jpg|json)$` - ) + regex = new RegExp(`^${makeFullUrl(base, format)}\\.(${extensionRegex})$`) captureNames = capture || [] } else { - const fullPattern = `${makeFullUrl( - base, - pattern - )}.:ext(svg|png|gif|jpg|json)` + const fullPattern = `${makeFullUrl(base, pattern)}.:ext(${extensionRegex})` const keys = [] regex = pathToRegexp(fullPattern, keys, { strict: true, diff --git a/core/base-service/route.spec.js b/core/base-service/route.spec.js index 8eda13d666..5b5331f8da 100644 --- a/core/base-service/route.spec.js +++ b/core/base-service/route.spec.js @@ -33,9 +33,6 @@ describe('Route helpers', function() { test(namedParams, () => { forCases([ given('/foo/bar.bar.bar.svg'), - given('/foo/bar.bar.bar.png'), - given('/foo/bar.bar.bar.gif'), - given('/foo/bar.bar.bar.jpg'), given('/foo/bar.bar.bar.json'), ]).expect({ namedParamA: 'bar.bar.bar' }) }) @@ -64,9 +61,6 @@ describe('Route helpers', function() { test(namedParams, () => { forCases([ given('/foo/bar.bar.bar.svg'), - given('/foo/bar.bar.bar.png'), - given('/foo/bar.bar.bar.gif'), - given('/foo/bar.bar.bar.jpg'), given('/foo/bar.bar.bar.json'), ]).expect({ namedParamA: 'bar.bar.bar' }) }) @@ -83,9 +77,6 @@ describe('Route helpers', function() { test(namedParams, () => { forCases([ given('/foo/bar.bar.bar.svg'), - given('/foo/bar.bar.bar.png'), - given('/foo/bar.bar.bar.gif'), - given('/foo/bar.bar.bar.jpg'), given('/foo/bar.bar.bar.json'), ]).expect({}) }) diff --git a/core/server/server.js b/core/server/server.js index 1fb650c908..c8000c115c 100644 --- a/core/server/server.js +++ b/core/server/server.js @@ -16,7 +16,10 @@ const { clearRequestCache, } = require('../base-service/legacy-request-handler') const { clearRegularUpdateCache } = require('../legacy/regular-update') -const { staticBadgeUrl } = require('../badge-urls/make-badge-url') +const { + staticBadgeUrl, + rasterRedirectUrl, +} = require('../badge-urls/make-badge-url') const log = require('./log') const sysMonitor = require('./monitor') const PrometheusMetrics = require('./prometheus-metrics') @@ -52,6 +55,7 @@ const publicConfigSchema = Joi.object({ cert: Joi.string(), }, redirectUrl: optionalUrl, + rasterUrl: optionalUrl, cors: { allowedOrigin: Joi.array() .items(optionalUrl) @@ -183,16 +187,43 @@ module.exports = class Server { * Set up Scoutcamp routes for 404/not found responses */ registerErrorHandlers() { - const { camp } = this + const { camp, config } = this + const { + public: { rasterUrl }, + } = config - camp.notfound(/\.(svg|png|gif|jpg|json)/, (query, match, end, request) => { + camp.notfound(/\.(gif|jpg)$/, (query, match, end, request) => { const [, format] = match - const svg = makeBadge({ - text: ['404', 'badge not found'], - color: 'red', - format, + makeSend('svg', request.res, end)( + makeBadge({ + text: ['410', `${format} no longer available`], + color: 'lightgray', + format: 'svg', + }) + ) + }) + + if (!rasterUrl) { + camp.notfound(/\.png$/, (query, match, end, request) => { + makeSend('svg', request.res, end)( + makeBadge({ + text: ['404', 'raster badges not available'], + color: 'lightgray', + format: 'svg', + }) + ) }) - makeSend(format, request.res, end)(svg) + } + + camp.notfound(/\.(svg|json)$/, (query, match, end, request) => { + const [, format] = match + makeSend(format, request.res, end)( + makeBadge({ + text: ['404', 'badge not found'], + color: 'red', + format, + }) + ) }) camp.notfound(/.*/, (query, match, end, request) => { @@ -219,6 +250,7 @@ module.exports = class Server { cacheHeaders: config.public.cacheHeaders, profiling: config.public.profiling, fetchLimitBytes: bytes(config.public.fetchLimit), + rasterUrl: config.public.rasterUrl, } ) ) @@ -234,34 +266,57 @@ module.exports = class Server { */ registerRedirects() { const { config, camp } = this + const { + public: { rasterUrl, redirectUrl }, + } = config + + if (rasterUrl) { + // Any badge, old version. + camp.route(/^\/([^/]+)\/(.+).png$/, (queryParams, match, end, ask) => { + const [, label, message] = match + const { color } = queryParams + + const redirectUrl = staticBadgeUrl({ + baseUrl: rasterUrl, + label, + message, + // Fixes https://github.com/badges/shields/issues/3260 + color: color ? color.toString() : undefined, + format: 'png', + }) + + ask.res.statusCode = 301 + ask.res.setHeader('Location', redirectUrl) + + // The redirect is permanent. + const cacheDuration = (365 * 24 * 3600) | 0 // 1 year + ask.res.setHeader('Cache-Control', `max-age=${cacheDuration}`) - // Any badge, old version. This route must be registered last. - camp.route(/^\/([^/]+)\/(.+).png$/, (queryParams, match, end, ask) => { - const [, label, message] = match - const { color } = queryParams - - const redirectUrl = staticBadgeUrl({ - label, - message, - // Fixes https://github.com/badges/shields/issues/3260 - color: color ? color.toString() : undefined, - format: 'png', + ask.res.end() }) - ask.res.statusCode = 301 - ask.res.setHeader('Location', redirectUrl) + // Redirect to the raster server for raster versions of modern badges. + camp.route(/\.png$/, (queryParams, match, end, ask) => { + ask.res.statusCode = 301 + ask.res.setHeader( + 'Location', + rasterRedirectUrl({ rasterUrl }, ask.req.url) + ) - // The redirect is permanent. - const cacheDuration = (365 * 24 * 3600) | 0 // 1 year - ask.res.setHeader('Cache-Control', `max-age=${cacheDuration}`) + // The redirect is permanent, though let's start off with a shorter + // cache time in case we've made mistakes. + // const cacheDuration = (365 * 24 * 3600) | 0 // 1 year + const cacheDuration = 3600 | 0 // 1 hour + ask.res.setHeader('Cache-Control', `max-age=${cacheDuration}`) - ask.res.end() - }) + ask.res.end() + }) + } - if (config.public.redirectUrl) { + if (redirectUrl) { camp.route(/^\/$/, (data, match, end, ask) => { ask.res.statusCode = 302 - ask.res.setHeader('Location', config.public.redirectUrl) + ask.res.setHeader('Location', redirectUrl) ask.res.end() }) } diff --git a/core/server/server.spec.js b/core/server/server.spec.js index 52d21891be..a3c8b81839 100644 --- a/core/server/server.spec.js +++ b/core/server/server.spec.js @@ -1,13 +1,8 @@ 'use strict' -const fs = require('fs') -const path = require('path') const { expect } = require('chai') -const isPng = require('is-png') const isSvg = require('is-svg') -const sinon = require('sinon') const portfinder = require('portfinder') -const svg2img = require('../../gh-badges/lib/svg-to-img') const got = require('../got-test-client') const { createTestServer } = require('./in-process-server-test-helpers') @@ -37,12 +32,17 @@ describe('The server', function() { .and.to.include('apple') }) - it('should produce colorscheme PNG badges', async function() { - const { statusCode, body } = await got(`${baseUrl}:fruit-apple-green.png`, { - encoding: null, - }) - expect(statusCode).to.equal(200) - expect(body).to.satisfy(isPng) + it('should redirect colorscheme PNG badges as configured', async function() { + const { statusCode, headers } = await got( + `${baseUrl}:fruit-apple-green.png`, + { + followRedirect: false, + } + ) + expect(statusCode).to.equal(301) + expect(headers.location).to.equal( + 'http://raster.example.com/:fruit-apple-green.png' + ) }) it('should preserve label case', async function() { @@ -118,28 +118,14 @@ describe('The server', function() { expect(headers.location).to.equal('http://badge-server.example.com') }) - context('with svg2img error', function() { - const expectedError = fs.readFileSync( - path.resolve(__dirname, 'error-pages', '500.html') - ) - - let toBufferStub - beforeEach(function() { - toBufferStub = sinon - .stub(svg2img._imageMagick.prototype, 'toBuffer') - .callsArgWith(1, Error('whoops')) - }) - afterEach(function() { - toBufferStub.restore() - }) - - it('should emit the 500 message', async function() { - const { statusCode, body } = await got( - `${baseUrl}:some_new-badge-green.png` - ) - // This emits status code 200, though 500 would be preferable. - expect(statusCode).to.equal(200) - expect(body).to.include(expectedError) + it('should return the 410 badge for obsolete formats', async function() { + const { statusCode, body } = await got(`${baseUrl}npm/v/express.jpg`, { + throwHttpErrors: false, }) + expect(statusCode).to.equal(404) + expect(body) + .to.satisfy(isSvg) + .and.to.include('410') + .and.to.include('jpg no longer available') }) }) diff --git a/doc/TUTORIAL.md b/doc/TUTORIAL.md index bfb4667c83..54cb90e3c5 100644 --- a/doc/TUTORIAL.md +++ b/doc/TUTORIAL.md @@ -42,11 +42,6 @@ install node and npm: https://nodejs.org/en/download/ In case you get the _"getaddrinfo ENOTFOUND localhost"_ error, visit [http://127.0.0.1:3000/](http://127.0.0.1:3000) instead or take a look at [this issue](https://github.com/angular/angular-cli/issues/2227#issuecomment-358036526). -You may also want to install -[ImageMagick](https://www.imagemagick.org/script/download.php). -This is an optional dependency needed for generating badges in raster format, -but you can get a dev copy running without it. - ## (3) Open an Issue Before you want to implement your service, you may want to [open an issue](https://github.com/badges/shields/issues/new?template=3_Badge_request.md) and describe what you have in mind: diff --git a/doc/production-hosting.md b/doc/production-hosting.md index e96241061b..973e7eaf44 100644 --- a/doc/production-hosting.md +++ b/doc/production-hosting.md @@ -73,12 +73,10 @@ Shields has mercifully little persistent state: inspectable. - The [request cache][] - The [regular-update cache][] - - The [raster cache][] [github auth admin endpoint]: https://github.com/badges/shields/blob/master/services/github/auth/admin.js [request cache]: https://github.com/badges/shields/blob/master/core/base-service/legacy-request-handler.js#L29-L30 [regular-update cache]: https://github.com/badges/shields/blob/master/core/legacy/regular-update.js -[raster cache]: https://github.com/badges/shields/blob/master/gh-badges/lib/svg-to-img.js#L9-L10 [oauth transfer]: https://developer.github.com/apps/managing-oauth-apps/transferring-ownership-of-an-oauth-app/ ## Configuration diff --git a/doc/self-hosting.md b/doc/self-hosting.md index 880d53e795..ae2ab7d485 100644 --- a/doc/self-hosting.md +++ b/doc/self-hosting.md @@ -93,6 +93,27 @@ machine. [shields.example.env]: ../shields.example.env +## Raster server + +If you want to host PNG badges, you can also self-host a [raster server][] +which points to your badge server. It's designed as a web function which is +tested on Zeit Now, though you may be able to run it on AWS Lambda. It's +built on the [micro][] framework, and comes with a `start` script that allows +it to run as a standalone Node service. + +- In your raster instance, set `BASE_URL` to your Shields instance, e.g. + `https://shields.example.co`. +- Optionally, in your Shields, instance, configure `RASTER_URL` to the base + URL, e.g. `https://raster.example.co`. This will send 301 redirects + for the legacy raster URLs instead of 404's. + +If anyone has set this up, more documentation on how to do this would be +welcome! It would also be nice to ship a Docker image that includes a +preconfigured raster server. + +[raster server]: https://github.com/badges/svg-to-image-proxy +[micro]: https://github.com/zeit/micro + ## Zeit Now To deploy using Zeit Now: diff --git a/gh-badges/lib/svg-to-img.js b/gh-badges/lib/svg-to-img.js index 61c04b616e..a8d11b0ca0 100644 --- a/gh-badges/lib/svg-to-img.js +++ b/gh-badges/lib/svg-to-img.js @@ -2,20 +2,10 @@ const { promisify } = require('util') const gm = require('gm') -const LruCache = require('./lru-cache') const imageMagick = gm.subClass({ imageMagick: true }) -// The following is an arbitrary limit (~1.5MB, 1.5kB/image). -const imgCache = new LruCache(1000) - async function svgToImg(svg, format) { - const cacheIndex = `${format}${svg}` - - if (imgCache.has(cacheIndex)) { - return imgCache.get(cacheIndex) - } - const svgBuffer = Buffer.from( `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>${svg}` ) @@ -26,14 +16,10 @@ async function svgToImg(svg, format) { .flatten() const toBuffer = chain.toBuffer.bind(chain) - const data = await promisify(toBuffer)(format) - - imgCache.set(cacheIndex, data) - return data + return promisify(toBuffer)(format) } module.exports = svgToImg // To simplify testing. -module.exports._imgCache = imgCache module.exports._imageMagick = imageMagick diff --git a/gh-badges/lib/svg-to-img.spec.js b/gh-badges/lib/svg-to-img.spec.js index 3406d19db0..cd46df6596 100644 --- a/gh-badges/lib/svg-to-img.spec.js +++ b/gh-badges/lib/svg-to-img.spec.js @@ -2,32 +2,13 @@ const { expect } = require('chai') const isPng = require('is-png') -const sinon = require('sinon') const svg2img = require('./svg-to-img') const makeBadge = require('./make-badge') describe('The rasterizer', function() { - let cacheGet - beforeEach(function() { - cacheGet = sinon.spy(svg2img._imgCache, 'get') - }) - afterEach(function() { - cacheGet.restore() - }) - it('should produce PNG', async function() { const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' }) const data = await svg2img(svg, 'png') expect(data).to.satisfy(isPng) }) - - it('should cache its results', async function() { - const svg = makeBadge({ text: ['will-this', 'be-cached?'], format: 'svg' }) - const data1 = await svg2img(svg, 'png') - expect(data1).to.satisfy(isPng) - expect(cacheGet).not.to.have.been.called - const data2 = await svg2img(svg, 'png') - expect(data2).to.satisfy(isPng) - expect(cacheGet).to.have.been.calledOnce - }) }) -- GitLab