This is the code part of #3027, following [this article](https://www.viget.com/articles/heroku-cloudflare-the-right-way/) and using [this middleware](https://github.com/clive-io/cloudflare-middleware). I pulled in the `addHandlerAtIndex()` function @chris48s wrote for #5574. The middleware isn't perfect for scoutcamp, since it relies on `req.ip` which is something set by Express. However, the other solutions I found were either explicitly deprecated ([cloudflare-ip](https://www.npmjs.com/package/cloudflare-ip)) or relied on dynamically fetching the list of Cloudflare hosts ([cloudflare-ips](https://www.npmjs.com/package/cloudflare-ips)), which seems unnecessary as this list has not changed in several years. I've left this off to start, so we can test it in production using an env var before we make it the production default.
355 lines
12 KiB
JavaScript
355 lines
12 KiB
JavaScript
'use strict'
|
|
|
|
const { expect } = require('chai')
|
|
const isSvg = require('is-svg')
|
|
const config = require('config')
|
|
const got = require('../got-test-client')
|
|
const Server = require('./server')
|
|
const { createTestServer } = require('./in-process-server-test-helpers')
|
|
|
|
describe('The server', function () {
|
|
describe('running', function () {
|
|
let server, baseUrl
|
|
before('Start the server', async function () {
|
|
// Fixes https://github.com/badges/shields/issues/2611
|
|
this.timeout(10000)
|
|
server = await createTestServer()
|
|
baseUrl = server.baseUrl
|
|
await server.start()
|
|
})
|
|
after('Shut down the server', async function () {
|
|
if (server) {
|
|
await server.stop()
|
|
}
|
|
server = undefined
|
|
})
|
|
|
|
it('should allow strings for port', async function () {
|
|
// fixes #4391 - This allows the app to be run using iisnode, which uses a named pipe for the port.
|
|
const pipeServer = await createTestServer({
|
|
public: {
|
|
bind: {
|
|
port: '\\\\.\\pipe\\9c137306-7c4d-461e-b7cf-5213a3939ad6',
|
|
},
|
|
},
|
|
})
|
|
expect(pipeServer).to.not.be.undefined
|
|
})
|
|
|
|
it('should produce colorscheme badges', async function () {
|
|
const { statusCode, body } = await got(`${baseUrl}:fruit-apple-green.svg`)
|
|
expect(statusCode).to.equal(200)
|
|
expect(body)
|
|
.to.satisfy(isSvg)
|
|
.and.to.include('fruit')
|
|
.and.to.include('apple')
|
|
})
|
|
|
|
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.test/:fruit-apple-green.png'
|
|
)
|
|
})
|
|
|
|
it('should redirect modern PNG badges as configured', async function () {
|
|
const { statusCode, headers } = await got(`${baseUrl}npm/v/express.png`, {
|
|
followRedirect: false,
|
|
})
|
|
expect(statusCode).to.equal(301)
|
|
expect(headers.location).to.equal(
|
|
'http://raster.example.test/npm/v/express.png'
|
|
)
|
|
})
|
|
|
|
it('should produce json badges', async function () {
|
|
const { statusCode, body, headers } = await got(
|
|
`${baseUrl}twitter/follow/_Pyves.json`
|
|
)
|
|
expect(statusCode).to.equal(200)
|
|
expect(headers['content-type']).to.equal('application/json')
|
|
expect(() => JSON.parse(body)).not.to.throw()
|
|
})
|
|
|
|
it('should preserve label case', async function () {
|
|
const { statusCode, body } = await got(`${baseUrl}:fRuiT-apple-green.svg`)
|
|
expect(statusCode).to.equal(200)
|
|
expect(body).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 { statusCode, body } = await got(
|
|
`${baseUrl}:fruit-apple-green.svg?logo=1`
|
|
)
|
|
expect(statusCode).to.equal(200)
|
|
expect(body)
|
|
.to.satisfy(isSvg)
|
|
.and.to.include('fruit')
|
|
.and.to.include('apple')
|
|
})
|
|
|
|
it('should not crash with a numeric link', async function () {
|
|
const { statusCode, body } = await got(
|
|
`${baseUrl}:fruit-apple-green.svg?link=1`
|
|
)
|
|
expect(statusCode).to.equal(200)
|
|
expect(body)
|
|
.to.satisfy(isSvg)
|
|
.and.to.include('fruit')
|
|
.and.to.include('apple')
|
|
})
|
|
|
|
it('should not crash with a boolean link', async function () {
|
|
const { statusCode, body } = await got(
|
|
`${baseUrl}:fruit-apple-green.svg?link=true`
|
|
)
|
|
expect(statusCode).to.equal(200)
|
|
expect(body)
|
|
.to.satisfy(isSvg)
|
|
.and.to.include('fruit')
|
|
.and.to.include('apple')
|
|
})
|
|
|
|
it('should return the 404 badge for unknown badges', async function () {
|
|
const { statusCode, body } = await got(
|
|
`${baseUrl}this/is/not/a/badge.svg`,
|
|
{
|
|
throwHttpErrors: false,
|
|
}
|
|
)
|
|
expect(statusCode).to.equal(404)
|
|
expect(body)
|
|
.to.satisfy(isSvg)
|
|
.and.to.include('404')
|
|
.and.to.include('badge not found')
|
|
})
|
|
|
|
it('should return the 404 badge page for rando links', async function () {
|
|
const { statusCode, body } = await got(
|
|
`${baseUrl}this/is/most/definitely/not/a/badge.js`,
|
|
{
|
|
throwHttpErrors: false,
|
|
}
|
|
)
|
|
expect(statusCode).to.equal(404)
|
|
expect(body)
|
|
.to.satisfy(isSvg)
|
|
.and.to.include('404')
|
|
.and.to.include('badge not found')
|
|
})
|
|
|
|
it('should redirect the root as configured', async function () {
|
|
const { statusCode, headers } = await got(baseUrl, {
|
|
followRedirect: false,
|
|
})
|
|
|
|
expect(statusCode).to.equal(302)
|
|
// This value is set in `config/test.yml`
|
|
expect(headers.location).to.equal('http://frontend.example.test')
|
|
})
|
|
|
|
it('should return the 410 badge for obsolete formats', async function () {
|
|
const { statusCode, body } = await got(`${baseUrl}npm/v/express.jpg`, {
|
|
throwHttpErrors: false,
|
|
})
|
|
// TODO It would be nice if this were 404 or 410.
|
|
expect(statusCode).to.equal(200)
|
|
expect(body)
|
|
.to.satisfy(isSvg)
|
|
.and.to.include('410')
|
|
.and.to.include('jpg no longer available')
|
|
})
|
|
})
|
|
|
|
context('`requireCloudflare` is enabled', function () {
|
|
let server
|
|
afterEach(async function () {
|
|
if (server) {
|
|
server.stop()
|
|
}
|
|
})
|
|
|
|
it('should reject requests from localhost with an empty 200 response', async function () {
|
|
this.timeout(10000)
|
|
server = await createTestServer({ public: { requireCloudflare: true } })
|
|
await server.start()
|
|
|
|
const { statusCode, body } = await got(
|
|
`${server.baseUrl}badge/foo-bar-blue.svg`
|
|
)
|
|
|
|
expect(statusCode).to.be.equal(200)
|
|
expect(body).to.equal('')
|
|
})
|
|
})
|
|
|
|
describe('configuration', function () {
|
|
let server
|
|
afterEach(async function () {
|
|
if (server) {
|
|
server.stop()
|
|
}
|
|
})
|
|
|
|
it('should allow to enable prometheus metrics', async function () {
|
|
// Fixes https://github.com/badges/shields/issues/2611
|
|
this.timeout(10000)
|
|
server = await createTestServer({
|
|
public: {
|
|
metrics: { prometheus: { enabled: true, endpointEnabled: true } },
|
|
},
|
|
})
|
|
await server.start()
|
|
|
|
const { statusCode } = await got(`${server.baseUrl}metrics`)
|
|
|
|
expect(statusCode).to.be.equal(200)
|
|
})
|
|
|
|
it('should allow to disable prometheus metrics', async function () {
|
|
// Fixes https://github.com/badges/shields/issues/2611
|
|
this.timeout(10000)
|
|
server = await createTestServer({
|
|
public: {
|
|
metrics: { prometheus: { enabled: true, endpointEnabled: false } },
|
|
},
|
|
})
|
|
await server.start()
|
|
|
|
const { statusCode } = await got(`${server.baseUrl}metrics`, {
|
|
throwHttpErrors: false,
|
|
})
|
|
|
|
expect(statusCode).to.be.equal(404)
|
|
})
|
|
})
|
|
|
|
describe('configuration validation', function () {
|
|
describe('influx', function () {
|
|
let customConfig
|
|
beforeEach(function () {
|
|
customConfig = config.util.toObject()
|
|
customConfig.public.metrics.influx = {
|
|
enabled: true,
|
|
url: 'http://localhost:8081/telegraf',
|
|
timeoutMilliseconds: 1000,
|
|
intervalSeconds: 2,
|
|
instanceIdFrom: 'random',
|
|
instanceIdEnvVarName: 'INSTANCE_ID',
|
|
hostnameAliases: { 'metrics-hostname': 'metrics-hostname-alias' },
|
|
envLabel: 'test-env',
|
|
}
|
|
customConfig.private = {
|
|
influx_username: 'telegraf',
|
|
influx_password: 'telegrafpass',
|
|
}
|
|
})
|
|
|
|
it('should not require influx configuration', function () {
|
|
delete customConfig.public.metrics.influx
|
|
expect(() => new Server(config.util.toObject())).to.not.throw()
|
|
})
|
|
|
|
it('should require url when influx configuration is enabled', function () {
|
|
delete customConfig.public.metrics.influx.url
|
|
expect(() => new Server(customConfig)).to.throw(
|
|
'"metrics.influx.url" is required'
|
|
)
|
|
})
|
|
|
|
it('should not require url when influx configuration is disabled', function () {
|
|
customConfig.public.metrics.influx.enabled = false
|
|
delete customConfig.public.metrics.influx.url
|
|
expect(() => new Server(customConfig)).to.not.throw()
|
|
})
|
|
|
|
it('should require timeoutMilliseconds when influx configuration is enabled', function () {
|
|
delete customConfig.public.metrics.influx.timeoutMilliseconds
|
|
expect(() => new Server(customConfig)).to.throw(
|
|
'"metrics.influx.timeoutMilliseconds" is required'
|
|
)
|
|
})
|
|
|
|
it('should require intervalSeconds when influx configuration is enabled', function () {
|
|
delete customConfig.public.metrics.influx.intervalSeconds
|
|
expect(() => new Server(customConfig)).to.throw(
|
|
'"metrics.influx.intervalSeconds" is required'
|
|
)
|
|
})
|
|
|
|
it('should require instanceIdFrom when influx configuration is enabled', function () {
|
|
delete customConfig.public.metrics.influx.instanceIdFrom
|
|
expect(() => new Server(customConfig)).to.throw(
|
|
'"metrics.influx.instanceIdFrom" is required'
|
|
)
|
|
})
|
|
|
|
it('should require instanceIdEnvVarName when instanceIdFrom is env-var', function () {
|
|
customConfig.public.metrics.influx.instanceIdFrom = 'env-var'
|
|
delete customConfig.public.metrics.influx.instanceIdEnvVarName
|
|
expect(() => new Server(customConfig)).to.throw(
|
|
'"metrics.influx.instanceIdEnvVarName" is required'
|
|
)
|
|
})
|
|
|
|
it('should allow instanceIdFrom = hostname', function () {
|
|
customConfig.public.metrics.influx.instanceIdFrom = 'hostname'
|
|
expect(() => new Server(customConfig)).to.not.throw()
|
|
})
|
|
|
|
it('should allow instanceIdFrom = env-var', function () {
|
|
customConfig.public.metrics.influx.instanceIdFrom = 'env-var'
|
|
expect(() => new Server(customConfig)).to.not.throw()
|
|
})
|
|
|
|
it('should allow instanceIdFrom = random', function () {
|
|
customConfig.public.metrics.influx.instanceIdFrom = 'random'
|
|
expect(() => new Server(customConfig)).to.not.throw()
|
|
})
|
|
|
|
it('should require envLabel when influx configuration is enabled', function () {
|
|
delete customConfig.public.metrics.influx.envLabel
|
|
expect(() => new Server(customConfig)).to.throw(
|
|
'"metrics.influx.envLabel" is required'
|
|
)
|
|
})
|
|
|
|
it('should not require hostnameAliases', function () {
|
|
delete customConfig.public.metrics.influx.hostnameAliases
|
|
expect(() => new Server(customConfig)).to.not.throw()
|
|
})
|
|
|
|
it('should allow empty hostnameAliases', function () {
|
|
customConfig.public.metrics.influx.hostnameAliases = {}
|
|
expect(() => new Server(customConfig)).to.not.throw()
|
|
})
|
|
|
|
it('should require username when influx configuration is enabled', function () {
|
|
delete customConfig.private.influx_username
|
|
expect(() => new Server(customConfig)).to.throw(
|
|
'Private configuration is invalid. Check these paths: influx_username'
|
|
)
|
|
})
|
|
|
|
it('should require password when influx configuration is enabled', function () {
|
|
delete customConfig.private.influx_password
|
|
expect(() => new Server(customConfig)).to.throw(
|
|
'Private configuration is invalid. Check these paths: influx_password'
|
|
)
|
|
})
|
|
|
|
it('should allow other private keys', function () {
|
|
customConfig.private.gh_token = 'my-token'
|
|
expect(() => new Server(customConfig)).to.not.throw()
|
|
})
|
|
})
|
|
})
|
|
})
|