Because `server.js` was long a monolith, there are a bunch of shims in place to facilitate unit testing. A few of the test suites share port 1111 which means if one of them fails to set up, the port won't be freed and other unrelated tests will fail. Some of the tests which trigger server setup include timeouts which were added to give setup code time to run. In one the test suites, we actually modify `process.argv`, which seems completely gross. This implements a few changes which improve this: 1. Separate the server from the server startup script, splitting out `lib/server.js`. 2. Inject config into the server and validate the config schema. 3. Inject config into the service test runner. 4. Use `portfinder`, a popular utility for grabbing open ports during testing. 5. Switch more of the setup code from callbacks to async-await. Overall it leaves everything acting more reliably and looking rather cleaner, if in a few places more verbose. It also fixes the root cause of #1455, a `setTimeout` in `rate-limit`. Off and on during development of this changeset, Mocha would decide not to exit, and that turned out to be the culprit. Fix #1455
141 lines
4.3 KiB
JavaScript
141 lines
4.3 KiB
JavaScript
'use strict'
|
|
|
|
const { expect } = require('chai')
|
|
const fetch = require('node-fetch')
|
|
const fs = require('fs')
|
|
const isPng = require('is-png')
|
|
const isSvg = require('is-svg')
|
|
const path = require('path')
|
|
const sinon = require('sinon')
|
|
const portfinder = require('portfinder')
|
|
const svg2img = require('../gh-badges/lib/svg-to-img')
|
|
const { createTestServer } = require('./in-process-server-test-helpers')
|
|
|
|
describe('The server', function() {
|
|
let server, baseUrl
|
|
before('Start the server', async function() {
|
|
const port = await portfinder.getPortPromise()
|
|
server = createTestServer({ port })
|
|
baseUrl = server.baseUrl
|
|
await server.start()
|
|
})
|
|
after('Shut down the server', async function() {
|
|
if (server) {
|
|
await server.stop()
|
|
}
|
|
server = undefined
|
|
})
|
|
|
|
it('should produce colorscheme badges', async function() {
|
|
const res = await fetch(`${baseUrl}/:fruit-apple-green.svg`)
|
|
expect(res.ok).to.be.true
|
|
expect(await res.text())
|
|
.to.satisfy(isSvg)
|
|
.and.to.include('fruit')
|
|
.and.to.include('apple')
|
|
})
|
|
|
|
it('should produce colorscheme PNG badges', async function() {
|
|
const res = await fetch(`${baseUrl}/:fruit-apple-green.png`)
|
|
expect(res.ok).to.be.true
|
|
expect(await res.buffer()).to.satisfy(isPng)
|
|
})
|
|
|
|
it('should preserve label case', async function() {
|
|
const res = await fetch(`${baseUrl}/:fRuiT-apple-green.svg`)
|
|
expect(res.ok).to.be.true
|
|
expect(await res.text())
|
|
.to.satisfy(isSvg)
|
|
.and.to.include('fRuiT')
|
|
})
|
|
|
|
// https://github.com/badges/shields/pull/1319
|
|
it('should not crash with a numeric logo', async function() {
|
|
const res = await fetch(`${baseUrl}/:fruit-apple-green.svg?logo=1`)
|
|
expect(res.ok).to.be.true
|
|
expect(await res.text())
|
|
.to.satisfy(isSvg)
|
|
.and.to.include('fruit')
|
|
.and.to.include('apple')
|
|
})
|
|
|
|
it('should not crash with a numeric link', async function() {
|
|
const res = await fetch(`${baseUrl}/:fruit-apple-green.svg?link=1`)
|
|
expect(res.ok).to.be.true
|
|
expect(await res.text())
|
|
.to.satisfy(isSvg)
|
|
.and.to.include('fruit')
|
|
.and.to.include('apple')
|
|
})
|
|
|
|
it('should not crash with a boolean link', async function() {
|
|
const res = await fetch(`${baseUrl}/:fruit-apple-green.svg?link=true`)
|
|
expect(res.ok).to.be.true
|
|
expect(await res.text())
|
|
.to.satisfy(isSvg)
|
|
.and.to.include('fruit')
|
|
.and.to.include('apple')
|
|
})
|
|
|
|
it('should return the 404 badge for unknown badges', async function() {
|
|
const res = await fetch(`${baseUrl}/this/is/not/a/badge.svg`)
|
|
expect(res.status).to.equal(404)
|
|
expect(await res.text())
|
|
.to.satisfy(isSvg)
|
|
.and.to.include('404')
|
|
.and.to.include('badge not found')
|
|
})
|
|
|
|
it('should return the 404 html page for rando links', async function() {
|
|
const res = await fetch(`${baseUrl}/this/is/most/definitely/not/a/badge.js`)
|
|
expect(res.status).to.equal(404)
|
|
expect(await res.text()).to.include('blood, toil, tears and sweat')
|
|
})
|
|
|
|
context('with svg2img error', function() {
|
|
const expectedError = fs.readFileSync(
|
|
path.resolve(__dirname, '..', 'public', '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 res = await fetch(`${baseUrl}/:some_new-badge-green.png`)
|
|
// This emits status code 200, though 500 would be preferable.
|
|
expect(res.status).to.equal(200)
|
|
expect(await res.text()).to.include(expectedError)
|
|
})
|
|
})
|
|
|
|
describe('analytics endpoint', function() {
|
|
it('should return analytics in the expected format', async function() {
|
|
const res = await fetch(`${baseUrl}/$analytics/v1`)
|
|
expect(res.ok).to.be.true
|
|
const json = await res.json()
|
|
const expectedKeys = [
|
|
'vendorMonthly',
|
|
'rawMonthly',
|
|
'vendorFlatMonthly',
|
|
'rawFlatMonthly',
|
|
'vendorFlatSquareMonthly',
|
|
'rawFlatSquareMonthly',
|
|
]
|
|
expect(json).to.have.all.keys(...expectedKeys)
|
|
|
|
Object.values(json).forEach(stats => {
|
|
expect(stats)
|
|
.to.be.an('array')
|
|
.with.length(36)
|
|
})
|
|
})
|
|
})
|
|
})
|