scripts/fixtures/fixtures-server.js (76 lines of code) (raw):
const { fixtures } = require('./fixtures');
const { merge } = require('lodash');
/**
* For a given relative path in production,
* retrieve the JSON required to render on DCR
*
* @param {string} path
* @returns {string}
*/
const getProdDataUrl = (path) =>
`https://theguardian.com/${path}.json?dcr=true`;
const isStringTuple = (_) => typeof _[1] === 'string';
/**
* @param {string} path
* @returns {Promise<Record<string, unknown>>}
*/
const fetchDcrDataModel = async (path, _headers) => {
const url = getProdDataUrl(path);
try {
// Forward cookies and x-gu headers
const headers = Object.fromEntries(
Object.entries(_headers)
.filter(
([key]) =>
key.toLowerCase() === 'cookie' ||
key.toLowerCase().startsWith('x-gu-'),
)
.filter(isStringTuple),
);
const res = await fetch(url, { headers });
if (!res.ok) {
return undefined;
}
const json = await res.json();
return json;
} catch (err) {
console.error(err);
return undefined;
}
};
/**
* Add additional endpoints that proxy Frontend, merging into the resulting JSON
* any overrides provided as a fixture.
*
* These can then be used by an E2E test in order to fix certain behavior about
* the system-under-test e.g. override a switch state to always be true.
*
* @param {import('webpack-dev-server')} devServer
*/
const setupFixturesServer = (devServer) => {
if (!devServer) {
throw new Error('webpack-dev-server is not defined');
}
devServer.app.get(
'/renderFixture/*.json',
/**
* Pass a base-64 encoded JSON fixture as a query parameter. This is
* merged into the DCR data retrieved from frontend for the given path
*
* `http://localhost:PORT/renderFixture/uk.json?fixture=...`
*/
async function (req, res) {
const path = req.params[0];
const fixtureQuery = req.query['fixture'];
const fixture = JSON.parse(
Buffer.from(fixtureQuery, 'base64').toString(),
);
// Fetch the JSON for the given path from PROD Frontend
const dataModel = await fetchDcrDataModel(path, req.headers);
if (!dataModel) {
console.error(
'Something went wrong retrieving DCR data from PROD',
);
return res.status(503).send();
}
// Merge the fixture into the data model
// Note that this will be a deep merge
merge(dataModel, fixture);
return res.json(dataModel);
},
);
devServer.app.get(
'/renderFixtureWithId/:fixtureId/*.json',
/**
* Take a fixture stored with the fixtures server and apply it by
* passing in its ID
*
* These fixtures are stored in fixtures.js in an object keyed by the
* fixture ID, and are merged into the JSON returned for the rest of the
* path. For example, to override the data for the /uk path with fixture
* id 'foo' you could call:
*
* `http://localhost:PORT/renderFixtureWithId/fixtureFoo/uk.json`
*/
async function (req, res) {
const path = req.params[0];
const fixtureId = req.params.fixtureId;
const fixture = fixtures[fixtureId];
if (!fixture) {
console.error(`Fixture with id ${fixtureId} not found`);
return res.status(404).send();
}
// Fetch the JSON for the given path from PROD Frontend
const dataModel = await fetchDcrDataModel(path, req.headers);
if (!dataModel) {
console.error(
'Something went wrong retrieving DCR data from PROD',
);
return res.status(503).send();
}
// Merge the fixture into the data model
// Note that this will be a deep merge
merge(dataModel, fixture);
return res.json(dataModel);
},
);
};
module.exports = {
setupFixturesServer,
};