Files
shields/lib/request-handler.spec.js
Paul Melnikow 5c665a70da Overhaul initialization pattern for server + server tests (#2519)
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
2018-12-23 11:24:22 -05:00

222 lines
7.3 KiB
JavaScript

'use strict'
const { expect } = require('chai')
const fetch = require('node-fetch')
const portfinder = require('portfinder')
const analytics = require('./analytics')
const Camp = require('camp')
const { makeBadgeData: getBadgeData } = require('./badge-data')
const {
handleRequest,
clearRequestCache,
_requestCache,
} = require('./request-handler')
async function performTwoRequests(baseUrl, first, second) {
expect((await fetch(`${baseUrl}${first}`)).ok).to.be.true
expect((await fetch(`${baseUrl}${second}`)).ok).to.be.true
}
function fakeHandler(queryParams, match, sendBadge, request) {
const [, someValue, format] = match
const badgeData = getBadgeData('testing', queryParams)
badgeData.text[1] = someValue
sendBadge(format, badgeData)
}
describe('The request handler', function() {
before(analytics.load)
let port, baseUrl
beforeEach(async function() {
port = await portfinder.getPortPromise()
baseUrl = `http://127.0.0.1:${port}`
})
let camp
beforeEach(function(done) {
camp = Camp.start({ port, hostname: '::' })
camp.on('listening', () => done())
})
afterEach(function(done) {
clearRequestCache()
if (camp) {
camp.close(() => done())
camp = null
}
})
const standardCacheHeaders = { defaultCacheLengthSeconds: 120 }
describe('the options object calling style', function() {
beforeEach(function() {
camp.route(
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
handleRequest(standardCacheHeaders, { handler: fakeHandler })
)
})
it('should return the expected response', async function() {
const res = await fetch(`${baseUrl}/testing/123.json`)
expect(res.ok).to.be.true
expect(await res.json()).to.deep.equal({ name: 'testing', value: '123' })
})
})
describe('the function shorthand calling style', function() {
beforeEach(function() {
camp.route(
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
handleRequest(standardCacheHeaders, fakeHandler)
)
})
it('should return the expected response', async function() {
const res = await fetch(`${baseUrl}/testing/123.json`)
expect(res.ok).to.be.true
expect(await res.json()).to.deep.equal({ name: 'testing', value: '123' })
})
})
describe('caching', function() {
describe('standard query parameters', function() {
let handlerCallCount
beforeEach(function() {
handlerCallCount = 0
})
function register({ cacheHeaderConfig }) {
camp.route(
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
handleRequest(
cacheHeaderConfig,
(queryParams, match, sendBadge, request) => {
++handlerCallCount
fakeHandler(queryParams, match, sendBadge, request)
}
)
)
}
context('With standard cache settings', function() {
beforeEach(function() {
register({ cacheHeaderConfig: standardCacheHeaders })
})
it('should cache identical requests', async function() {
await performTwoRequests(
baseUrl,
'/testing/123.svg',
'/testing/123.svg'
)
expect(handlerCallCount).to.equal(1)
})
it('should differentiate known query parameters', async function() {
await performTwoRequests(
baseUrl,
'/testing/123.svg?label=foo',
'/testing/123.svg?label=bar'
)
expect(handlerCallCount).to.equal(2)
})
it('should ignore unknown query parameters', async function() {
await performTwoRequests(
baseUrl,
'/testing/123.svg?foo=1',
'/testing/123.svg?foo=2'
)
expect(handlerCallCount).to.equal(1)
})
})
it('should set the expires header to current time + defaultCacheLengthSeconds', async function() {
register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 900 } })
const res = await fetch(`${baseUrl}/testing/123.json`)
const expectedExpiry = new Date(
+new Date(res.headers.get('date')) + 900000
).toGMTString()
expect(res.headers.get('expires')).to.equal(expectedExpiry)
expect(res.headers.get('cache-control')).to.equal('max-age=900')
})
it('should set the expires header to current time + maxAge', async function() {
register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 0 } })
const res = await fetch(`${baseUrl}/testing/123.json?maxAge=3600`)
const expectedExpiry = new Date(
+new Date(res.headers.get('date')) + 3600000
).toGMTString()
expect(res.headers.get('expires')).to.equal(expectedExpiry)
expect(res.headers.get('cache-control')).to.equal('max-age=3600')
})
it('should ignore maxAge if maxAge < defaultCacheLengthSeconds', async function() {
register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 600 } })
const res = await fetch(`${baseUrl}/testing/123.json?maxAge=300`)
const expectedExpiry = new Date(
+new Date(res.headers.get('date')) + 600000
).toGMTString()
expect(res.headers.get('expires')).to.equal(expectedExpiry)
expect(res.headers.get('cache-control')).to.equal('max-age=600')
})
it('should set Cache-Control: no-cache, no-store, must-revalidate if maxAge=0', async function() {
register({ cacheHeaderConfig: { defaultCacheLengthSeconds: 0 } })
const res = await fetch(`${baseUrl}/testing/123.json`)
expect(res.headers.get('expires')).to.equal(res.headers.get('date'))
expect(res.headers.get('cache-control')).to.equal(
'no-cache, no-store, must-revalidate'
)
})
describe('the cache key', function() {
beforeEach(function() {
register({ cacheHeaderConfig: standardCacheHeaders })
})
const expectedCacheKey = '/testing/123.json?colorB=123&label=foo'
it('should match expected and use canonical order - 1', async function() {
const res = await fetch(
`${baseUrl}/testing/123.json?colorB=123&label=foo`
)
expect(res.ok).to.be.true
expect(_requestCache.cache).to.have.keys(expectedCacheKey)
})
it('should match expected and use canonical order - 2', async function() {
const res = await fetch(
`${baseUrl}/testing/123.json?label=foo&colorB=123`
)
expect(res.ok).to.be.true
expect(_requestCache.cache).to.have.keys(expectedCacheKey)
})
})
})
describe('custom query parameters', function() {
let handlerCallCount
beforeEach(function() {
handlerCallCount = 0
camp.route(
/^\/testing\/([^/]+)\.(svg|png|gif|jpg|json)$/,
handleRequest(standardCacheHeaders, {
queryParams: ['foo'],
handler: (queryParams, match, sendBadge, request) => {
++handlerCallCount
fakeHandler(queryParams, match, sendBadge, request)
},
})
)
})
it('should differentiate them', async function() {
await performTwoRequests(
baseUrl,
'/testing/123.svg?foo=1',
'/testing/123.svg?foo=2'
)
expect(handlerCallCount).to.equal(2)
})
})
})
})