feat: v4.0.0 (#1016)
Co-authored-by: Denys Konovalov <kontakt@denyskon.de> Co-authored-by: Mathieu COSYNS <64072917+Mathieu-COSYNS@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
682576ffc4
commit
799c7e6936
310
cypress/plugins/bitbucket.js
Normal file
310
cypress/plugins/bitbucket.js
Normal file
@ -0,0 +1,310 @@
|
||||
const fs = require('fs-extra');
|
||||
const fetch = require('node-fetch');
|
||||
const path = require('path');
|
||||
const { updateConfig } = require('../utils/config');
|
||||
const { escapeRegExp } = require('../utils/regexp');
|
||||
const {
|
||||
getExpectationsFilename,
|
||||
transformRecordedData: transformData,
|
||||
getGitClient,
|
||||
} = require('./common');
|
||||
const { merge } = require('lodash');
|
||||
|
||||
const BITBUCKET_REPO_OWNER_SANITIZED_VALUE = 'owner';
|
||||
const BITBUCKET_REPO_NAME_SANITIZED_VALUE = 'repo';
|
||||
const BITBUCKET_REPO_TOKEN_SANITIZED_VALUE = 'fakeToken';
|
||||
|
||||
const FAKE_OWNER_USER = {
|
||||
name: 'owner',
|
||||
display_name: 'owner',
|
||||
links: {
|
||||
avatar: {
|
||||
href: 'https://avatars1.githubusercontent.com/u/7892489?v=4',
|
||||
},
|
||||
},
|
||||
nickname: 'owner',
|
||||
};
|
||||
|
||||
async function getEnvs() {
|
||||
const {
|
||||
BITBUCKET_REPO_OWNER: owner,
|
||||
BITBUCKET_REPO_NAME: repo,
|
||||
BITBUCKET_OUATH_CONSUMER_KEY: consumerKey,
|
||||
BITBUCKET_OUATH_CONSUMER_SECRET: consumerSecret,
|
||||
} = process.env;
|
||||
if (!owner || !repo || !consumerKey || !consumerSecret) {
|
||||
throw new Error(
|
||||
'Please set BITBUCKET_REPO_OWNER, BITBUCKET_REPO_NAME, BITBUCKET_OUATH_CONSUMER_KEY, BITBUCKET_OUATH_CONSUMER_SECRET environment variables',
|
||||
);
|
||||
}
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.append('grant_type', 'client_credentials');
|
||||
|
||||
const { access_token: token } = await fetch(
|
||||
`https://${consumerKey}:${consumerSecret}@bitbucket.org/site/oauth2/access_token`,
|
||||
{ method: 'POST', body: params },
|
||||
).then(r => r.json());
|
||||
|
||||
return { owner, repo, token };
|
||||
}
|
||||
|
||||
const API_URL = 'https://api.bitbucket.org/2.0/';
|
||||
|
||||
function get(token, path) {
|
||||
return fetch(`${API_URL}${path}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
}).then(r => r.json());
|
||||
}
|
||||
|
||||
function post(token, path, body) {
|
||||
return fetch(`${API_URL}${path}`, {
|
||||
method: 'POST',
|
||||
...(body ? { body } : {}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(body ? { 'Content-Type': 'application/json' } : {}),
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function del(token, path) {
|
||||
return fetch(`${API_URL}${path}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function prepareTestBitBucketRepo({ lfs }) {
|
||||
const { owner, repo, token } = await getEnvs();
|
||||
|
||||
// postfix a random string to avoid collisions
|
||||
const postfix = Math.random().toString(32).slice(2);
|
||||
const testRepoName = `${repo}-${Date.now()}-${postfix}`;
|
||||
|
||||
console.info('Creating repository', testRepoName, token);
|
||||
const response = await post(
|
||||
token,
|
||||
`repositories/${owner}/${testRepoName}`,
|
||||
JSON.stringify({ scm: 'git' }),
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Unable to create repository. ${response.statusText}`);
|
||||
}
|
||||
|
||||
const tempDir = path.join('.temp', testRepoName);
|
||||
await fs.remove(tempDir);
|
||||
let git = getGitClient();
|
||||
|
||||
const repoUrl = `git@bitbucket.org:${owner}/${repo}.git`;
|
||||
|
||||
console.info('Cloning repository', repoUrl);
|
||||
await git.clone(repoUrl, tempDir);
|
||||
git = getGitClient(tempDir);
|
||||
|
||||
console.info('Pushing to new repository', testRepoName);
|
||||
|
||||
console.info('Updating remote...');
|
||||
await git.removeRemote('origin');
|
||||
await git.addRemote('origin', `git@bitbucket.org:${owner}/${testRepoName}`);
|
||||
|
||||
console.info('Pushing...');
|
||||
await git.push(['-u', 'origin', 'main']);
|
||||
|
||||
console.info('Pushed to new repository', testRepoName);
|
||||
|
||||
if (lfs) {
|
||||
console.info(`Enabling LFS for repo ${owner}/${repo}`);
|
||||
await git.addConfig('commit.gpgsign', 'false');
|
||||
await git.raw(['lfs', 'track', '*.png', '*.jpg']);
|
||||
await git.add('.gitattributes');
|
||||
await git.commit('chore: track images files under LFS');
|
||||
await git.push('origin', 'main');
|
||||
}
|
||||
|
||||
return { owner, repo: testRepoName, tempDir };
|
||||
}
|
||||
|
||||
async function getUser() {
|
||||
const { token } = await getEnvs();
|
||||
const user = await get(token, 'user');
|
||||
return { ...user, token, backendName: 'bitbucket' };
|
||||
}
|
||||
|
||||
async function deleteRepositories({ owner, repo, tempDir }) {
|
||||
const { token } = await getEnvs();
|
||||
|
||||
console.info('Deleting repository', `${owner}/${repo}`);
|
||||
await fs.remove(tempDir);
|
||||
|
||||
await del(token, `repositories/${owner}/${repo}`);
|
||||
}
|
||||
|
||||
async function resetOriginRepo({ owner, repo, tempDir }) {
|
||||
console.info('Resetting origin repo:', `${owner}/${repo}`);
|
||||
|
||||
const { token } = await getEnvs();
|
||||
|
||||
const pullRequests = await get(token, `repositories/${owner}/${repo}/pullrequests`);
|
||||
const ids = pullRequests.values.map(mr => mr.id);
|
||||
|
||||
console.info('Closing pull requests:', ids);
|
||||
await Promise.all(
|
||||
ids.map(id => post(token, `repositories/${owner}/${repo}/pullrequests/${id}/decline`)),
|
||||
);
|
||||
const branches = await get(token, `repositories/${owner}/${repo}/refs/branches`);
|
||||
const toDelete = branches.values.filter(b => b.name !== 'main').map(b => b.name);
|
||||
|
||||
console.info('Deleting branches', toDelete);
|
||||
await Promise.all(
|
||||
toDelete.map(branch => del(token, `repositories/${owner}/${repo}/refs/branches/${branch}`)),
|
||||
);
|
||||
|
||||
console.info('Resetting main');
|
||||
const git = getGitClient(tempDir);
|
||||
await git.push(['--force', 'origin', 'main']);
|
||||
console.info('Done resetting origin repo:', `${owner}/${repo}`);
|
||||
}
|
||||
|
||||
async function resetRepositories({ owner, repo, tempDir }) {
|
||||
await resetOriginRepo({ owner, repo, tempDir });
|
||||
}
|
||||
|
||||
async function setupBitBucket(options) {
|
||||
const { lfs = false, ...rest } = options;
|
||||
|
||||
console.info('Running tests - live data will be used!');
|
||||
const [user, repoData] = await Promise.all([getUser(), prepareTestBitBucketRepo({ lfs })]);
|
||||
|
||||
console.info('Updating config...');
|
||||
await updateConfig(config => {
|
||||
merge(config, rest, {
|
||||
backend: {
|
||||
repo: `${repoData.owner}/${repoData.repo}`,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return { ...repoData, user };
|
||||
}
|
||||
|
||||
async function teardownBitBucket(taskData) {
|
||||
await deleteRepositories(taskData);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function setupBitBucketTest(taskData) {
|
||||
await resetRepositories(taskData);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const sanitizeString = (str, { owner, repo, token, ownerName }) => {
|
||||
let replaced = str
|
||||
.replace(new RegExp(escapeRegExp(owner), 'g'), BITBUCKET_REPO_OWNER_SANITIZED_VALUE)
|
||||
.replace(new RegExp(escapeRegExp(repo), 'g'), BITBUCKET_REPO_NAME_SANITIZED_VALUE)
|
||||
.replace(new RegExp(escapeRegExp(token), 'g'), BITBUCKET_REPO_TOKEN_SANITIZED_VALUE)
|
||||
.replace(
|
||||
new RegExp('https://secure.gravatar.+?/u/.+?v=\\d', 'g'),
|
||||
`${FAKE_OWNER_USER.links.avatar.href}`,
|
||||
)
|
||||
.replace(new RegExp(/\?token=.+?&/g), 'token=fakeToken&')
|
||||
.replace(new RegExp(/&client=.+?&/g), 'client=fakeClient&');
|
||||
|
||||
if (ownerName) {
|
||||
replaced = replaced.replace(
|
||||
new RegExp(escapeRegExp(ownerName), 'g'),
|
||||
FAKE_OWNER_USER.display_name,
|
||||
);
|
||||
}
|
||||
|
||||
return replaced;
|
||||
};
|
||||
|
||||
const transformRecordedData = (expectation, toSanitize) => {
|
||||
const requestBodySanitizer = httpRequest => {
|
||||
let body;
|
||||
if (httpRequest.body && httpRequest.body.type === 'JSON' && httpRequest.body.json) {
|
||||
const bodyObject = JSON.parse(httpRequest.body.json);
|
||||
if (bodyObject.encoding === 'base64') {
|
||||
// sanitize encoded data
|
||||
const decodedBody = Buffer.from(bodyObject.content, 'base64').toString('binary');
|
||||
const sanitizedContent = sanitizeString(decodedBody, toSanitize);
|
||||
const sanitizedEncodedContent = Buffer.from(sanitizedContent, 'binary').toString('base64');
|
||||
bodyObject.content = sanitizedEncodedContent;
|
||||
body = JSON.stringify(bodyObject);
|
||||
} else {
|
||||
body = httpRequest.body.json;
|
||||
}
|
||||
} else if (httpRequest.body && httpRequest.body.type === 'STRING' && httpRequest.body.string) {
|
||||
body = httpRequest.body.string;
|
||||
} else if (
|
||||
httpRequest.body &&
|
||||
httpRequest.body.type === 'BINARY' &&
|
||||
httpRequest.body.base64Bytes
|
||||
) {
|
||||
body = {
|
||||
encoding: 'base64',
|
||||
content: httpRequest.body.base64Bytes,
|
||||
contentType: httpRequest.body.contentType,
|
||||
};
|
||||
}
|
||||
return body;
|
||||
};
|
||||
|
||||
const responseBodySanitizer = (httpRequest, httpResponse) => {
|
||||
let responseBody = null;
|
||||
if (httpResponse.body && httpResponse.body.string) {
|
||||
responseBody = httpResponse.body.string;
|
||||
} else if (
|
||||
httpResponse.body &&
|
||||
httpResponse.body.type === 'BINARY' &&
|
||||
httpResponse.body.base64Bytes
|
||||
) {
|
||||
responseBody = {
|
||||
encoding: 'base64',
|
||||
content: httpResponse.body.base64Bytes,
|
||||
};
|
||||
} else if (httpResponse.body) {
|
||||
if (httpResponse.body && httpResponse.body.type === 'JSON' && httpResponse.body.json) {
|
||||
responseBody = JSON.stringify(httpResponse.body.json);
|
||||
} else {
|
||||
responseBody = httpResponse.body;
|
||||
}
|
||||
}
|
||||
|
||||
// replace recorded user with fake one
|
||||
if (
|
||||
responseBody &&
|
||||
httpRequest.path === '/2.0/user' &&
|
||||
httpRequest.headers.host.includes('api.bitbucket.org')
|
||||
) {
|
||||
responseBody = JSON.stringify(FAKE_OWNER_USER);
|
||||
}
|
||||
|
||||
return responseBody;
|
||||
};
|
||||
|
||||
return transformData(expectation, requestBodySanitizer, responseBodySanitizer);
|
||||
};
|
||||
|
||||
async function teardownBitBucketTest(taskData) {
|
||||
await resetRepositories(taskData);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setupBitBucket,
|
||||
teardownBitBucket,
|
||||
setupBitBucketTest,
|
||||
teardownBitBucketTest,
|
||||
};
|
80
cypress/plugins/common.js
Normal file
80
cypress/plugins/common.js
Normal file
@ -0,0 +1,80 @@
|
||||
const path = require('path');
|
||||
const { default: simpleGit } = require('simple-git');
|
||||
|
||||
const GIT_SSH_COMMAND = 'ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no';
|
||||
const GIT_SSL_NO_VERIFY = true;
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
const HEADERS_TO_IGNORE = [
|
||||
'Date',
|
||||
'X-RateLimit-Remaining',
|
||||
'X-RateLimit-Reset',
|
||||
'ETag',
|
||||
'Last-Modified',
|
||||
'X-GitHub-Request-Id',
|
||||
'X-NF-Request-ID',
|
||||
'X-Request-Id',
|
||||
'X-Runtime',
|
||||
'RateLimit-Limit',
|
||||
'RateLimit-Observed',
|
||||
'RateLimit-Remaining',
|
||||
'RateLimit-Reset',
|
||||
'RateLimit-ResetTime',
|
||||
'GitLab-LB',
|
||||
].map(h => h.toLowerCase());
|
||||
|
||||
const transformRecordedData = (expectation, requestBodySanitizer, responseBodySanitizer) => {
|
||||
const { httpRequest, httpResponse } = expectation;
|
||||
|
||||
const responseHeaders = {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
};
|
||||
|
||||
Object.keys(httpResponse.headers)
|
||||
.filter(key => !HEADERS_TO_IGNORE.includes(key.toLowerCase()))
|
||||
.forEach(key => {
|
||||
responseHeaders[key] = httpResponse.headers[key][0];
|
||||
});
|
||||
|
||||
let queryString;
|
||||
if (httpRequest.queryStringParameters) {
|
||||
const { queryStringParameters } = httpRequest;
|
||||
|
||||
queryString = Object.keys(queryStringParameters)
|
||||
.map(key => `${key}=${queryStringParameters[key]}`)
|
||||
.join('&');
|
||||
}
|
||||
|
||||
const body = requestBodySanitizer(httpRequest);
|
||||
const responseBody = responseBodySanitizer(httpRequest, httpResponse);
|
||||
|
||||
const cypressRouteOptions = {
|
||||
body,
|
||||
method: httpRequest.method,
|
||||
url: queryString ? `${httpRequest.path}?${queryString}` : httpRequest.path,
|
||||
headers: responseHeaders,
|
||||
response: responseBody,
|
||||
status: httpResponse.statusCode,
|
||||
};
|
||||
|
||||
return cypressRouteOptions;
|
||||
};
|
||||
|
||||
function getGitClient(repoDir) {
|
||||
const git = simpleGit(repoDir).env({ ...process.env, GIT_SSH_COMMAND, GIT_SSL_NO_VERIFY });
|
||||
return git;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getExpectationsFilename,
|
||||
transformRecordedData,
|
||||
getGitClient,
|
||||
};
|
284
cypress/plugins/gitGateway.js
Normal file
284
cypress/plugins/gitGateway.js
Normal file
@ -0,0 +1,284 @@
|
||||
const fetch = require('node-fetch');
|
||||
const {
|
||||
transformRecordedData: transformGitHub,
|
||||
setupGitHub,
|
||||
teardownGitHub,
|
||||
setupGitHubTest,
|
||||
teardownGitHubTest,
|
||||
} = require('./github');
|
||||
const {
|
||||
transformRecordedData: transformGitLab,
|
||||
setupGitLab,
|
||||
teardownGitLab,
|
||||
setupGitLabTest,
|
||||
teardownGitLabTest,
|
||||
} = require('./gitlab');
|
||||
const { getGitClient } = require('./common');
|
||||
|
||||
function getEnvs() {
|
||||
const {
|
||||
NETLIFY_API_TOKEN: netlifyApiToken,
|
||||
GITHUB_REPO_TOKEN: githubToken,
|
||||
GITLAB_REPO_TOKEN: gitlabToken,
|
||||
NETLIFY_INSTALLATION_ID: installationId,
|
||||
} = process.env;
|
||||
if (!netlifyApiToken) {
|
||||
throw new Error(
|
||||
'Please set NETLIFY_API_TOKEN, GITHUB_REPO_TOKEN, GITLAB_REPO_TOKEN, NETLIFY_INSTALLATION_ID environment variables',
|
||||
);
|
||||
}
|
||||
return { netlifyApiToken, githubToken, gitlabToken, installationId };
|
||||
}
|
||||
|
||||
const apiRoot = 'https://api.netlify.com/api/v1/';
|
||||
|
||||
async function get(netlifyApiToken, path) {
|
||||
const response = await fetch(`${apiRoot}${path}`, {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${netlifyApiToken}`,
|
||||
},
|
||||
}).then(res => res.json());
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async function post(netlifyApiToken, path, payload) {
|
||||
const response = await fetch(`${apiRoot}${path}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${netlifyApiToken}`,
|
||||
},
|
||||
...(payload ? { body: JSON.stringify(payload) } : {}),
|
||||
}).then(res => res.json());
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async function del(netlifyApiToken, path) {
|
||||
const response = await fetch(`${apiRoot}${path}`, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${netlifyApiToken}`,
|
||||
},
|
||||
}).then(res => res.text());
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async function createSite(netlifyApiToken, payload) {
|
||||
return post(netlifyApiToken, 'sites', payload);
|
||||
}
|
||||
|
||||
async function enableIdentity(netlifyApiToken, siteId) {
|
||||
return post(netlifyApiToken, `sites/${siteId}/identity`, {});
|
||||
}
|
||||
|
||||
async function enableGitGateway(netlifyApiToken, siteId, provider, token, repo) {
|
||||
return post(netlifyApiToken, `sites/${siteId}/services/git/instances`, {
|
||||
[provider]: {
|
||||
repo,
|
||||
access_token: token,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function enableLargeMedia(netlifyApiToken, siteId) {
|
||||
return post(netlifyApiToken, `sites/${siteId}/services/large-media/instances`, {});
|
||||
}
|
||||
|
||||
async function waitForDeploys(netlifyApiToken, siteId) {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const deploys = await get(netlifyApiToken, `sites/${siteId}/deploys`);
|
||||
if (deploys.some(deploy => deploy.state === 'ready')) {
|
||||
console.info('Deploy finished for site:', siteId);
|
||||
return;
|
||||
}
|
||||
console.info('Waiting on deploy of site:', siteId);
|
||||
await new Promise(resolve => setTimeout(resolve, 30 * 1000));
|
||||
}
|
||||
console.info('Timed out waiting on deploy of site:', siteId);
|
||||
}
|
||||
|
||||
async function createUser(netlifyApiToken, siteUrl, email, password) {
|
||||
const response = await fetch(`${siteUrl}/.netlify/functions/create-user`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${netlifyApiToken}`,
|
||||
},
|
||||
body: JSON.stringify({ email, password }),
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
console.info('User created successfully');
|
||||
} else {
|
||||
throw new Error('Failed to create user');
|
||||
}
|
||||
}
|
||||
|
||||
const netlifySiteURL = 'https://fake-site-url.netlify.com/';
|
||||
const email = 'static@p-m.si';
|
||||
const password = '12345678';
|
||||
const backendName = 'git-gateway';
|
||||
|
||||
const methods = {
|
||||
github: {
|
||||
setup: setupGitHub,
|
||||
teardown: teardownGitHub,
|
||||
setupTest: setupGitHubTest,
|
||||
teardownTest: teardownGitHubTest,
|
||||
transformData: transformGitHub,
|
||||
createSite: (netlifyApiToken, result) => {
|
||||
const { installationId } = getEnvs();
|
||||
return createSite(netlifyApiToken, {
|
||||
repo: {
|
||||
provider: 'github',
|
||||
installation_id: installationId,
|
||||
repo: `${result.owner}/${result.repo}`,
|
||||
},
|
||||
});
|
||||
},
|
||||
token: () => getEnvs().githubToken,
|
||||
},
|
||||
gitlab: {
|
||||
setup: setupGitLab,
|
||||
teardown: teardownGitLab,
|
||||
setupTest: setupGitLabTest,
|
||||
teardownTest: teardownGitLabTest,
|
||||
transformData: transformGitLab,
|
||||
createSite: async (netlifyApiToken, result) => {
|
||||
const { id, public_key } = await post(netlifyApiToken, 'deploy_keys');
|
||||
const { gitlabToken } = getEnvs();
|
||||
const project = `${result.owner}/${result.repo}`;
|
||||
await fetch(`https://gitlab.com/api/v4/projects/${encodeURIComponent(project)}/deploy_keys`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${gitlabToken}`,
|
||||
},
|
||||
body: JSON.stringify({ title: 'Netlify Deploy Key', key: public_key, can_push: false }),
|
||||
}).then(res => res.json());
|
||||
|
||||
const site = await createSite(netlifyApiToken, {
|
||||
account_slug: result.owner,
|
||||
repo: {
|
||||
provider: 'gitlab',
|
||||
repo: `${result.owner}/${result.repo}`,
|
||||
deploy_key_id: id,
|
||||
},
|
||||
});
|
||||
|
||||
await fetch(`https://gitlab.com/api/v4/projects/${encodeURIComponent(project)}/hooks`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${gitlabToken}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
url: 'https://api.netlify.com/hooks/gitlab',
|
||||
push_events: true,
|
||||
merge_requests_events: true,
|
||||
enable_ssl_verification: true,
|
||||
}),
|
||||
}).then(res => res.json());
|
||||
|
||||
return site;
|
||||
},
|
||||
token: () => getEnvs().gitlabToken,
|
||||
},
|
||||
};
|
||||
|
||||
async function setupGitGateway(options) {
|
||||
const { provider, ...rest } = options;
|
||||
const result = await methods[provider].setup(rest);
|
||||
|
||||
const { netlifyApiToken } = getEnvs();
|
||||
|
||||
console.info(`Creating Netlify Site for provider: ${provider}`);
|
||||
let site_id, ssl_url;
|
||||
try {
|
||||
({ site_id, ssl_url } = await methods[provider].createSite(netlifyApiToken, result));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
console.info('Enabling identity for site:', site_id);
|
||||
await enableIdentity(netlifyApiToken, site_id);
|
||||
|
||||
console.info('Enabling git gateway for site:', site_id);
|
||||
const token = methods[provider].token();
|
||||
await enableGitGateway(
|
||||
netlifyApiToken,
|
||||
site_id,
|
||||
provider,
|
||||
token,
|
||||
`${result.owner}/${result.repo}`,
|
||||
);
|
||||
|
||||
console.info('Enabling large media for site:', site_id);
|
||||
await enableLargeMedia(netlifyApiToken, site_id);
|
||||
|
||||
const git = getGitClient(result.tempDir);
|
||||
await git.raw([
|
||||
'config',
|
||||
'-f',
|
||||
'.lfsconfig',
|
||||
'lfs.url',
|
||||
`https://${site_id}.netlify.com/.netlify/large-media`,
|
||||
]);
|
||||
await git.addConfig('commit.gpgsign', 'false');
|
||||
await git.add('.lfsconfig');
|
||||
await git.commit('add .lfsconfig');
|
||||
await git.push('origin', 'main');
|
||||
|
||||
await waitForDeploys(netlifyApiToken, site_id);
|
||||
console.info('Creating user for site:', site_id, 'with email:', email);
|
||||
|
||||
try {
|
||||
await createUser(netlifyApiToken, ssl_url, email, password);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
return {
|
||||
...result,
|
||||
user: {
|
||||
...result.user,
|
||||
backendName,
|
||||
netlifySiteURL: ssl_url,
|
||||
email,
|
||||
password,
|
||||
},
|
||||
site_id,
|
||||
ssl_url,
|
||||
provider,
|
||||
};
|
||||
}
|
||||
|
||||
async function teardownGitGateway(taskData) {
|
||||
const { netlifyApiToken } = getEnvs();
|
||||
const { site_id } = taskData;
|
||||
console.info('Deleting Netlify site:', site_id);
|
||||
await del(netlifyApiToken, `sites/${site_id}`);
|
||||
|
||||
return methods[taskData.provider].teardown(taskData);
|
||||
}
|
||||
|
||||
async function setupGitGatewayTest(taskData) {
|
||||
return methods[taskData.provider].setupTest(taskData);
|
||||
}
|
||||
|
||||
async function teardownGitGatewayTest(taskData) {
|
||||
return methods[taskData.provider].teardownTest(taskData, {});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setupGitGateway,
|
||||
teardownGitGateway,
|
||||
setupGitGatewayTest,
|
||||
teardownGitGatewayTest,
|
||||
};
|
426
cypress/plugins/github.js
Normal file
426
cypress/plugins/github.js
Normal file
@ -0,0 +1,426 @@
|
||||
const { Octokit } = require('@octokit/rest');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const {
|
||||
getExpectationsFilename,
|
||||
transformRecordedData: transformData,
|
||||
getGitClient,
|
||||
} = require('./common');
|
||||
const { updateConfig } = require('../utils/config');
|
||||
const { escapeRegExp } = require('../utils/regexp');
|
||||
const { merge } = require('lodash');
|
||||
|
||||
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({
|
||||
auth: `token ${token}`,
|
||||
baseUrl: 'https://api.github.com',
|
||||
});
|
||||
return client;
|
||||
}
|
||||
|
||||
function getEnvs() {
|
||||
const {
|
||||
GITHUB_REPO_OWNER: owner,
|
||||
GITHUB_REPO_NAME: repo,
|
||||
GITHUB_REPO_TOKEN: token,
|
||||
GITHUB_OPEN_AUTHORING_OWNER: forkOwner,
|
||||
GITHUB_OPEN_AUTHORING_TOKEN: forkToken,
|
||||
} = process.env;
|
||||
if (!owner || !repo || !token || !forkOwner || !forkToken) {
|
||||
throw new Error(
|
||||
'Please set GITHUB_REPO_OWNER, GITHUB_REPO_NAME, GITHUB_REPO_TOKEN, GITHUB_OPEN_AUTHORING_OWNER, GITHUB_OPEN_AUTHORING_TOKEN environment variables',
|
||||
);
|
||||
}
|
||||
return { owner, repo, token, forkOwner, forkToken };
|
||||
}
|
||||
|
||||
async function prepareTestGitHubRepo() {
|
||||
const { owner, repo, token } = getEnvs();
|
||||
|
||||
// postfix a random string to avoid collisions
|
||||
const postfix = Math.random().toString(32).slice(2);
|
||||
const testRepoName = `${repo}-${Date.now()}-${postfix}`;
|
||||
|
||||
const client = getGitHubClient(token);
|
||||
|
||||
console.info('Creating repository', testRepoName);
|
||||
await client.repos.createForAuthenticatedUser({
|
||||
name: testRepoName,
|
||||
});
|
||||
|
||||
const tempDir = path.join('.temp', testRepoName);
|
||||
await fs.remove(tempDir);
|
||||
let git = getGitClient();
|
||||
|
||||
const repoUrl = `git@github.com:${owner}/${repo}.git`;
|
||||
|
||||
console.info('Cloning repository', repoUrl);
|
||||
await git.clone(repoUrl, tempDir);
|
||||
git = getGitClient(tempDir);
|
||||
|
||||
console.info('Pushing to new repository', testRepoName);
|
||||
|
||||
await git.removeRemote('origin');
|
||||
await git.addRemote(
|
||||
'origin',
|
||||
`https://${token}:x-oauth-basic@github.com/${owner}/${testRepoName}`,
|
||||
);
|
||||
await git.push(['-u', 'origin', 'main']);
|
||||
|
||||
return { owner, repo: testRepoName, tempDir };
|
||||
}
|
||||
|
||||
async function getAuthenticatedUser(token) {
|
||||
const client = getGitHubClient(token);
|
||||
const { data: user } = await client.users.getAuthenticated();
|
||||
return { ...user, token, backendName: 'github' };
|
||||
}
|
||||
|
||||
async function getUser() {
|
||||
const { token } = getEnvs();
|
||||
return getAuthenticatedUser(token);
|
||||
}
|
||||
|
||||
async function getForkUser() {
|
||||
const { forkToken } = getEnvs();
|
||||
return getAuthenticatedUser(forkToken);
|
||||
}
|
||||
|
||||
async function deleteRepositories({ owner, repo, tempDir }) {
|
||||
const { forkOwner, token, forkToken } = getEnvs();
|
||||
|
||||
const errorHandler = e => {
|
||||
if (e.status !== 404) {
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
console.info('Deleting repository', `${owner}/${repo}`);
|
||||
await fs.remove(tempDir);
|
||||
|
||||
let client = getGitHubClient(token);
|
||||
await client.repos
|
||||
.delete({
|
||||
owner,
|
||||
repo,
|
||||
})
|
||||
.catch(errorHandler);
|
||||
|
||||
console.info('Deleting forked repository', `${forkOwner}/${repo}`);
|
||||
client = getGitHubClient(forkToken);
|
||||
await client.repos
|
||||
.delete({
|
||||
owner: forkOwner,
|
||||
repo,
|
||||
})
|
||||
.catch(errorHandler);
|
||||
}
|
||||
|
||||
async function batchRequests(items, batchSize, func) {
|
||||
while (items.length > 0) {
|
||||
const batch = items.splice(0, batchSize);
|
||||
await Promise.all(batch.map(func));
|
||||
await new Promise(resolve => setTimeout(resolve, 2500));
|
||||
}
|
||||
}
|
||||
|
||||
async function resetOriginRepo({ owner, repo, tempDir }) {
|
||||
console.info('Resetting origin repo:', `${owner}/${repo}`);
|
||||
const { token } = getEnvs();
|
||||
const client = getGitHubClient(token);
|
||||
|
||||
const { data: prs } = await client.pulls.list({
|
||||
repo,
|
||||
owner,
|
||||
state: 'open',
|
||||
});
|
||||
const numbers = prs.map(pr => pr.number);
|
||||
console.info('Closing prs:', numbers);
|
||||
|
||||
await batchRequests(numbers, 10, async pull_number => {
|
||||
await client.pulls.update({
|
||||
owner,
|
||||
repo,
|
||||
pull_number,
|
||||
state: 'closed',
|
||||
});
|
||||
});
|
||||
|
||||
const { data: branches } = await client.repos.listBranches({ owner, repo });
|
||||
const refs = branches.filter(b => b.name !== 'main').map(b => `heads/${b.name}`);
|
||||
|
||||
console.info('Deleting refs', refs);
|
||||
|
||||
await batchRequests(refs, 10, async ref => {
|
||||
await client.git.deleteRef({
|
||||
owner,
|
||||
repo,
|
||||
ref,
|
||||
});
|
||||
});
|
||||
|
||||
console.info('Resetting main');
|
||||
const git = getGitClient(tempDir);
|
||||
await git.push(['--force', 'origin', 'main']);
|
||||
console.info('Done resetting origin repo:', `${owner}/${repo}`);
|
||||
}
|
||||
|
||||
async function resetForkedRepo({ repo }) {
|
||||
const { forkToken, forkOwner } = getEnvs();
|
||||
const client = getGitHubClient(forkToken);
|
||||
|
||||
const { data: repos } = await client.repos.list();
|
||||
if (repos.some(r => r.name === repo)) {
|
||||
console.info('Resetting forked repo:', `${forkOwner}/${repo}`);
|
||||
const { data: branches } = await client.repos.listBranches({ owner: forkOwner, repo });
|
||||
const refs = branches.filter(b => b.name !== 'main').map(b => `heads/${b.name}`);
|
||||
|
||||
console.info('Deleting refs', refs);
|
||||
await Promise.all(
|
||||
refs.map(ref =>
|
||||
client.git.deleteRef({
|
||||
owner: forkOwner,
|
||||
repo,
|
||||
ref,
|
||||
}),
|
||||
),
|
||||
);
|
||||
console.info('Done resetting forked repo:', `${forkOwner}/${repo}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function resetRepositories({ owner, repo, tempDir }) {
|
||||
await resetOriginRepo({ owner, repo, tempDir });
|
||||
await resetForkedRepo({ repo });
|
||||
}
|
||||
|
||||
async function setupGitHub(options) {
|
||||
console.info('Running tests - live data will be used!');
|
||||
const [user, forkUser, repoData] = await Promise.all([
|
||||
getUser(),
|
||||
getForkUser(),
|
||||
prepareTestGitHubRepo(),
|
||||
]);
|
||||
|
||||
await updateConfig(config => {
|
||||
merge(config, options, {
|
||||
backend: {
|
||||
repo: `${repoData.owner}/${repoData.repo}`,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return { ...repoData, user, forkUser };
|
||||
}
|
||||
|
||||
async function teardownGitHub(taskData) {
|
||||
await deleteRepositories(taskData);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function setupGitHubTest(taskData) {
|
||||
await resetRepositories(taskData);
|
||||
|
||||
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\\d+\\.githubusercontent\\.com/u/\\d+?\\?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 requestBodySanitizer = httpRequest => {
|
||||
let body;
|
||||
if (httpRequest.body && httpRequest.body.type === 'JSON' && httpRequest.body.json) {
|
||||
const bodyObject =
|
||||
typeof httpRequest.body.json === 'string'
|
||||
? JSON.parse(httpRequest.body.json)
|
||||
: httpRequest.body.json;
|
||||
|
||||
if (bodyObject.encoding === 'base64') {
|
||||
// sanitize encoded data
|
||||
const decodedBody = Buffer.from(bodyObject.content, 'base64').toString('binary');
|
||||
const sanitizedContent = sanitizeString(decodedBody, toSanitize);
|
||||
const sanitizedEncodedContent = Buffer.from(sanitizedContent, 'binary').toString('base64');
|
||||
bodyObject.content = sanitizedEncodedContent;
|
||||
body = JSON.stringify(bodyObject);
|
||||
} else {
|
||||
body = JSON.stringify(bodyObject);
|
||||
}
|
||||
} else if (httpRequest.body && httpRequest.body.type === 'STRING' && httpRequest.body.string) {
|
||||
body = httpRequest.body.string;
|
||||
} else if (httpRequest.body) {
|
||||
const str =
|
||||
typeof httpRequest.body !== 'string' ? JSON.stringify(httpRequest.body) : httpRequest.body;
|
||||
body = sanitizeString(str, toSanitize);
|
||||
}
|
||||
return body;
|
||||
};
|
||||
|
||||
const responseBodySanitizer = (httpRequest, httpResponse) => {
|
||||
let responseBody = null;
|
||||
if (httpResponse.body && httpResponse.body.string) {
|
||||
responseBody = httpResponse.body.string;
|
||||
} else if (
|
||||
httpResponse.body &&
|
||||
httpResponse.body.type === 'BINARY' &&
|
||||
httpResponse.body.base64Bytes
|
||||
) {
|
||||
responseBody = {
|
||||
encoding: 'base64',
|
||||
content: httpResponse.body.base64Bytes,
|
||||
};
|
||||
} else if (httpResponse.body && httpResponse.body.json) {
|
||||
responseBody = JSON.stringify(httpResponse.body.json);
|
||||
} else {
|
||||
responseBody =
|
||||
typeof httpResponse.body === 'string'
|
||||
? httpResponse.body
|
||||
: httpResponse.body && JSON.stringify(httpResponse.body);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
return responseBody;
|
||||
};
|
||||
|
||||
const cypressRouteOptions = transformData(
|
||||
expectation,
|
||||
requestBodySanitizer,
|
||||
responseBodySanitizer,
|
||||
);
|
||||
|
||||
return cypressRouteOptions;
|
||||
};
|
||||
|
||||
const defaultOptions = {
|
||||
transformRecordedData,
|
||||
};
|
||||
|
||||
async function teardownGitHubTest(taskData, { transformRecordedData } = defaultOptions) {
|
||||
await resetRepositories(taskData);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function seedGitHubRepo(taskData) {
|
||||
const { owner, token } = getEnvs();
|
||||
|
||||
const client = getGitHubClient(token);
|
||||
const repo = taskData.repo;
|
||||
|
||||
try {
|
||||
console.info('Getting main branch');
|
||||
const { data: main } = await client.repos.getBranch({
|
||||
owner,
|
||||
repo,
|
||||
branch: 'main',
|
||||
});
|
||||
|
||||
const prCount = 120;
|
||||
const prs = new Array(prCount).fill(0).map((v, i) => i);
|
||||
const batchSize = 5;
|
||||
await batchRequests(prs, batchSize, async i => {
|
||||
const branch = `seed_branch_${i}`;
|
||||
console.info(`Creating branch ${branch}`);
|
||||
await client.git.createRef({
|
||||
owner,
|
||||
repo,
|
||||
ref: `refs/heads/${branch}`,
|
||||
sha: main.commit.sha,
|
||||
});
|
||||
|
||||
const path = `seed/file_${i}`;
|
||||
console.info(`Creating file ${path}`);
|
||||
await client.repos.createOrUpdateFile({
|
||||
owner,
|
||||
repo,
|
||||
branch,
|
||||
content: Buffer.from(`Seed File ${i}`).toString('base64'),
|
||||
message: `Create seed file ${i}`,
|
||||
path,
|
||||
});
|
||||
|
||||
const title = `Non CMS Pull Request ${i}`;
|
||||
console.info(`Creating PR ${title}`);
|
||||
await client.pulls.create({
|
||||
owner,
|
||||
repo,
|
||||
base: 'main',
|
||||
head: branch,
|
||||
title,
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
transformRecordedData,
|
||||
setupGitHub,
|
||||
teardownGitHub,
|
||||
setupGitHubTest,
|
||||
teardownGitHubTest,
|
||||
seedGitHubRepo,
|
||||
};
|
275
cypress/plugins/gitlab.js
Normal file
275
cypress/plugins/gitlab.js
Normal file
@ -0,0 +1,275 @@
|
||||
const { Gitlab } = require('gitlab');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const { updateConfig } = require('../utils/config');
|
||||
const { escapeRegExp } = require('../utils/regexp');
|
||||
const {
|
||||
getExpectationsFilename,
|
||||
transformRecordedData: transformData,
|
||||
getGitClient,
|
||||
} = require('./common');
|
||||
const { merge } = require('lodash');
|
||||
|
||||
const GITLAB_REPO_OWNER_SANITIZED_VALUE = 'owner';
|
||||
const GITLAB_REPO_NAME_SANITIZED_VALUE = 'repo';
|
||||
const GITLAB_REPO_TOKEN_SANITIZED_VALUE = 'fakeToken';
|
||||
|
||||
const FAKE_OWNER_USER = {
|
||||
id: 1,
|
||||
name: 'owner',
|
||||
username: 'owner',
|
||||
avatar_url: 'https://avatars1.githubusercontent.com/u/7892489?v=4',
|
||||
email: 'owner@email.com',
|
||||
login: 'owner',
|
||||
};
|
||||
|
||||
function getGitLabClient(token) {
|
||||
const client = new Gitlab({
|
||||
token,
|
||||
});
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
function getEnvs() {
|
||||
const {
|
||||
GITLAB_REPO_OWNER: owner,
|
||||
GITLAB_REPO_NAME: repo,
|
||||
GITLAB_REPO_TOKEN: token,
|
||||
} = process.env;
|
||||
if (!owner || !repo || !token) {
|
||||
throw new Error(
|
||||
'Please set GITLAB_REPO_OWNER, GITLAB_REPO_NAME, GITLAB_REPO_TOKEN environment variables',
|
||||
);
|
||||
}
|
||||
return { owner, repo, token };
|
||||
}
|
||||
|
||||
async function prepareTestGitLabRepo() {
|
||||
const { owner, repo, token } = getEnvs();
|
||||
|
||||
// postfix a random string to avoid collisions
|
||||
const postfix = Math.random().toString(32).slice(2);
|
||||
const testRepoName = `${repo}-${Date.now()}-${postfix}`;
|
||||
|
||||
const client = getGitLabClient(token);
|
||||
|
||||
console.info('Creating repository', testRepoName);
|
||||
await client.Projects.create({
|
||||
name: testRepoName,
|
||||
lfs_enabled: false,
|
||||
});
|
||||
|
||||
const tempDir = path.join('.temp', testRepoName);
|
||||
await fs.remove(tempDir);
|
||||
let git = getGitClient();
|
||||
|
||||
const repoUrl = `git@gitlab.com:${owner}/${repo}.git`;
|
||||
|
||||
console.info('Cloning repository', repoUrl);
|
||||
await git.clone(repoUrl, tempDir);
|
||||
git = getGitClient(tempDir);
|
||||
|
||||
console.info('Pushing to new repository', testRepoName);
|
||||
|
||||
await git.removeRemote('origin');
|
||||
await git.addRemote('origin', `https://oauth2:${token}@gitlab.com/${owner}/${testRepoName}`);
|
||||
await git.push(['-u', 'origin', 'main']);
|
||||
|
||||
await client.ProtectedBranches.unprotect(`${owner}/${testRepoName}`, 'main');
|
||||
|
||||
return { owner, repo: testRepoName, tempDir };
|
||||
}
|
||||
|
||||
async function getAuthenticatedUser(token) {
|
||||
const client = getGitLabClient(token);
|
||||
const user = await client.Users.current();
|
||||
return { ...user, token, backendName: 'gitlab' };
|
||||
}
|
||||
|
||||
async function getUser() {
|
||||
const { token } = getEnvs();
|
||||
return getAuthenticatedUser(token);
|
||||
}
|
||||
|
||||
async function deleteRepositories({ owner, repo, tempDir }) {
|
||||
const { token } = getEnvs();
|
||||
|
||||
const errorHandler = e => {
|
||||
if (e.status !== 404) {
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
console.info('Deleting repository', `${owner}/${repo}`);
|
||||
await fs.remove(tempDir);
|
||||
|
||||
const client = getGitLabClient(token);
|
||||
await client.Projects.remove(`${owner}/${repo}`).catch(errorHandler);
|
||||
}
|
||||
|
||||
async function resetOriginRepo({ owner, repo, tempDir }) {
|
||||
console.info('Resetting origin repo:', `${owner}/${repo}`);
|
||||
|
||||
const { token } = getEnvs();
|
||||
const client = getGitLabClient(token);
|
||||
|
||||
const projectId = `${owner}/${repo}`;
|
||||
const mergeRequests = await client.MergeRequests.all({
|
||||
projectId,
|
||||
state: 'opened',
|
||||
});
|
||||
const ids = mergeRequests.map(mr => mr.iid);
|
||||
console.info('Closing merge requests:', ids);
|
||||
await Promise.all(
|
||||
ids.map(id => client.MergeRequests.edit(projectId, id, { state_event: 'close' })),
|
||||
);
|
||||
const branches = await client.Branches.all(projectId);
|
||||
const toDelete = branches.filter(b => b.name !== 'main').map(b => b.name);
|
||||
|
||||
console.info('Deleting branches', toDelete);
|
||||
await Promise.all(toDelete.map(branch => client.Branches.remove(projectId, branch)));
|
||||
|
||||
console.info('Resetting main');
|
||||
const git = getGitClient(tempDir);
|
||||
await git.push(['--force', 'origin', 'main']);
|
||||
console.info('Done resetting origin repo:', `${owner}/${repo}`);
|
||||
}
|
||||
|
||||
async function resetRepositories({ owner, repo, tempDir }) {
|
||||
await resetOriginRepo({ owner, repo, tempDir });
|
||||
}
|
||||
|
||||
async function setupGitLab(options) {
|
||||
console.info('Running tests - live data will be used!');
|
||||
const [user, repoData] = await Promise.all([getUser(), prepareTestGitLabRepo()]);
|
||||
|
||||
await updateConfig(config => {
|
||||
merge(config, options, {
|
||||
backend: {
|
||||
repo: `${repoData.owner}/${repoData.repo}`,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return { ...repoData, user };
|
||||
}
|
||||
|
||||
async function teardownGitLab(taskData) {
|
||||
await deleteRepositories(taskData);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function setupGitLabTest(taskData) {
|
||||
await resetRepositories(taskData);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const sanitizeString = (str, { owner, repo, token, ownerName }) => {
|
||||
let replaced = str
|
||||
.replace(new RegExp(escapeRegExp(owner), 'g'), GITLAB_REPO_OWNER_SANITIZED_VALUE)
|
||||
.replace(new RegExp(escapeRegExp(repo), 'g'), GITLAB_REPO_NAME_SANITIZED_VALUE)
|
||||
.replace(new RegExp(escapeRegExp(token), 'g'), GITLAB_REPO_TOKEN_SANITIZED_VALUE)
|
||||
.replace(
|
||||
new RegExp('https://secure.gravatar.+?/u/.+?v=\\d', 'g'),
|
||||
`${FAKE_OWNER_USER.avatar_url}`,
|
||||
);
|
||||
|
||||
if (ownerName) {
|
||||
replaced = replaced.replace(new RegExp(escapeRegExp(ownerName), 'g'), FAKE_OWNER_USER.name);
|
||||
}
|
||||
|
||||
return replaced;
|
||||
};
|
||||
|
||||
const transformRecordedData = (expectation, toSanitize) => {
|
||||
const requestBodySanitizer = httpRequest => {
|
||||
let body;
|
||||
if (httpRequest.body && httpRequest.body.type === 'JSON' && httpRequest.body.json) {
|
||||
const bodyObject =
|
||||
typeof httpRequest.body.json === 'string'
|
||||
? JSON.parse(httpRequest.body.json)
|
||||
: httpRequest.body.json;
|
||||
|
||||
if (bodyObject.encoding === 'base64') {
|
||||
// sanitize encoded data
|
||||
const decodedBody = Buffer.from(bodyObject.content, 'base64').toString('binary');
|
||||
const sanitizedContent = sanitizeString(decodedBody, toSanitize);
|
||||
const sanitizedEncodedContent = Buffer.from(sanitizedContent, 'binary').toString('base64');
|
||||
bodyObject.content = sanitizedEncodedContent;
|
||||
body = JSON.stringify(bodyObject);
|
||||
} else {
|
||||
body = JSON.stringify(bodyObject);
|
||||
}
|
||||
} else if (httpRequest.body && httpRequest.body.type === 'STRING' && httpRequest.body.string) {
|
||||
body = sanitizeString(httpRequest.body.string, toSanitize);
|
||||
} else if (httpRequest.body) {
|
||||
const str =
|
||||
typeof httpRequest.body !== 'string' ? JSON.stringify(httpRequest.body) : httpRequest.body;
|
||||
body = sanitizeString(str, toSanitize);
|
||||
}
|
||||
return body;
|
||||
};
|
||||
|
||||
const responseBodySanitizer = (httpRequest, httpResponse) => {
|
||||
let responseBody = null;
|
||||
if (httpResponse.body && httpResponse.body.string) {
|
||||
responseBody = httpResponse.body.string;
|
||||
} else if (
|
||||
httpResponse.body &&
|
||||
httpResponse.body.type === 'BINARY' &&
|
||||
httpResponse.body.base64Bytes
|
||||
) {
|
||||
responseBody = {
|
||||
encoding: 'base64',
|
||||
content: httpResponse.body.base64Bytes,
|
||||
};
|
||||
} else if (httpResponse.body && httpResponse.body.json) {
|
||||
responseBody = JSON.stringify(httpResponse.body.json);
|
||||
} else {
|
||||
responseBody =
|
||||
typeof httpResponse.body === 'string'
|
||||
? httpResponse.body
|
||||
: httpResponse.body && JSON.stringify(httpResponse.body);
|
||||
}
|
||||
|
||||
// replace recorded user with fake one
|
||||
if (
|
||||
responseBody &&
|
||||
httpRequest.path === '/api/v4/user' &&
|
||||
httpRequest.headers.host.includes('gitlab.com')
|
||||
) {
|
||||
responseBody = JSON.stringify(FAKE_OWNER_USER);
|
||||
}
|
||||
|
||||
return responseBody;
|
||||
};
|
||||
|
||||
const cypressRouteOptions = transformData(
|
||||
expectation,
|
||||
requestBodySanitizer,
|
||||
responseBodySanitizer,
|
||||
);
|
||||
|
||||
return cypressRouteOptions;
|
||||
};
|
||||
|
||||
const defaultOptions = {
|
||||
transformRecordedData,
|
||||
};
|
||||
|
||||
async function teardownGitLabTest(taskData, { transformRecordedData } = defaultOptions) {
|
||||
await resetRepositories(taskData);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
transformRecordedData,
|
||||
setupGitLab,
|
||||
teardownGitLab,
|
||||
setupGitLabTest,
|
||||
teardownGitLabTest,
|
||||
};
|
178
cypress/plugins/index.ts
Normal file
178
cypress/plugins/index.ts
Normal file
@ -0,0 +1,178 @@
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
import 'dotenv/config';
|
||||
|
||||
import { addMatchImageSnapshotPlugin } from '@simonsmith/cypress-image-snapshot/plugin';
|
||||
import merge from 'lodash/merge';
|
||||
|
||||
// const { setupGitHub, teardownGitHub, setupGitHubTest, teardownGitHubTest, seedGitHubRepo } = require("./github");
|
||||
// const { setupGitGateway, teardownGitGateway, setupGitGatewayTest, teardownGitGatewayTest } = require("./gitGateway");
|
||||
// const { setupGitLab, teardownGitLab, setupGitLabTest, teardownGitLabTest } = require("./gitlab");
|
||||
// const { setupBitBucket, teardownBitBucket, setupBitBucketTest, teardownBitBucketTest } = require("./bitbucket");
|
||||
// const { setupProxy, teardownProxy, setupProxyTest, teardownProxyTest } = require("./proxy");
|
||||
import { setupTestBackend } from './testBackend';
|
||||
|
||||
import { copyBackendFiles, switchVersion, updateConfig } from '../utils/config';
|
||||
|
||||
import type { Config } from '@staticcms/core/interface';
|
||||
import type {
|
||||
SeedRepoProps,
|
||||
SetupBackendProps,
|
||||
SetupBackendResponse,
|
||||
SetupBackendTestProps,
|
||||
TeardownBackendProps,
|
||||
TeardownBackendTestProps,
|
||||
} from '../interface';
|
||||
|
||||
export default async (on: Cypress.PluginEvents) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
on('task', {
|
||||
async setupBackend({ backend, options }: SetupBackendProps): Promise<SetupBackendResponse> {
|
||||
console.info('Preparing environment for backend', backend);
|
||||
await copyBackendFiles(backend);
|
||||
|
||||
let result = null;
|
||||
switch (backend) {
|
||||
// case "github":
|
||||
// result = await setupGitHub(options);
|
||||
// break;
|
||||
// case "git-gateway":
|
||||
// result = await setupGitGateway(options);
|
||||
// break;
|
||||
// case "gitlab":
|
||||
// result = await setupGitLab(options);
|
||||
// break;
|
||||
// case "bitbucket":
|
||||
// result = await setupBitBucket(options);
|
||||
// break;
|
||||
// case "proxy":
|
||||
// result = await setupProxy(options);
|
||||
// break;
|
||||
case 'test':
|
||||
result = await setupTestBackend(options);
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
async teardownBackend({ backend }: TeardownBackendProps): Promise<null> {
|
||||
console.info('Tearing down backend', backend);
|
||||
|
||||
switch (
|
||||
backend
|
||||
// case "github":
|
||||
// await teardownGitHub(taskData);
|
||||
// break;
|
||||
// case "git-gateway":
|
||||
// await teardownGitGateway(taskData);
|
||||
// break;
|
||||
// case "gitlab":
|
||||
// await teardownGitLab(taskData);
|
||||
// break;
|
||||
// case "bitbucket":
|
||||
// await teardownBitBucket(taskData);
|
||||
// break;
|
||||
// case "proxy":
|
||||
// await teardownProxy(taskData);
|
||||
// break;
|
||||
) {
|
||||
}
|
||||
|
||||
console.info('Restoring defaults');
|
||||
await copyBackendFiles('test');
|
||||
|
||||
return null;
|
||||
},
|
||||
async setupBackendTest({ backend, testName }: SetupBackendTestProps): Promise<null> {
|
||||
console.info(`Setting up single test '${testName}' for backend`, backend);
|
||||
|
||||
switch (
|
||||
backend
|
||||
// case "github":
|
||||
// await setupGitHubTest(taskData);
|
||||
// break;
|
||||
// case "git-gateway":
|
||||
// await setupGitGatewayTest(taskData);
|
||||
// break;
|
||||
// case "gitlab":
|
||||
// await setupGitLabTest(taskData);
|
||||
// break;
|
||||
// case "bitbucket":
|
||||
// await setupBitBucketTest(taskData);
|
||||
// break;
|
||||
// case "proxy":
|
||||
// await setupProxyTest(taskData);
|
||||
// break;
|
||||
) {
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
async teardownBackendTest({ backend, testName }: TeardownBackendTestProps): Promise<null> {
|
||||
console.info(`Tearing down single test '${testName}' for backend`, backend);
|
||||
|
||||
switch (
|
||||
backend
|
||||
// case "github":
|
||||
// await teardownGitHubTest(taskData);
|
||||
// break;
|
||||
// case "git-gateway":
|
||||
// await teardownGitGatewayTest(taskData);
|
||||
// break;
|
||||
// case "gitlab":
|
||||
// await teardownGitLabTest(taskData);
|
||||
// break;
|
||||
// case "bitbucket":
|
||||
// await teardownBitBucketTest(taskData);
|
||||
// break;
|
||||
// case "proxy":
|
||||
// await teardownProxyTest(taskData);
|
||||
// break;
|
||||
) {
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
async seedRepo({ backend }: SeedRepoProps): Promise<null> {
|
||||
console.info(`Seeding repository for backend`, backend);
|
||||
|
||||
switch (
|
||||
backend
|
||||
// case "github":
|
||||
// await seedGitHubRepo(taskData);
|
||||
// break;
|
||||
) {
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
async switchToVersion(taskData) {
|
||||
const { version } = taskData;
|
||||
|
||||
console.info(`Switching CMS to version '${version}'`);
|
||||
|
||||
await switchVersion(version);
|
||||
|
||||
return null;
|
||||
},
|
||||
async updateConfig(config: Partial<Config>) {
|
||||
await updateConfig(current => {
|
||||
merge(current, config);
|
||||
});
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
addMatchImageSnapshotPlugin(on);
|
||||
};
|
105
cypress/plugins/proxy.js
Normal file
105
cypress/plugins/proxy.js
Normal file
@ -0,0 +1,105 @@
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const { spawn } = require('child_process');
|
||||
const { merge } = require('lodash');
|
||||
|
||||
const { updateConfig } = require('../utils/config');
|
||||
const { getGitClient } = require('./common');
|
||||
|
||||
const initRepo = async dir => {
|
||||
await fs.remove(dir);
|
||||
await fs.mkdirp(dir);
|
||||
const git = getGitClient(dir);
|
||||
await git.init({ '--initial-branch': 'main' });
|
||||
await git.addConfig('user.email', 'cms-cypress-test@netlify.com');
|
||||
await git.addConfig('user.name', 'cms-cypress-test');
|
||||
|
||||
const readme = 'README.md';
|
||||
await fs.writeFile(path.join(dir, readme), '');
|
||||
await git.add(readme);
|
||||
await git.commit('initial commit', readme, { '--no-verify': true, '--no-gpg-sign': true });
|
||||
};
|
||||
|
||||
const startServer = async (repoDir, mode) => {
|
||||
const tsNode = path.join(__dirname, '..', '..', 'node_modules', '.bin', 'ts-node');
|
||||
const serverDir = path.join(__dirname, '..', '..', 'packages', 'static-server');
|
||||
const distIndex = path.join(serverDir, 'dist', 'index.js');
|
||||
const tsIndex = path.join(serverDir, 'src', 'index.ts');
|
||||
|
||||
const port = 8082;
|
||||
const env = {
|
||||
...process.env,
|
||||
GIT_REPO_DIRECTORY: path.resolve(repoDir),
|
||||
PORT: port,
|
||||
MODE: mode,
|
||||
};
|
||||
|
||||
console.info(`Starting proxy server on port '${port}' with mode ${mode}`);
|
||||
if (await fs.pathExists(distIndex)) {
|
||||
serverProcess = spawn('node', [distIndex], { env, cwd: serverDir });
|
||||
} else {
|
||||
serverProcess = spawn(tsNode, ['--files', tsIndex], { env, cwd: serverDir });
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
serverProcess.stdout.on('data', data => {
|
||||
const message = data.toString().trim();
|
||||
console.info(`server:stdout: ${message}`);
|
||||
if (message.includes('Static CMS Proxy Server listening on port')) {
|
||||
resolve(serverProcess);
|
||||
}
|
||||
});
|
||||
|
||||
serverProcess.stderr.on('data', data => {
|
||||
console.error(`server:stderr: ${data.toString().trim()}`);
|
||||
reject(data.toString());
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
let serverProcess;
|
||||
|
||||
async function setupProxy(options) {
|
||||
const postfix = Math.random().toString(32).slice(2);
|
||||
|
||||
const testRepoName = `proxy-test-repo-${Date.now()}-${postfix}`;
|
||||
const tempDir = path.join('.temp', testRepoName);
|
||||
|
||||
const { mode, ...rest } = options;
|
||||
|
||||
await updateConfig(config => {
|
||||
merge(config, rest);
|
||||
});
|
||||
|
||||
return { tempDir, mode };
|
||||
}
|
||||
|
||||
async function teardownProxy(taskData) {
|
||||
if (serverProcess) {
|
||||
serverProcess.kill();
|
||||
}
|
||||
await fs.remove(taskData.tempDir);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function setupProxyTest(taskData) {
|
||||
await initRepo(taskData.tempDir);
|
||||
serverProcess = await startServer(taskData.tempDir, taskData.mode);
|
||||
return null;
|
||||
}
|
||||
|
||||
async function teardownProxyTest(taskData) {
|
||||
if (serverProcess) {
|
||||
serverProcess.kill();
|
||||
}
|
||||
await fs.remove(taskData.tempDir);
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setupProxy,
|
||||
teardownProxy,
|
||||
setupProxyTest,
|
||||
teardownProxyTest,
|
||||
};
|
13
cypress/plugins/testBackend.ts
Normal file
13
cypress/plugins/testBackend.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import merge from 'lodash/merge';
|
||||
import { updateConfig } from '../utils/config';
|
||||
|
||||
import type { Config } from '@staticcms/core/interface';
|
||||
import type { SetupBackendResponse } from '../interface';
|
||||
|
||||
export async function setupTestBackend(options: Partial<Config>): Promise<SetupBackendResponse> {
|
||||
await updateConfig(current => {
|
||||
merge(current, options);
|
||||
});
|
||||
|
||||
return null;
|
||||
}
|
Reference in New Issue
Block a user