212 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			212 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
|  | /** | ||
|  |  * @license | ||
|  |  * Copyright Google LLC All Rights Reserved. | ||
|  |  * | ||
|  |  * Use of this source code is governed by an MIT-style license that can be | ||
|  |  * found in the LICENSE file at https://angular.io/license
 | ||
|  |  */ | ||
|  | 
 | ||
|  | import {readFileSync} from 'fs'; | ||
|  | import {join} from 'path'; | ||
|  | import * as semver from 'semver'; | ||
|  | 
 | ||
|  | import {getBranchPushMatcher} from '../../../utils/testing'; | ||
|  | import {ActiveReleaseTrains} from '../../versioning/active-release-trains'; | ||
|  | import * as npm from '../../versioning/npm-publish'; | ||
|  | import {ReleaseTrain} from '../../versioning/release-trains'; | ||
|  | import {ReleaseAction} from '../actions'; | ||
|  | import {actions} from '../actions/index'; | ||
|  | import {changelogPath} from '../constants'; | ||
|  | 
 | ||
|  | import {getChangelogForVersion, getTestingMocksForReleaseAction, parse, setupReleaseActionForTesting, testTmpDir} from './test-utils'; | ||
|  | 
 | ||
|  | describe('common release action logic', () => { | ||
|  |   const baseReleaseTrains: ActiveReleaseTrains = { | ||
|  |     releaseCandidate: null, | ||
|  |     next: new ReleaseTrain('master', parse('10.1.0-next.0')), | ||
|  |     latest: new ReleaseTrain('10.0.x', parse('10.0.1')), | ||
|  |   }; | ||
|  | 
 | ||
|  |   describe('version computation', async () => { | ||
|  |     const testReleaseTrain: ActiveReleaseTrains = { | ||
|  |       releaseCandidate: new ReleaseTrain('10.1.x', parse('10.1.0-next.3')), | ||
|  |       next: new ReleaseTrain('master', parse('10.2.0-next.0')), | ||
|  |       latest: new ReleaseTrain('10.0.x', parse('10.0.1')), | ||
|  |     }; | ||
|  | 
 | ||
|  |     it('should not modify release train versions and cause invalid other actions', async () => { | ||
|  |       const {releaseConfig, gitClient} = getTestingMocksForReleaseAction(); | ||
|  |       const descriptions: string[] = []; | ||
|  | 
 | ||
|  |       for (const actionCtor of actions) { | ||
|  |         if (await actionCtor.isActive(testReleaseTrain)) { | ||
|  |           const action = new actionCtor(testReleaseTrain, gitClient, releaseConfig, testTmpDir); | ||
|  |           descriptions.push(await action.getDescription()); | ||
|  |         } | ||
|  |       } | ||
|  | 
 | ||
|  |       expect(descriptions).toEqual([ | ||
|  |         `Cut a first release-candidate for the feature-freeze branch (v10.1.0-rc.0).`, | ||
|  |         `Cut a new patch release for the "10.0.x" branch (v10.0.2).`, | ||
|  |         `Cut a new next pre-release for the "10.1.x" branch (v10.1.0-next.4).`, | ||
|  |         `Cut a new release for an active LTS branch (0 active).` | ||
|  |       ]); | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   describe('build and publishing', () => { | ||
|  |     it('should support a custom NPM registry', async () => { | ||
|  |       const {repo, instance, releaseConfig} = | ||
|  |           setupReleaseActionForTesting(TestAction, baseReleaseTrains); | ||
|  |       const {version, branchName} = baseReleaseTrains.next; | ||
|  |       const tagName = version.format(); | ||
|  |       const customRegistryUrl = 'https://custom-npm-registry.google.com'; | ||
|  | 
 | ||
|  |       repo.expectBranchRequest(branchName, 'STAGING_SHA') | ||
|  |           .expectCommitRequest('STAGING_SHA', `release: cut the v${version} release`) | ||
|  |           .expectTagToBeCreated(tagName, 'STAGING_SHA') | ||
|  |           .expectReleaseToBeCreated(`v${version}`, tagName); | ||
|  | 
 | ||
|  |       // Set up a custom NPM registry.
 | ||
|  |       releaseConfig.publishRegistry = customRegistryUrl; | ||
|  | 
 | ||
|  |       await instance.testBuildAndPublish(version, branchName, 'latest'); | ||
|  | 
 | ||
|  |       expect(npm.runNpmPublish).toHaveBeenCalledTimes(2); | ||
|  |       expect(npm.runNpmPublish) | ||
|  |           .toHaveBeenCalledWith(`${testTmpDir}/dist/pkg1`, 'latest', customRegistryUrl); | ||
|  |       expect(npm.runNpmPublish) | ||
|  |           .toHaveBeenCalledWith(`${testTmpDir}/dist/pkg2`, 'latest', customRegistryUrl); | ||
|  |     }); | ||
|  |   }); | ||
|  | 
 | ||
|  |   describe('changelog cherry-picking', () => { | ||
|  |     const {version, branchName} = baseReleaseTrains.latest; | ||
|  |     const fakeReleaseNotes = getChangelogForVersion(version.format()); | ||
|  |     const forkBranchName = `changelog-cherry-pick-${version}`; | ||
|  | 
 | ||
|  |     it('should prepend fetched changelog', async () => { | ||
|  |       const {repo, fork, instance, testTmpDir} = | ||
|  |           setupReleaseActionForTesting(TestAction, baseReleaseTrains); | ||
|  | 
 | ||
|  |       // Expect the changelog to be fetched and return a fake changelog to test that
 | ||
|  |       // it is properly appended. Also expect a pull request to be created in the fork.
 | ||
|  |       repo.expectChangelogFetch(branchName, fakeReleaseNotes) | ||
|  |           .expectFindForkRequest(fork) | ||
|  |           .expectPullRequestToBeCreated('master', fork, forkBranchName, 200); | ||
|  | 
 | ||
|  |       // Simulate that the fork branch name is available.
 | ||
|  |       fork.expectBranchRequest(forkBranchName, null); | ||
|  | 
 | ||
|  |       await instance.testCherryPickWithPullRequest(version, branchName); | ||
|  | 
 | ||
|  |       const changelogContent = readFileSync(join(testTmpDir, changelogPath), 'utf8'); | ||
|  |       expect(changelogContent).toEqual(`${fakeReleaseNotes}Existing changelog`); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('should respect a custom release note extraction pattern', async () => { | ||
|  |       const {repo, fork, instance, testTmpDir, releaseConfig} = | ||
|  |           setupReleaseActionForTesting(TestAction, baseReleaseTrains); | ||
|  | 
 | ||
|  |       // Custom pattern matching changelog output sections grouped through
 | ||
|  |       // basic level-1 markdown headers (compared to the default anchor pattern).
 | ||
|  |       releaseConfig.extractReleaseNotesPattern = version => | ||
|  |           new RegExp(`(# v${version} \\("[^"]+"\\).*?)(?:# v|$)`, 's'); | ||
|  | 
 | ||
|  |       const customReleaseNotes = `# v${version} ("newton-kepler")\n\nNew Content!`; | ||
|  | 
 | ||
|  |       // Expect the changelog to be fetched and return a fake changelog to test that
 | ||
|  |       // it is properly appended. Also expect a pull request to be created in the fork.
 | ||
|  |       repo.expectChangelogFetch(branchName, customReleaseNotes) | ||
|  |           .expectFindForkRequest(fork) | ||
|  |           .expectPullRequestToBeCreated('master', fork, forkBranchName, 200); | ||
|  | 
 | ||
|  |       // Simulate that the fork branch name is available.
 | ||
|  |       fork.expectBranchRequest(forkBranchName, null); | ||
|  | 
 | ||
|  |       await instance.testCherryPickWithPullRequest(version, branchName); | ||
|  | 
 | ||
|  |       const changelogContent = readFileSync(join(testTmpDir, changelogPath), 'utf8'); | ||
|  |       expect(changelogContent).toEqual(`${customReleaseNotes}\n\nExisting changelog`); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('should print an error if release notes cannot be extracted', async () => { | ||
|  |       const {repo, fork, instance, testTmpDir, releaseConfig} = | ||
|  |           setupReleaseActionForTesting(TestAction, baseReleaseTrains); | ||
|  | 
 | ||
|  |       // Expect the changelog to be fetched and return a fake changelog to test that
 | ||
|  |       // it is properly appended. Also expect a pull request to be created in the fork.
 | ||
|  |       repo.expectChangelogFetch(branchName, `non analyzable changelog`) | ||
|  |           .expectFindForkRequest(fork) | ||
|  |           .expectPullRequestToBeCreated('master', fork, forkBranchName, 200); | ||
|  | 
 | ||
|  |       // Simulate that the fork branch name is available.
 | ||
|  |       fork.expectBranchRequest(forkBranchName, null); | ||
|  | 
 | ||
|  |       spyOn(console, 'error'); | ||
|  | 
 | ||
|  |       await instance.testCherryPickWithPullRequest(version, branchName); | ||
|  | 
 | ||
|  |       expect(console.error) | ||
|  |           .toHaveBeenCalledWith( | ||
|  |               jasmine.stringMatching(`Could not cherry-pick release notes for v${version}`)); | ||
|  |       expect(console.error) | ||
|  |           .toHaveBeenCalledWith(jasmine.stringMatching( | ||
|  |               `Please copy the release notes manually into the "master" branch.`)); | ||
|  | 
 | ||
|  |       const changelogContent = readFileSync(join(testTmpDir, changelogPath), 'utf8'); | ||
|  |       expect(changelogContent).toEqual(`Existing changelog`); | ||
|  |     }); | ||
|  | 
 | ||
|  |     it('should push changes to a fork for creating a pull request', async () => { | ||
|  |       const {repo, fork, instance, gitClient} = | ||
|  |           setupReleaseActionForTesting(TestAction, baseReleaseTrains); | ||
|  | 
 | ||
|  |       // Expect the changelog to be fetched and return a fake changelog to test that
 | ||
|  |       // it is properly appended. Also expect a pull request to be created in the fork.
 | ||
|  |       repo.expectChangelogFetch(branchName, fakeReleaseNotes) | ||
|  |           .expectFindForkRequest(fork) | ||
|  |           .expectPullRequestToBeCreated('master', fork, forkBranchName, 200); | ||
|  | 
 | ||
|  |       // Simulate that the fork branch name is available.
 | ||
|  |       fork.expectBranchRequest(forkBranchName, null); | ||
|  | 
 | ||
|  |       await instance.testCherryPickWithPullRequest(version, branchName); | ||
|  | 
 | ||
|  |       expect(gitClient.pushed.length).toBe(1); | ||
|  |       expect(gitClient.pushed[0]).toEqual(getBranchPushMatcher({ | ||
|  |         targetBranch: forkBranchName, | ||
|  |         targetRepo: fork, | ||
|  |         baseBranch: 'master', | ||
|  |         baseRepo: repo, | ||
|  |         expectedCommits: [{ | ||
|  |           message: `docs: release notes for the v${version} release`, | ||
|  |           files: ['CHANGELOG.md'], | ||
|  |         }], | ||
|  |       })); | ||
|  |     }); | ||
|  |   }); | ||
|  | }); | ||
|  | 
 | ||
|  | /** | ||
|  |  * Test release action that exposes protected units of the base | ||
|  |  * release action class. This allows us to add unit tests. | ||
|  |  */ | ||
|  | class TestAction extends ReleaseAction { | ||
|  |   async getDescription() { | ||
|  |     return 'Test action'; | ||
|  |   } | ||
|  | 
 | ||
|  |   async perform() { | ||
|  |     throw Error('Not implemented.'); | ||
|  |   } | ||
|  | 
 | ||
|  |   async testBuildAndPublish(newVersion: semver.SemVer, publishBranch: string, distTag: string) { | ||
|  |     await this.buildAndPublish(newVersion, publishBranch, distTag); | ||
|  |   } | ||
|  | 
 | ||
|  |   async testCherryPickWithPullRequest(version: semver.SemVer, branch: string) { | ||
|  |     await this.cherryPickChangelogIntoNextBranch(version, branch); | ||
|  |   } | ||
|  | } |