test(cypress-github-backend): optionally record tests and run using recorded data (#2776)

This commit is contained in:
Erez Rokah
2019-10-22 19:59:13 +03:00
committed by Shawn Erquhart
parent 0f60a559c1
commit b869ce05ae
59 changed files with 57725 additions and 146 deletions

View File

@ -2,8 +2,32 @@ const Octokit = require('@octokit/rest');
const fs = require('fs-extra');
const path = require('path');
const simpleGit = require('simple-git/promise');
const { updateConfig } = require('../utils/config');
const { escapeRegExp } = require('../utils/regexp');
const { retrieveRecordedExpectations, resetMockServerState } = require('../utils/mock-server');
const GIT_SSH_COMMAND = 'ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no';
const GIT_SSL_NO_VERIFY = true;
const GITHUB_REPO_OWNER_SANITIZED_VALUE = 'owner';
const GITHUB_REPO_NAME_SANITIZED_VALUE = 'repo';
const GITHUB_REPO_TOKEN_SANITIZED_VALUE = 'fakeToken';
const GITHUB_OPEN_AUTHORING_OWNER_SANITIZED_VALUE = 'forkOwner';
const GITHUB_OPEN_AUTHORING_TOKEN_SANITIZED_VALUE = 'fakeForkToken';
const FAKE_OWNER_USER = {
login: 'owner',
id: 1,
avatar_url: 'https://avatars1.githubusercontent.com/u/7892489?v=4',
name: 'owner',
};
const FAKE_FORK_OWNER_USER = {
login: 'forkOwner',
id: 2,
avatar_url: 'https://avatars1.githubusercontent.com/u/9919?s=200&v=4',
name: 'forkOwner',
};
function getGitHubClient(token) {
const client = new Octokit({
@ -47,13 +71,13 @@ async function prepareTestGitHubRepo() {
const tempDir = path.join('.temp', testRepoName);
await fs.remove(tempDir);
let git = simpleGit().env({ ...process.env, GIT_SSH_COMMAND });
let git = simpleGit().env({ ...process.env, GIT_SSH_COMMAND, GIT_SSL_NO_VERIFY });
const repoUrl = `git@github.com:${owner}/${repo}.git`;
console.log('Cloning repository', repoUrl);
await git.clone(repoUrl, tempDir);
git = simpleGit(tempDir).env({ ...process.env, GIT_SSH_COMMAND });
git = simpleGit(tempDir).env({ ...process.env, GIT_SSH_COMMAND, GIT_SSL_NO_VERIFY });
console.log('Pushing to new repository', testRepoName);
@ -150,7 +174,7 @@ async function resetOriginRepo({ owner, repo, tempDir }) {
);
console.log('Resetting master');
const git = simpleGit(tempDir).env({ ...process.env, GIT_SSH_COMMAND });
const git = simpleGit(tempDir).env({ ...process.env, GIT_SSH_COMMAND, GIT_SSL_NO_VERIFY });
await git.push(['--force', 'origin', 'master']);
console.log('Done resetting origin repo:', `${owner}/repo`);
}
@ -184,10 +208,209 @@ async function resetRepositories({ owner, repo, tempDir }) {
await resetForkedRepo({ repo });
}
module.exports = {
prepareTestGitHubRepo,
deleteRepositories,
getUser,
getForkUser,
resetRepositories,
async function setupGitHub(options) {
if (process.env.RECORD_FIXTURES) {
console.log('Running tests in "record" mode - live data with be used!');
const [user, forkUser, repoData] = await Promise.all([
getUser(),
getForkUser(),
prepareTestGitHubRepo(),
]);
const { use_graphql = false, open_authoring = false } = options;
await updateConfig(config => {
config.backend = {
...config.backend,
use_graphql,
open_authoring,
repo: `${repoData.owner}/${repoData.repo}`,
};
});
return { ...repoData, user, forkUser, mockResponses: false };
} else {
console.log('Running tests in "playback" mode - local data with be used');
await updateConfig(config => {
config.backend = {
...config.backend,
...options,
repo: `${GITHUB_REPO_OWNER_SANITIZED_VALUE}/${GITHUB_REPO_NAME_SANITIZED_VALUE}`,
};
});
return {
owner: GITHUB_REPO_OWNER_SANITIZED_VALUE,
repo: GITHUB_REPO_NAME_SANITIZED_VALUE,
user: { ...FAKE_OWNER_USER, token: GITHUB_REPO_TOKEN_SANITIZED_VALUE, backendName: 'github' },
forkUser: {
...FAKE_FORK_OWNER_USER,
token: GITHUB_OPEN_AUTHORING_TOKEN_SANITIZED_VALUE,
backendName: 'github',
},
mockResponses: true,
};
}
}
async function teardownGitHub(taskData) {
if (process.env.RECORD_FIXTURES) {
await deleteRepositories(taskData);
}
return null;
}
const getExpectationsFilename = taskData => {
const { spec, testName } = taskData;
const basename = `${spec}__${testName}`;
const fixtures = path.join(__dirname, '..', 'fixtures');
const filename = path.join(fixtures, `${basename}.json`);
return filename;
};
async function setupGitHubTest(taskData) {
if (process.env.RECORD_FIXTURES) {
await resetRepositories(taskData);
await resetMockServerState();
}
return null;
}
const sanitizeString = (
str,
{ owner, repo, token, forkOwner, forkToken, ownerName, forkOwnerName },
) => {
let replaced = str
.replace(new RegExp(escapeRegExp(forkOwner), 'g'), GITHUB_OPEN_AUTHORING_OWNER_SANITIZED_VALUE)
.replace(new RegExp(escapeRegExp(forkToken), 'g'), GITHUB_OPEN_AUTHORING_TOKEN_SANITIZED_VALUE)
.replace(new RegExp(escapeRegExp(owner), 'g'), GITHUB_REPO_OWNER_SANITIZED_VALUE)
.replace(new RegExp(escapeRegExp(repo), 'g'), GITHUB_REPO_NAME_SANITIZED_VALUE)
.replace(new RegExp(escapeRegExp(token), 'g'), GITHUB_REPO_TOKEN_SANITIZED_VALUE)
.replace(new RegExp('https://avatars.+?/u/.+?v=\\d', 'g'), `${FAKE_OWNER_USER.avatar_url}`);
if (ownerName) {
replaced = replaced.replace(new RegExp(escapeRegExp(ownerName), 'g'), FAKE_OWNER_USER.name);
}
if (forkOwnerName) {
replaced = replaced.replace(
new RegExp(escapeRegExp(forkOwnerName), 'g'),
FAKE_FORK_OWNER_USER.name,
);
}
return replaced;
};
const transformRecordedData = (expectation, toSanitize) => {
const { httpRequest, httpResponse } = expectation;
const responseHeaders = {};
Object.keys(httpResponse.headers).forEach(key => {
responseHeaders[key] = httpResponse.headers[key][0];
});
let responseBody = null;
if (httpResponse.body && httpResponse.body.string) {
responseBody = httpResponse.body.string;
}
// replace recorded user with fake one
if (
responseBody &&
httpRequest.path === '/user' &&
httpRequest.headers.Host.includes('api.github.com')
) {
const parsed = JSON.parse(responseBody);
if (parsed.login === toSanitize.forkOwner) {
responseBody = JSON.stringify(FAKE_FORK_OWNER_USER);
} else {
responseBody = JSON.stringify(FAKE_OWNER_USER);
}
}
let queryString;
if (httpRequest.queryStringParameters) {
const { queryStringParameters } = httpRequest;
queryString = Object.keys(queryStringParameters)
.map(key => `${key}=${queryStringParameters[key]}`)
.join('&');
}
let body;
if (httpRequest.body && httpRequest.body.string) {
const bodyObject = JSON.parse(httpRequest.body.string);
if (bodyObject.encoding === 'base64') {
// sanitize encoded data
const decodedBody = Buffer.from(bodyObject.content, 'base64').toString();
bodyObject.content = Buffer.from(sanitizeString(decodedBody, toSanitize)).toString('base64');
body = JSON.stringify(bodyObject);
} else {
body = httpRequest.body.string;
}
}
const cypressRouteOptions = {
body,
method: httpRequest.method,
url: queryString ? `${httpRequest.path}?${queryString}` : httpRequest.path,
headers: responseHeaders,
response: responseBody,
status: httpResponse.statusCode,
};
return cypressRouteOptions;
};
async function teardownGitHubTest(taskData) {
if (process.env.RECORD_FIXTURES) {
await resetRepositories(taskData);
try {
const filename = getExpectationsFilename(taskData);
console.log('Persisting recorded data for test:', path.basename(filename));
const { owner, token, forkOwner, forkToken } = getEnvs();
const expectations = await retrieveRecordedExpectations();
const toSanitize = {
owner,
repo: taskData.repo,
token,
forkOwner,
forkToken,
ownerName: taskData.user.name,
forkOwnerName: taskData.forkUser.name,
};
// transform the mock proxy recorded requests into Cypress route format
const toPersist = expectations.map(expectation =>
transformRecordedData(expectation, toSanitize),
);
const toPersistString = sanitizeString(JSON.stringify(toPersist, null, 2), toSanitize);
await fs.writeFile(filename, toPersistString);
} catch (e) {
console.log(e);
}
await resetMockServerState();
}
return null;
}
module.exports = {
setupGitHub,
teardownGitHub,
setupGitHubTest,
teardownGitHubTest,
};