ccbcba0969
refs: https://github.com/TryGhost/DevOps/issues/78 This speeds up the tests by another 30 seconds on my local machine, and hopefully takes some time off in CI too
156 lines
6.2 KiB
JavaScript
156 lines
6.2 KiB
JavaScript
// express-test.js
|
|
const base = require('@playwright/test');
|
|
const {promisify} = require('util');
|
|
const {spawn, exec} = require('child_process');
|
|
const {setupGhost, setupMailgun, enableLabs, setupStripe, getStripeAccountId, generateStripeIntegrationToken} = require('../utils/e2e-browser-utils');
|
|
const {allowStripe, mockMail} = require('../../utils/e2e-framework-mock-manager');
|
|
const MailgunClient = require('@tryghost/mailgun-client');
|
|
const sinon = require('sinon');
|
|
const ObjectID = require('bson-objectid').default;
|
|
const Stripe = require('stripe').Stripe;
|
|
const configUtils = require('../../utils/configUtils');
|
|
|
|
const startWebhookServer = (port) => {
|
|
const command = `stripe listen --forward-connect-to http://127.0.0.1:${port}/members/webhooks/stripe/ ${process.env.CI ? `--api-key ${process.env.STRIPE_SECRET_KEY}` : ''}`.trim();
|
|
const webhookServer = spawn(command.split(' ')[0], command.split(' ').slice(1));
|
|
|
|
// Adding event listeners here seems to prevent heisenbug where webhooks aren't received
|
|
webhookServer.stdout.on('data', () => {});
|
|
webhookServer.stderr.on('data', () => {});
|
|
|
|
return webhookServer;
|
|
};
|
|
|
|
const getWebhookSecret = async () => {
|
|
const command = `stripe listen --print-secret ${process.env.CI ? `--api-key ${process.env.STRIPE_SECRET_KEY}` : ''}`.trim();
|
|
const webhookSecret = (await promisify(exec)(command)).stdout;
|
|
return webhookSecret.toString().trim();
|
|
};
|
|
|
|
// Global promises for webhook secret / Stripe integration token
|
|
const webhookSecretPromise = getWebhookSecret();
|
|
|
|
/**
|
|
* @type {import('@playwright/test').TestType<
|
|
* import('@playwright/test').PlaywrightTestArgs &
|
|
* import('@playwright/test').PlaywrightTestOptions &
|
|
* import('@playwright/test').PlaywrightWorkerArgs &
|
|
* import('@playwright/test').PlaywrightWorkerOptions
|
|
* >}
|
|
* @property {import('@playwright/test').Page} sharedPage
|
|
*/
|
|
module.exports = base.test.extend({
|
|
baseURL: async ({port, baseURL}, use) => {
|
|
// Replace the port in baseURL with the one we got from the port fixture
|
|
const url = new URL(baseURL);
|
|
url.port = port.toString();
|
|
await use(url.toString());
|
|
},
|
|
|
|
storageState: async ({ghost}, use) => {
|
|
await use(ghost.state);
|
|
},
|
|
|
|
sharedPage: [async ({browser}, use) => {
|
|
const page = await browser.newPage();
|
|
await use(page);
|
|
}, {scope: 'worker'}],
|
|
|
|
// eslint-disable-next-line no-empty-pattern
|
|
port: [async ({}, use, workerInfo) => {
|
|
await use(2369 + workerInfo.parallelIndex);
|
|
}, {scope: 'worker'}],
|
|
|
|
ghost: [async ({browser, port}, use, workerInfo) => {
|
|
// Do not initialise database before this block
|
|
const currentDate = new Date();
|
|
const formattedDate = `${currentDate.getFullYear()}-${String(currentDate.getMonth() + 1).padStart(2, '0')}-${String(currentDate.getDate()).padStart(2, '0')}-${String(currentDate.getHours()).padStart(2, '0')}-${String(currentDate.getMinutes()).padStart(2, '0')}-${String(currentDate.getSeconds()).padStart(2, '0')}`;
|
|
process.env.database__connection__filename = `/tmp/ghost-playwright.${workerInfo.workerIndex}.${formattedDate}.db`;
|
|
configUtils.set('database:connection:filename', process.env.database__connection__filename);
|
|
configUtils.set('server:port', port);
|
|
configUtils.set('url', `http://127.0.0.1:${port}`);
|
|
|
|
const stripeAccountId = await getStripeAccountId();
|
|
const stripeIntegrationToken = await generateStripeIntegrationToken(stripeAccountId);
|
|
|
|
const WebhookManager = require('../../../../stripe/lib/WebhookManager');
|
|
const originalParseWebhook = WebhookManager.prototype.parseWebhook;
|
|
const sandbox = sinon.createSandbox();
|
|
sandbox.stub(WebhookManager.prototype, 'parseWebhook').callsFake(function (body, signature) {
|
|
const parsedBody = JSON.parse(body);
|
|
if (!('account' in parsedBody)) {
|
|
throw new Error('Webhook without account');
|
|
} else if (parsedBody.account !== stripeAccountId) {
|
|
throw new Error('Webhook for wrong account');
|
|
} else {
|
|
return originalParseWebhook.call(this, body, signature);
|
|
}
|
|
});
|
|
|
|
const StripeAPI = require('../../../../stripe/lib/StripeAPI');
|
|
const originalStripeConfigure = StripeAPI.prototype.configure;
|
|
sandbox.stub(StripeAPI.prototype, 'configure').callsFake(function (stripeConfig) {
|
|
originalStripeConfigure.call(this, stripeConfig);
|
|
if (stripeConfig) {
|
|
this._stripe = new Stripe(stripeConfig.secretKey, {
|
|
apiVersion: '2020-08-27',
|
|
stripeAccount: stripeAccountId
|
|
});
|
|
}
|
|
});
|
|
|
|
const stripeServer = startWebhookServer(port);
|
|
|
|
process.env.WEBHOOK_SECRET = await webhookSecretPromise;
|
|
|
|
sandbox.stub(MailgunClient.prototype, 'getInstance').returns({
|
|
// @ts-ignore
|
|
messages: {
|
|
create: async function () {
|
|
return {
|
|
id: `mailgun-mock-id-${ObjectID().toHexString()}`
|
|
};
|
|
}
|
|
}
|
|
});
|
|
|
|
mockMail();
|
|
|
|
const {startGhost} = require('../../utils/e2e-framework');
|
|
const server = await startGhost({
|
|
frontend: true,
|
|
server: true,
|
|
backend: true
|
|
});
|
|
|
|
// StartGhost automatically disables network, so we need to re-enable it for Stripe
|
|
allowStripe();
|
|
|
|
const page = await browser.newPage({
|
|
baseURL: `http://127.0.0.1:${port}/`,
|
|
storageState: undefined
|
|
});
|
|
|
|
await setupGhost(page);
|
|
await setupStripe(page, stripeIntegrationToken);
|
|
await setupMailgun(page);
|
|
await enableLabs(page);
|
|
const state = await page.context().storageState();
|
|
|
|
await page.close();
|
|
|
|
// Use the server in the tests.
|
|
try {
|
|
await use({
|
|
server,
|
|
state
|
|
});
|
|
} finally {
|
|
const {stopGhost} = require('../../utils/e2e-utils');
|
|
await stopGhost();
|
|
stripeServer.kill();
|
|
sandbox.restore();
|
|
}
|
|
}, {scope: 'worker', auto: true}]
|
|
});
|