-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: merge in accordance with branch protection rules
- Loading branch information
Showing
17 changed files
with
1,002 additions
and
76 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
src/common/__snapshots__/getPullRequestInformation.spec.ts.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`getPullRequestInformation returns pull request information with mergeStateStatus field 1`] = ` | ||
Object { | ||
"authorLogin": "test-author", | ||
"commitMessage": "test message", | ||
"commitMessageHeadline": "test message headline", | ||
"mergeStateStatus": "CLEAN", | ||
"mergeableState": "MERGEABLE", | ||
"merged": false, | ||
"pullRequestId": "123", | ||
"pullRequestNumber": 1, | ||
"pullRequestState": "OPEN", | ||
"pullRequestTitle": "test", | ||
"repositoryName": "test-repository", | ||
"repositoryOwner": "test-owner", | ||
"reviewEdges": Array [], | ||
} | ||
`; | ||
|
||
exports[`getPullRequestInformation returns pull request information without mergeStateStatus field 1`] = ` | ||
Object { | ||
"authorLogin": "test-author", | ||
"commitMessage": "test message", | ||
"commitMessageHeadline": "test message headline", | ||
"mergeableState": "MERGEABLE", | ||
"merged": false, | ||
"pullRequestId": "123", | ||
"pullRequestNumber": 1, | ||
"pullRequestState": "OPEN", | ||
"pullRequestTitle": "test", | ||
"repositoryName": "test-repository", | ||
"repositoryOwner": "test-owner", | ||
"reviewEdges": Array [], | ||
} | ||
`; |
93 changes: 93 additions & 0 deletions
93
src/common/computeRequiresStrictStatusChecksForRefs.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import { computeRequiresStrictStatusChecksForRefs as computeRequiresStrictStatusChecksForReferences } from './computeRequiresStrictStatusChecksForRefs'; | ||
|
||
/** | ||
* Tests | ||
*/ | ||
describe('computeRequiresStrictStatusChecksForRefs', (): void => { | ||
it('returns false for all refs when no branch protection rules exist for the repository', (): void => { | ||
expect.assertions(1); | ||
|
||
const result = computeRequiresStrictStatusChecksForReferences( | ||
[], | ||
['master', 'dev'], | ||
); | ||
|
||
expect(result).toStrictEqual([false, false]); | ||
}); | ||
|
||
it('returns false for all refs when none of the branch protection rule patterns match provided refs', (): void => { | ||
expect.assertions(1); | ||
|
||
const result = computeRequiresStrictStatusChecksForReferences( | ||
[ | ||
{ | ||
pattern: 'test1', | ||
requiresStrictStatusChecks: true, | ||
}, | ||
{ | ||
pattern: 'test2', | ||
requiresStrictStatusChecks: true, | ||
}, | ||
], | ||
['master', 'dev'], | ||
); | ||
|
||
expect(result).toStrictEqual([false, false]); | ||
}); | ||
|
||
it('returns false for all refs when all matching branch protection rule patterns do not require strict status checks', (): void => { | ||
expect.assertions(1); | ||
|
||
const result = computeRequiresStrictStatusChecksForReferences( | ||
[ | ||
{ | ||
pattern: 'dev', | ||
requiresStrictStatusChecks: false, | ||
}, | ||
{ | ||
pattern: 'master', | ||
requiresStrictStatusChecks: false, | ||
}, | ||
], | ||
['master', 'dev'], | ||
); | ||
|
||
expect(result).toStrictEqual([false, false]); | ||
}); | ||
|
||
it('returns true for all refs when branch protection rule patterns match provided refs with wildcard', (): void => { | ||
expect.assertions(1); | ||
|
||
const result = computeRequiresStrictStatusChecksForReferences( | ||
[ | ||
{ | ||
pattern: 'test*', | ||
requiresStrictStatusChecks: true, | ||
}, | ||
], | ||
['test', 'testing', 'master'], | ||
); | ||
|
||
expect(result).toStrictEqual([true, true, false]); | ||
}); | ||
|
||
it('returns true for refs when matching branch protection rules require strict status checks', (): void => { | ||
expect.assertions(1); | ||
|
||
const result = computeRequiresStrictStatusChecksForReferences( | ||
[ | ||
{ | ||
pattern: 'dev', | ||
requiresStrictStatusChecks: true, | ||
}, | ||
{ | ||
pattern: 'master', | ||
requiresStrictStatusChecks: false, | ||
}, | ||
], | ||
['master', 'dev'], | ||
); | ||
|
||
expect(result).toStrictEqual([false, true]); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { isMatch } from 'micromatch'; | ||
|
||
import { BranchProtectionRule } from './listBranchProtectionRules'; | ||
|
||
/** | ||
* Returns an array of booleans indicating whether the provided pull requests | ||
* require their branches to be up to date before merging. | ||
*/ | ||
export const computeRequiresStrictStatusChecksForRefs = ( | ||
branchProtectionRules: BranchProtectionRule[], | ||
refs: string[], | ||
): boolean[] => | ||
refs.map((reference: string): boolean => | ||
branchProtectionRules.some( | ||
({ | ||
pattern, | ||
requiresStrictStatusChecks, | ||
}: BranchProtectionRule): boolean => | ||
isMatch(reference, pattern) && requiresStrictStatusChecks === true, | ||
), | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
/* cspell:ignore reqheaders */ | ||
|
||
import { getOctokit } from '@actions/github'; | ||
import { StatusCodes } from 'http-status-codes'; | ||
import * as nock from 'nock'; | ||
|
||
import { | ||
MergeableState, | ||
MergeStateStatus, | ||
PullRequestState, | ||
ReviewEdges, | ||
} from '../types'; | ||
import { getMergeablePullRequestInformationByPullRequestNumber } from './getPullRequestInformation'; | ||
|
||
/** | ||
* Test utilities | ||
*/ | ||
const octokit = getOctokit('SECRET_GITHUB_TOKEN'); | ||
const repositoryName = 'test-repository'; | ||
const repositoryOwner = 'test-owner'; | ||
const pullRequestNumber = 1; | ||
|
||
const pullRequestFields = (githubPreviewApiEnabled: boolean): string => { | ||
const fields = [ | ||
`author { | ||
login | ||
}`, | ||
`commits(last: 1) { | ||
edges { | ||
node { | ||
commit { | ||
author { | ||
name | ||
} | ||
messageHeadline | ||
message | ||
} | ||
} | ||
} | ||
}`, | ||
'id', | ||
'mergeable', | ||
'merged', | ||
...(githubPreviewApiEnabled ? ['mergeStateStatus'] : []), | ||
'number', | ||
`reviews(last: 1, states: APPROVED) { | ||
edges { | ||
node { | ||
state | ||
} | ||
} | ||
}`, | ||
'state', | ||
'title', | ||
]; | ||
|
||
return `{ | ||
${fields.join('\n')} | ||
}`; | ||
}; | ||
|
||
const findPullRequestInfoByNumberQuery = ( | ||
githubPreviewApiEnabled: boolean, | ||
): string => ` | ||
query FindPullRequestInfoByNumber( | ||
$repositoryOwner: String!, | ||
$repositoryName: String!, | ||
$pullRequestNumber: Int! | ||
) { | ||
repository(owner: $repositoryOwner, name: $repositoryName) { | ||
pullRequest(number: $pullRequestNumber) ${pullRequestFields( | ||
githubPreviewApiEnabled, | ||
)} | ||
} | ||
} | ||
`; | ||
|
||
interface GraphQLResponse { | ||
repository: { | ||
pullRequest: { | ||
author: { | ||
login: string; | ||
}; | ||
commits: { | ||
edges: Array<{ | ||
node: { | ||
commit: { | ||
author: { | ||
name: string; | ||
}; | ||
message: string; | ||
messageHeadline: string; | ||
}; | ||
}; | ||
}>; | ||
}; | ||
id: string; | ||
mergeStateStatus?: MergeStateStatus; | ||
mergeable: MergeableState; | ||
merged: boolean; | ||
number: number; | ||
reviews: { | ||
edges: ReviewEdges[]; | ||
}; | ||
state: PullRequestState; | ||
title: string; | ||
}; | ||
}; | ||
} | ||
|
||
const makeGraphQLResponse = ( | ||
includeMergeStateStatus: boolean, | ||
): GraphQLResponse => ({ | ||
repository: { | ||
pullRequest: { | ||
author: { | ||
login: 'test-author', | ||
}, | ||
commits: { | ||
edges: [ | ||
{ | ||
node: { | ||
commit: { | ||
author: { | ||
name: 'Test Author', | ||
}, | ||
message: 'test message', | ||
messageHeadline: 'test message headline', | ||
}, | ||
}, | ||
}, | ||
], | ||
}, | ||
id: '123', | ||
...(includeMergeStateStatus ? { mergeStateStatus: 'CLEAN' } : {}), | ||
mergeable: 'MERGEABLE', | ||
merged: false, | ||
number: pullRequestNumber, | ||
reviews: { | ||
edges: [], | ||
}, | ||
state: 'OPEN', | ||
title: 'test', | ||
}, | ||
}, | ||
}); | ||
|
||
/** | ||
* Tests | ||
*/ | ||
describe('getPullRequestInformation', (): void => { | ||
it.each<[string, boolean]>([ | ||
['without mergeStateStatus field', false], | ||
['with mergeStateStatus field', true], | ||
])( | ||
'returns pull request information %s', | ||
async (_: string, githubPreviewApiEnabled: boolean): Promise<void> => { | ||
expect.assertions(1); | ||
|
||
nock('https://api.github.com', { | ||
reqheaders: { | ||
accept: githubPreviewApiEnabled | ||
? 'application/vnd.github.merge-info-preview+json' | ||
: 'application/vnd.github.v3+json', | ||
}, | ||
}) | ||
.post('/graphql', { | ||
query: findPullRequestInfoByNumberQuery(githubPreviewApiEnabled), | ||
variables: { | ||
pullRequestNumber, | ||
repositoryName, | ||
repositoryOwner, | ||
}, | ||
}) | ||
.reply(StatusCodes.OK, { | ||
data: makeGraphQLResponse(githubPreviewApiEnabled), | ||
}); | ||
|
||
const result = await getMergeablePullRequestInformationByPullRequestNumber( | ||
octokit, | ||
{ | ||
pullRequestNumber, | ||
repositoryName, | ||
repositoryOwner, | ||
}, | ||
{ githubPreviewApiEnabled }, | ||
); | ||
|
||
expect(result).toMatchSnapshot(); | ||
}, | ||
); | ||
}); |
Oops, something went wrong.