angular/aio/scripts/deploy-to-firebase/index.spec.mjs
George Kalpakas 8d8cdc1991 ci: improve angular.io deployment process for the master branch (#43963)
Previously, the master branch was only deployed to the
`next-angular-io-site` Firebase site, which is connected to the
`next.angular.io` domain. However, if the master major version was
higher than the stable major version (or the RC major version in case
there was an active RC), we also had to manually configure (via the
Firebase console and/or DNS records) the `v<X>.angular.io` domain to
redirect to `next.angular.io`. Then, once `<X>` became the new stable or
RC version, we had to manually remove the redirect (to let
`v<X>.angular.io` be redirected to `angular.io` or `rc.angular.io`).

This commit is part of a new process that reduces the manual steps as
follows (the steps below only apply when the master major version is
higher than the current stable and RC (if applicable)):
- A `v<X>-angular-io-site` Firebase site will be created as soon as the
  version in the `master` branch's `package.json` is updated to a new
  major.
- The `v<X>.angular.io` domain will be connected to that new Firebase
  site.
- When deploying from the master branch, we will deploy to both
  `next-angular-io-site` and `v<X>-angular-io-site`. In addition, the
  deployment to `v<X>-angular-io-site` will update the Firebase config
  file to redirect to `next.angular.io`.
- When the master version becomes the new stable/RC, we will start
  deploying to `v<X>-angular-io-site` from the stable/RC branch, which
  will update the Firebase config to stop redirecting to
  `next.angular.io` and redirect to `(rc.)angular.io` instead (without
  requiring changes in the Firebase console or DNS).

PR Close #43963
2021-10-29 15:05:04 -07:00

777 lines
26 KiB
JavaScript

import {execSync} from 'child_process';
import {
computeDeploymentsInfo,
computeInputVars,
skipDeployment,
validateDeploymentsInfo,
} from './index.mjs';
import u from './utils.mjs';
describe('deploy-to-firebase:', () => {
// Pre-computed values to avoid unnecessary re-computations.
let mostRecentMinorBranch;
let latestCommits;
beforeAll(() => {
// Computing the values involves network requests and may add a noticeable delay (during which
// nothing is printed to stdout). Print a message to let the user know what is happening.
console.log('\nPre-computing values for \'deploy-to-firebase\' tests...');
mostRecentMinorBranch = u.getMostRecentMinorBranch();
latestCommits = {
master: u.getLatestCommit('master'),
'2.1.x': u.getLatestCommit('2.1.x'),
'2.4.x': u.getLatestCommit('2.4.x'),
'4.3.x': u.getLatestCommit('4.3.x'),
'4.4.x': u.getLatestCommit('4.4.x'),
'9.1.x': u.getLatestCommit('9.1.x'),
[mostRecentMinorBranch]: u.getLatestCommit(mostRecentMinorBranch),
};
});
// Helpers
const jsonFunctionReplacer = (_key, val) =>
(typeof val === 'function') ? `function:${val.name}` : val;
const getDeploymentsInfoFor = env => {
const deploymentsInfo = computeDeploymentsInfo(computeInputVars(env));
validateDeploymentsInfo(deploymentsInfo);
return JSON.parse(JSON.stringify(deploymentsInfo, jsonFunctionReplacer));
};
it('master - skip deploy - not angular', () => {
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'notangular',
})).toEqual([
{
name: 'skipped',
type: 'skipped',
reason: 'Skipping deploy because this is not angular/angular.',
},
]);
});
it('master - skip deploy - angular fork', () => {
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'notangular',
CI_REPO_NAME: 'angular',
})).toEqual([
{
name: 'skipped',
type: 'skipped',
reason: 'Skipping deploy because this is not angular/angular.',
},
]);
});
it('master - skip deploy - pull request', () => {
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'true',
})).toEqual([
{
name: 'skipped',
type: 'skipped',
reason: 'Skipping deploy because this is a PR build.',
},
]);
});
it('master - deploy success - no active RC, major higher than stable', () => {
const mostRecentMajorVersion = u.computeMajorVersion(mostRecentMinorBranch);
const fakeMasterMajorVersion = mostRecentMajorVersion + 1;
// Fake the `package.json` version.
spyOn(u, 'loadJson').and.returnValue({version: `${fakeMasterMajorVersion}.0.0-next.42`});
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'false',
CI_BRANCH: 'master',
CI_STABLE_BRANCH: mostRecentMinorBranch,
CI_COMMIT: latestCommits.master,
})).toEqual([
{
name: 'next',
type: 'primary',
deployEnv: 'next',
projectId: 'angular-io',
siteId: 'next-angular-io-site',
deployedUrl: 'https://next.angular.io/',
preDeployActions: ['function:build', 'function:checkPayloadSize'],
postDeployActions: ['function:testPwaScore'],
},
{
name: 'redirectVersionDomainToNext',
type: 'secondary',
deployEnv: 'next',
projectId: 'angular-io',
siteId: `v${fakeMasterMajorVersion}-angular-io-site`,
deployedUrl: `https://v${fakeMasterMajorVersion}.angular.io/`,
preDeployActions: ['function:redirectAllToNext'],
postDeployActions: ['function:undoRedirectAllToNext', 'function:testRedirectToNext'],
},
]);
});
it('master - deploy success - no active RC, major same as stable', () => {
const mostRecentMajorVersion = u.computeMajorVersion(mostRecentMinorBranch);
// Fake the `package.json` version.
spyOn(u, 'loadJson').and.returnValue({version: `${mostRecentMajorVersion}.42.0`});
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'false',
CI_BRANCH: 'master',
CI_STABLE_BRANCH: mostRecentMinorBranch,
CI_COMMIT: latestCommits.master,
})).toEqual([
{
name: 'next',
type: 'primary',
deployEnv: 'next',
projectId: 'angular-io',
siteId: 'next-angular-io-site',
deployedUrl: 'https://next.angular.io/',
preDeployActions: ['function:build', 'function:checkPayloadSize'],
postDeployActions: ['function:testPwaScore'],
},
]);
});
it('master - deploy success - active RC, major higher than RC and stable', () => {
const mostRecentMajorVersion = u.computeMajorVersion(mostRecentMinorBranch);
const fakeMasterMajorVersion = mostRecentMajorVersion + 1;
// Fake the `package.json` version.
spyOn(u, 'loadJson').and.returnValue({version: `${fakeMasterMajorVersion}.0.0-next.42`});
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'false',
CI_BRANCH: 'master',
CI_STABLE_BRANCH: '4.4.x',
CI_COMMIT: latestCommits.master,
})).toEqual([
{
name: 'next',
type: 'primary',
deployEnv: 'next',
projectId: 'angular-io',
siteId: 'next-angular-io-site',
deployedUrl: 'https://next.angular.io/',
preDeployActions: ['function:build', 'function:checkPayloadSize'],
postDeployActions: ['function:testPwaScore'],
},
{
name: 'redirectVersionDomainToNext',
type: 'secondary',
deployEnv: 'next',
projectId: 'angular-io',
siteId: `v${fakeMasterMajorVersion}-angular-io-site`,
deployedUrl: `https://v${fakeMasterMajorVersion}.angular.io/`,
preDeployActions: ['function:redirectAllToNext'],
postDeployActions: ['function:undoRedirectAllToNext', 'function:testRedirectToNext'],
},
]);
});
it('master - deploy success - active RC, major same as RC and higher than stable', () => {
const mostRecentMajorVersion = u.computeMajorVersion(mostRecentMinorBranch);
// Fake the `package.json` version.
spyOn(u, 'loadJson').and.returnValue({version: `${mostRecentMajorVersion}.0.0-next.42`});
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'false',
CI_BRANCH: 'master',
CI_STABLE_BRANCH: '4.4.x',
CI_COMMIT: latestCommits.master,
})).toEqual([
{
name: 'next',
type: 'primary',
deployEnv: 'next',
projectId: 'angular-io',
siteId: 'next-angular-io-site',
deployedUrl: 'https://next.angular.io/',
preDeployActions: ['function:build', 'function:checkPayloadSize'],
postDeployActions: ['function:testPwaScore'],
},
]);
});
it('master - skip deploy - commit not HEAD', () => {
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'false',
CI_BRANCH: 'master',
CI_COMMIT: 'DUMMY_TEST_COMMIT',
})).toEqual([
{
name: 'skipped',
type: 'skipped',
reason:
'Skipping deploy because DUMMY_TEST_COMMIT is not the latest commit ' +
`(${latestCommits.master}).`,
},
]);
});
it('stable - deploy success - active RC', () => {
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'false',
CI_BRANCH: '4.3.x',
CI_STABLE_BRANCH: '4.3.x',
CI_COMMIT: latestCommits['4.3.x'],
})).toEqual([
{
name: 'stable',
type: 'primary',
deployEnv: 'stable',
projectId: 'angular-io',
siteId: 'stable-angular-io-site',
deployedUrl: 'https://angular.io/',
preDeployActions: ['function:build', 'function:checkPayloadSize'],
postDeployActions: ['function:testPwaScore'],
},
{
name: 'redirectVersionDomainToStable',
type: 'secondary',
deployEnv: 'stable',
projectId: 'angular-io',
siteId: 'v4-angular-io-site',
deployedUrl: 'https://v4.angular.io/',
preDeployActions: ['function:redirectAllToStable'],
postDeployActions: ['function:undoRedirectAllToStable', 'function:testRedirectToStable'],
},
]);
});
it('stable - deploy success - no active RC', () => {
const stableMajorVersion = u.computeMajorVersion(mostRecentMinorBranch);
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'false',
CI_BRANCH: mostRecentMinorBranch,
CI_STABLE_BRANCH: mostRecentMinorBranch,
CI_COMMIT: latestCommits[mostRecentMinorBranch],
})).toEqual([
{
name: 'stable',
type: 'primary',
deployEnv: 'stable',
projectId: 'angular-io',
siteId: 'stable-angular-io-site',
deployedUrl: 'https://angular.io/',
preDeployActions: ['function:build', 'function:checkPayloadSize'],
postDeployActions: ['function:testPwaScore'],
},
{
name: 'redirectVersionDomainToStable',
type: 'secondary',
deployEnv: 'stable',
projectId: 'angular-io',
siteId: `v${stableMajorVersion}-angular-io-site`,
deployedUrl: `https://v${stableMajorVersion}.angular.io/`,
preDeployActions: ['function:redirectAllToStable'],
postDeployActions: ['function:undoRedirectAllToStable', 'function:testRedirectToStable'],
},
{
name: 'redirectRcToStable',
type: 'secondary',
deployEnv: 'stable',
projectId: 'angular-io',
siteId: 'rc-angular-io-site',
deployedUrl: 'https://rc.angular.io/',
preDeployActions: ['function:disableServiceWorker', 'function:redirectNonFilesToStable'],
postDeployActions: [
'function:undoRedirectNonFilesToStable',
'function:undoDisableServiceWorker',
'function:testNoActiveRcDeployment',
],
},
]);
});
it('stable - skip deploy - commit not HEAD', () => {
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'false',
CI_BRANCH: '4.3.x',
CI_STABLE_BRANCH: '4.3.x',
CI_COMMIT: 'DUMMY_TEST_COMMIT',
})).toEqual([
{
name: 'skipped',
type: 'skipped',
reason:
'Skipping deploy because DUMMY_TEST_COMMIT is not the latest commit ' +
`(${latestCommits['4.3.x']}).`,
},
]);
});
it('archive - deploy success', () => {
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'false',
CI_BRANCH: '2.4.x',
CI_STABLE_BRANCH: '4.3.x',
CI_COMMIT: latestCommits['2.4.x'],
})).toEqual([
{
name: 'archive',
type: 'primary',
deployEnv: 'archive',
projectId: 'angular-io',
siteId: 'v2-angular-io-site',
deployedUrl: 'https://v2.angular.io/',
preDeployActions: ['function:build', 'function:checkPayloadSize'],
postDeployActions: ['function:testPwaScore'],
},
]);
});
// v9 used to be special-cased, because it was piloting the Firebase hosting "multisites" setup.
// See https://angular-team.atlassian.net/browse/DEV-125 for more info.
it('archive - deploy success (no special case for v9)', () => {
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'false',
CI_BRANCH: '9.1.x',
CI_STABLE_BRANCH: '10.0.x',
CI_COMMIT: latestCommits['9.1.x'],
})).toEqual([
{
name: 'archive',
type: 'primary',
deployEnv: 'archive',
projectId: 'angular-io',
siteId: 'v9-angular-io-site',
deployedUrl: 'https://v9.angular.io/',
preDeployActions: ['function:build', 'function:checkPayloadSize'],
postDeployActions: ['function:testPwaScore'],
},
]);
});
it('archive - skip deploy - commit not HEAD', () => {
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'false',
CI_BRANCH: '2.4.x',
CI_STABLE_BRANCH: '4.3.x',
CI_COMMIT: 'DUMMY_TEST_COMMIT',
})).toEqual([
{
name: 'skipped',
type: 'skipped',
reason:
'Skipping deploy because DUMMY_TEST_COMMIT is not the latest commit ' +
`(${latestCommits['2.4.x']}).`,
},
]);
});
it('archive - skip deploy - major same as stable, minor lower than stable', () => {
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'false',
CI_BRANCH: '2.1.x',
CI_STABLE_BRANCH: '2.2.x',
CI_COMMIT: latestCommits['2.1.x'],
})).toEqual([
{
name: 'skipped',
type: 'skipped',
reason:
'Skipping deploy of branch "2.1.x" to Firebase.\n' +
'There is a more recent branch with the same major version: "2.4.x"',
},
]);
});
it('archive - skip deploy - major lower than stable, minor not highest for major', () => {
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'false',
CI_BRANCH: '2.1.x',
CI_STABLE_BRANCH: '4.3.x',
CI_COMMIT: latestCommits['2.1.x'],
})).toEqual([
{
name: 'skipped',
type: 'skipped',
reason:
'Skipping deploy of branch "2.1.x" to Firebase.\n' +
'There is a more recent branch with the same major version: "2.4.x"',
},
]);
});
it('rc - deploy success - major higher than stable', () => {
const rcMajorVersion = u.computeMajorVersion(mostRecentMinorBranch);
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'false',
CI_BRANCH: mostRecentMinorBranch,
CI_STABLE_BRANCH: '2.2.x',
CI_COMMIT: latestCommits[mostRecentMinorBranch],
})).toEqual([
{
name: 'rc',
type: 'primary',
deployEnv: 'rc',
projectId: 'angular-io',
siteId: 'rc-angular-io-site',
deployedUrl: 'https://rc.angular.io/',
preDeployActions: ['function:build', 'function:checkPayloadSize'],
postDeployActions: ['function:testPwaScore'],
},
{
name: 'redirectVersionDomainToRc',
type: 'secondary',
deployEnv: 'rc',
projectId: 'angular-io',
siteId: `v${rcMajorVersion}-angular-io-site`,
deployedUrl: `https://v${rcMajorVersion}.angular.io/`,
preDeployActions: ['function:redirectAllToRc'],
postDeployActions: ['function:undoRedirectAllToRc', 'function:testRedirectToRc'],
},
]);
});
it('rc - deploy success - major same as stable, minor highest for major', () => {
// Create a stable branch name that has the same major and lower minor than
// `mostRecentMinorBranch`.
// NOTE: Since `mostRecentMinorBranch` can have a minor version of `0`, we may end up with `-1`
// as the minor version for stable. This is a hack, but it works ¯\_(ツ)_/¯
const stableBranch = mostRecentMinorBranch.replace(
/^(\d+)\.(\d+)\.x$/, (_, major, minor) => `${major}.${minor - 1}.x`);
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'false',
CI_BRANCH: mostRecentMinorBranch,
CI_STABLE_BRANCH: stableBranch,
CI_COMMIT: latestCommits[mostRecentMinorBranch],
})).toEqual([
{
name: 'rc',
type: 'primary',
deployEnv: 'rc',
projectId: 'angular-io',
siteId: 'rc-angular-io-site',
deployedUrl: 'https://rc.angular.io/',
preDeployActions: ['function:build', 'function:checkPayloadSize'],
postDeployActions: ['function:testPwaScore'],
},
]);
});
it('rc - skip deploy - commit not HEAD', () => {
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'false',
CI_BRANCH: mostRecentMinorBranch,
CI_STABLE_BRANCH: '2.2.x',
CI_COMMIT: 'DUMMY_TEST_COMMIT',
})).toEqual([
{
name: 'skipped',
type: 'skipped',
reason:
'Skipping deploy because DUMMY_TEST_COMMIT is not the latest commit ' +
`(${latestCommits[mostRecentMinorBranch]}).`,
},
]);
});
it('rc - skip deploy - major same as stable, minor not highest for major', () => {
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'false',
CI_BRANCH: '2.1.x',
CI_STABLE_BRANCH: '2.0.x',
CI_COMMIT: latestCommits['2.1.x'],
})).toEqual([
{
name: 'skipped',
type: 'skipped',
reason:
'Skipping deploy of branch "2.1.x" to Firebase.\n' +
'There is a more recent branch with the same major version: "2.4.x"',
},
]);
});
it('rc - skip deploy - major higher than stable, minor not highest for major', () => {
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'false',
CI_BRANCH: '4.3.x',
CI_STABLE_BRANCH: '2.4.x',
CI_COMMIT: latestCommits['4.3.x'],
})).toEqual([
{
name: 'skipped',
type: 'skipped',
reason:
'Skipping deploy of branch "4.3.x" to Firebase.\n' +
'There is a more recent branch with the same major version: "4.4.x"',
},
]);
});
it('rc - skip deploy - major higher than stable but not highest, minor highest for major', () => {
expect(getDeploymentsInfoFor({
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'false',
CI_BRANCH: '4.4.x',
CI_STABLE_BRANCH: '2.4.x',
CI_COMMIT: latestCommits['4.4.x'],
})).toEqual([
{
name: 'skipped',
type: 'skipped',
reason:
'Skipping deploy of branch "4.4.x" to Firebase.\n' +
'This branch has an equal or higher major version than the stable branch ("2.4.x") ' +
'and is not the most recent minor branch.',
},
]);
});
it('integration - should run the main script without error', () => {
// NOTE:
// This test executes a new instance of the `deploy-to-firebase` script on a separate process
// and thus does not share the `getRemoteRefs()` cache. To improve stability, we use an older
// branch (`4.4.x`) that is unlikely to receive any new commits (so the one retrieved at the
// beginning of the tests will still be the latest one for that branch).
const scriptPath = `${u.getDirname(import.meta.url)}/index.mjs`;
const cmd = `"${process.execPath}" "${scriptPath}" --dry-run`;
const env = {
CI_REPO_OWNER: 'angular',
CI_REPO_NAME: 'angular',
CI_PULL_REQUEST: 'false',
CI_BRANCH: '4.4.x',
CI_STABLE_BRANCH: mostRecentMinorBranch,
CI_COMMIT: latestCommits['4.4.x'],
};
const result = execSync(cmd, {encoding: 'utf8', env}).trim();
expect(result).toBe(
'Deployments (1): archive\n' +
'\n' +
'\n' +
'\n' +
'Deployment 1 of 1: archive\n' +
'--------------------------\n' +
'Git branch : 4.4.x\n' +
`Git commit : ${latestCommits['4.4.x']}\n` +
'Build/deploy mode : archive\n' +
'Firebase project : angular-io\n' +
'Firebase site : v4-angular-io-site\n' +
'Pre-deploy actions : build, checkPayloadSize\n' +
'Post-deploy actions : testPwaScore\n' +
'Deployment URLs : https://v4.angular.io/\n' +
' https://v4-angular-io-site.web.app/');
});
});
describe('validateDeploymentsInfo()', () => {
const createTarget = (name, type) => ({
name,
type,
deployEnv: 'deployEnv',
projectId: 'projectId',
siteId: 'siteId',
deployedUrl: 'deployedUrl',
preDeployActions: [],
postDeployActions: [],
});
it('should error if there are deploy targets with unknown types', () => {
const targets = [
createTarget('target-1', 'primary'),
createTarget('target-2', 'tertiary'),
createTarget('target-3', 'secondary'),
createTarget(undefined, 'other'),
];
expect(() => validateDeploymentsInfo(targets)).toThrowError(
'Expected all deploy targets to have a type of primary or secondary or skipped, but ' +
'found 2 targets with an unknown type: target-2 (type: tertiary), <no name> (type: other)');
});
it('should error if there are non-skipped targets missing required properties', () => {
// With target missing `name`.
const targets1 = [
createTarget('target-1', 'primary'),
createTarget(undefined, 'secondary'),
];
expect(() => validateDeploymentsInfo(targets1)).toThrowError(
'Expected deploy target \'<no name>\' to have all required properties, but it is missing ' +
'\'name\'.');
// With target missing multiple properties.
const targets2 = [
createTarget('target-1', 'primary'),
{
...createTarget('target-2', 'secondary'),
deployEnv: undefined,
postDeployActions: undefined,
},
];
expect(() => validateDeploymentsInfo(targets2)).toThrowError(
'Expected deploy target \'target-2\' to have all required properties, but it is missing ' +
'\'deployEnv\', \'postDeployActions\'.');
});
it('should error if there are skipped targets missing required properties', () => {
// With target missing `name`.
const targets1 = [
createTarget('target-1', 'primary'),
{...skipDeployment('just because'), name: undefined},
];
expect(() => validateDeploymentsInfo(targets1)).toThrowError(
'Expected deploy target \'<no name>\' to have all required properties, but it is missing ' +
'\'name\'.');
// With target missing `reason`.
const targets2 = [
createTarget('target-1', 'primary'),
skipDeployment(undefined),
];
expect(() => validateDeploymentsInfo(targets2)).toThrowError(
'Expected deploy target \'skipped\' to have all required properties, but it is missing ' +
'\'reason\'.');
});
it('should error if there are both skipped and non-skipped targets', () => {
const targets = [
skipDeployment('just because'),
createTarget('target-2', 'secondary'),
];
expect(() => validateDeploymentsInfo(targets)).toThrowError(
'Expected a single skipped deploy target, but found 2 targets in total: skipped, target-2');
});
it('should error if there are multiple skipped targets', () => {
const targets = [
skipDeployment('just because'),
skipDeployment('because why not'),
];
expect(() => validateDeploymentsInfo(targets)).toThrowError(
'Expected a single skipped deploy target, but found 2 targets in total: skipped, skipped');
});
it('should error if there is no primary target', () => {
const targets = [
createTarget('target-1', 'secondary'),
createTarget('target-2', 'secondary'),
];
expect(() => validateDeploymentsInfo(targets)).toThrowError(
'Expected exactly one primary deploy target, but found 0: -');
});
it('should error if there is more than one primary target', () => {
const targets = [
createTarget('target-1', 'primary'),
createTarget('target-2', 'secondary'),
createTarget('target-3', 'primary'),
];
expect(() => validateDeploymentsInfo(targets)).toThrowError(
'Expected exactly one primary deploy target, but found 2: target-1, target-3');
});
it('should error if the primary target is not the first item in the list', () => {
const targets = [
createTarget('target-1', 'secondary'),
createTarget('target-2', 'primary'),
createTarget('target-3', 'secondary'),
];
expect(() => validateDeploymentsInfo(targets)).toThrowError(
'Expected the primary target (target-2) to be the first item in the deploy target list, ' +
'but it was found at index 1 (0-based): target-1, target-2, target-3');
});
it('should error if there are secondary targets with a different `deployEnv` than primary',
() => {
const targets = [
{...createTarget('target-1', 'primary'), deployEnv: 'deploy-env-1'},
{...createTarget('target-2', 'secondary'), deployEnv: 'deploy-env-1'},
{...createTarget('target-3', 'secondary'), deployEnv: 'deploy-env-2'},
{...createTarget('target-4', 'secondary'), deployEnv: 'deploy-env-1'},
{...createTarget('target-5', 'secondary'), deployEnv: 'deploy-env-2'},
{...createTarget('target-6', 'secondary'), deployEnv: 'deploy-env-3'},
];
expect(() => validateDeploymentsInfo(targets)).toThrowError(
'Expected all secondary deploy targets to match the primary target\'s `deployEnv` ' +
'(deploy-env-1), but 3 targets do not: target-3 (deployEnv: deploy-env-2), target-5 ' +
'(deployEnv: deploy-env-2), target-6 (deployEnv: deploy-env-3)');
});
it('should succeed with a valid skipped target', () => {
const targets = [
skipDeployment('due to valid reasons'),
];
expect(() => validateDeploymentsInfo(targets)).not.toThrow();
});
it('should succeed with a valid non-skipped target', () => {
const targets = [
createTarget('target-1', 'primary'),
];
expect(() => validateDeploymentsInfo(targets)).not.toThrow();
});
it('should succeed with multiple valid non-skipped targets', () => {
const targets = [
createTarget('target-1', 'primary'),
createTarget('target-2', 'secondary'),
createTarget('target-3', 'secondary'),
createTarget('target-4', 'secondary'),
];
expect(() => validateDeploymentsInfo(targets)).not.toThrow();
});
});