diff --git a/lib/plugins/branches.js b/lib/plugins/branches.js index 2c487523e..8e47f064d 100644 --- a/lib/plugins/branches.js +++ b/lib/plugins/branches.js @@ -10,24 +10,317 @@ module.exports = class Branches { this.branches = settings } + /** + * Get branch protection rule ID query + * @type {string} + */ + getBranchProtectionRuleIdQuery = ` + query($owner: String!, $repo: String!) { + repository(owner: $owner, name: $repo) { + branchProtectionRules(first: 100) { + nodes { + id + pattern + } + } + } + } + ` + + /** + * Get team ID query + * @type {string} + */ + getTeamIdQuery = ` + query($org: String!, $slug: String!) { + organization(login: $org) { + team(slug: $slug) { + id + } + } + } + ` + + /** + * Get user ID query + * @type {string} + */ + getUserIdQuery = ` + query($login: String!) { + user(login: $login) { + id + } + } + ` + + /** + * Update branch protection mutation + * @type {string} + */ + updateBranchProtectionMutation = ` + mutation($protectionSettings: UpdateBranchProtectionRuleInput!) { + updateBranchProtectionRule(input: $protectionSettings) { + id + pattern + } + } + ` + + /** + * Delete branch protection mutation + * @type {string} + */ + deleteBranchProtectionMutation = ` + mutation($input: DeleteBranchProtectionRuleInput!) { + deleteBranchProtectionRule(input: $input) { + clientMutationId + } + } + ` + + /** + * Sync branch protection settings + * @returns {Promise[]>} + */ sync () { return Promise.all( this.branches .filter(branch => branch.protection !== undefined) .map(branch => { const params = Object.assign(this.repo, { branch: branch.name }) - - if (this.isEmpty(branch.protection)) { - return this.github.repos.deleteBranchProtection(params) + if (branch?.name?.includes('*')) { + if (this.isEmpty(branch.protection)) { + return this.deleteBranchProtectionGraphQL(this.github, branch) + } else { + return this.updateBranchProtectionGraphQL(this.github, this.repo, branch) + } } else { - Object.assign(params, branch.protection, { headers: previewHeaders }) - return this.github.repos.updateBranchProtection(params) + if (this.isEmpty(branch.protection)) { + return this.github.repos.deleteBranchProtection(params) + } else { + Object.assign(params, branch.protection, { headers: previewHeaders }) + return this.github.repos.updateBranchProtection(params) + } } }) ) } + /** + * Update branch protection using GraphQL + * @param github + * @param repo + * @param branch + * @returns {Promise<*>} + */ + async updateBranchProtectionGraphQL (github, repo, branch) { + try { + const ruleId = await this.getBranchProtectionRuleId(github, repo.owner, repo.repo, branch.name) + + if (ruleId) { + const protection = branch?.protection + const updateVariables = { + protectionSettings: { + allowsDeletions: protection?.allow_deletions ?? false, + allowsForcePushes: protection?.allow_force_pushes ?? false, + branchProtectionRuleId: ruleId, + dismissesStaleReviews: protection?.required_pull_request_reviews?.dismiss_stale_reviews ?? false, + isAdminEnforced: protection?.enforce_admins ?? false, + pattern: branch.name, + requireLastPushApproval: protection?.restrictions?.required_pull_request_reviews ?? false, + requiredApprovingReviewCount: protection?.required_pull_request_reviews?.required_approving_review_count ?? 0, + requiredPullRequestReviewCount: protection?.required_pull_request_reviews?.required_approving_review_count ?? 0, + requiredStatusCheckContexts: protection?.required_status_checks?.contexts ?? [], + requiresApprovingReviews: protection?.required_pull_request_reviews?.required_approving_review_count > 0 ?? false, + requiresCodeOwnerReviews: protection?.required_pull_request_reviews?.require_code_owner_reviews ?? false, + requiresConversationResolution: protection?.required_conversation_resolution ?? false, + requiresLinearHistory: protection?.required_linear_history ?? false, + requiresStatusChecks: protection?.required_status_checks !== null && protection?.required_status_checks !== undefined && protection?.required_status_checks?.contexts.length > 0, + bypassPullRequestActorIds: [], // initialize empty arrays + bypassForcePushActorIds: [], // initialize empty arrays + reviewDismissalActorIds: [] // initialize empty arrays + } + } + + if (protection?.required_pull_request_reviews?.dismissal_restrictions?.users) { + for (const user of protection.required_pull_request_reviews.dismissal_restrictions.users) { + const userId = await this.getUserId(github, user) + updateVariables.protectionSettings.reviewDismissalActorIds.push(userId) + } + } + + if (protection?.required_pull_request_reviews?.dismissal_restrictions?.teams) { + for (const team of protection.required_pull_request_reviews.dismissal_restrictions.teams) { + const teamId = await this.getTeamId(github, repo.owner, team) + updateVariables.protectionSettings.reviewDismissalActorIds.push(teamId) + } + } + + if (protection?.restrictions?.users) { + for (const user of protection.restrictions.users) { + const userId = await this.getUserId(github, user) + updateVariables.protectionSettings.bypassForcePushActorIds.push(userId) + } + } + + if (protection?.restrictions?.teams) { + for (const team of protection.restrictions.teams) { + const teamId = await this.getTeamId(github, repo.owner, team) + updateVariables.protectionSettings.bypassForcePushActorIds.push(teamId) + } + } + + if (protection?.restrictions?.apps) { + for (const app of protection.restrictions.apps) { + const appId = await this.getAppId(github, app) + updateVariables.protectionSettings.bypassForcePushActorIds.push(appId) + } + } + + if (protection?.required_pull_request_reviews?.bypass_pull_request_allowances?.users) { + for (const user of protection.required_pull_request_reviews.bypass_pull_request_allowances.users) { + const userId = await this.getUserId(github, user) + updateVariables.protectionSettings.bypassPullRequestActorIds.push(userId) + } + } + + if (protection?.required_pull_request_reviews?.bypass_pull_request_allowances?.teams) { + for (const team of protection.required_pull_request_reviews.bypass_pull_request_allowances.teams) { + const teamId = await this.getTeamId(github, repo.owner, team) + updateVariables.protectionSettings.bypassPullRequestActorIds.push(teamId) + } + } + + if (protection?.required_pull_request_reviews?.bypass_pull_request_allowances?.apps) { + for (const app of protection.required_pull_request_reviews.bypass_pull_request_allowances.apps) { + const appId = await this.getAppId(github, app) + updateVariables.protectionSettings.bypassPullRequestActorIds.push(appId) + } + } + + return await github.graphql(this.updateBranchProtectionMutation, updateVariables) + } + } catch (error) { + console.error('Error updating branch protection:', error) + } + } + + /** + * Check if an object is empty + * @param maybeEmpty + * @returns {boolean} + */ isEmpty (maybeEmpty) { return maybeEmpty === null || Object.keys(maybeEmpty).length === 0 } + + /** + * Get the ID of a branch protection rule + * @param github + * @param owner + * @param repo + * @param pattern + * @returns {Promise<*|null>} + */ + async getBranchProtectionRuleId (github, owner, repo, pattern) { + try { + const response = await github.graphql(this.getBranchProtectionRuleIdQuery, { + owner, + repo, + pattern + }) + + const branchProtectionRule = response?.repository?.branchProtectionRules?.nodes?.find( + (rule) => rule.pattern === pattern + ) + + if (branchProtectionRule) { + return branchProtectionRule.id + } else { + console.error('Branch protection rule not found for branch:', pattern) + return null + } + } catch (error) { + console.error('Error getting branch protection rule ID:', error) + return null + } + } + + /** + * Delete branch protection using GraphQL + * @param github + * @param branch + * @returns {Promise<*>} + */ + async deleteBranchProtectionGraphQL (github, branch) { + try { + return this.getBranchProtectionRuleId( + this.github, + this.repo.owner, + this.repo.repo, + branch.name + ).then(ruleId => { + if (ruleId) { + const deleteVariables = { + input: { + branchProtectionRuleId: ruleId + } + } + return github.graphql(this.deleteBranchProtectionMutation, deleteVariables) + } + }) + } catch (error) { + console.error('Error deleting branch protection:', error) + } + } + + /** + * Get the ID of a user + * @param github + * @param login + * @returns {Promise} + */ + async getUserId (github, login) { + try { + const response = await github.graphql(this.getUserIdQuery, { login }) + return response?.data?.user?.id ?? null + } catch (error) { + console.error('Error getting user ID:', error) + return null + } + } + + /** + * Get the ID of a team + * @param github + * @param org + * @param slug + * @returns {Promise} + */ + async getTeamId (github, org, slug) { + try { + const response = await github.graphql(this.getTeamIdQuery, { org, slug }) + return response?.data?.organization?.team?.id ?? null + } catch (error) { + console.error('Error getting team ID:', error) + return null + } + } + + /** + * Get the ID of an app + * @param github + * @param appSlug + */ + async getAppId (github, appSlug) { + try { + const response = await github.apps.getBySlug({ + slug: appSlug + }) + return response?.id ?? null + } catch (error) { + console.error('Error getting app ID:', error) + return null + } + } } diff --git a/test/unit/lib/plugins/branches.test.js b/test/unit/lib/plugins/branches.test.js index fa3b2c0ea..59dc7b767 100644 --- a/test/unit/lib/plugins/branches.test.js +++ b/test/unit/lib/plugins/branches.test.js @@ -7,12 +7,63 @@ describe('Branches', () => { return new Branches(github, { owner: 'bkeepers', repo: 'test' }, config) } + function standardGraphQLFunction () { + return (mutation) => { + if (mutation.includes('updateBranchProtectionRule')) { + return Promise.resolve('updateBranchProtectionRule') + } + if (mutation.includes('deleteBranchProtectionRule')) { + return Promise.resolve('deleteBranchProtectionRule') + } + if (mutation.includes('branchProtectionRules')) { + return Promise.resolve({ + repository: { + branchProtectionRules: { + nodes: [ + { + id: 'BRANCHPROTECTIONID=', + pattern: 'release/*' + } + ] + } + } + }) + } + if (mutation.includes('query($login: String!)')) { + return Promise.resolve({ + data: { + user: { + id: 'ABC123==' + } + } + }) + } + if (mutation.includes('query($org: String!, $slug: String!)')) { + return Promise.resolve({ + data: { + organization: { + team: { + id: 'TEAMID==' + } + } + } + }) + } + + return Promise.resolve('unknown') + } + } + beforeEach(() => { github = { repos: { updateBranchProtection: jest.fn().mockImplementation(() => Promise.resolve('updateBranchProtection')), deleteBranchProtection: jest.fn().mockImplementation(() => Promise.resolve('deleteBranchProtection')) - } + }, + apps: { + getBySlug: jest.fn().mockImplementation(() => Promise.resolve('getBySlug')) + }, + graphql: jest.fn() } }) @@ -181,6 +232,388 @@ describe('Branches', () => { }) }) + describe('sync for wildcard', () => { + it('syncs branch protection settings with wildcard', () => { + github.graphql = jest.fn(standardGraphQLFunction()) + const plugin = configure([ + { + name: 'release/*', + protection: { + required_status_checks: { + strict: true, + contexts: ['travis-ci'] + }, + enforce_admins: true, + required_pull_request_reviews: { + require_code_owner_reviews: true + } + } + } + ]) + return plugin.sync().then(() => { + expect(github.graphql).toHaveBeenCalledWith(expect.any(String), expect.any(Object)) + const [branchProtectionRules] = github.graphql.mock.calls[0] + expect(branchProtectionRules).toContain('branchProtectionRules') + const [updateBranchProtectionRule, updateBranchProtectionRuleVariables] = github.graphql.mock.calls[1] + expect(updateBranchProtectionRule).toContain('updateBranchProtectionRule') + expect(updateBranchProtectionRuleVariables.protectionSettings.allowsDeletions).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.branchProtectionRuleId).toBe('BRANCHPROTECTIONID=') + expect(updateBranchProtectionRuleVariables.protectionSettings.dismissesStaleReviews).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.isAdminEnforced).toBe(true) + expect(updateBranchProtectionRuleVariables.protectionSettings.pattern).toBe('release/*') + expect(updateBranchProtectionRuleVariables.protectionSettings.requireLastPushApproval).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiredApprovingReviewCount).toBe(0) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiredPullRequestReviewCount).toBe(0) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiredStatusCheckContexts).toStrictEqual(['travis-ci']) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiresApprovingReviews).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiresCodeOwnerReviews).toBe(true) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiresConversationResolution).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiresLinearHistory).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiresStatusChecks).toBe(true) + expect(updateBranchProtectionRuleVariables.protectionSettings.bypassPullRequestActorIds).toStrictEqual([]) + expect(updateBranchProtectionRuleVariables.protectionSettings.bypassForcePushActorIds).toStrictEqual([]) + expect(updateBranchProtectionRuleVariables.protectionSettings.reviewDismissalActorIds).toStrictEqual([]) + }) + }) + + it('syncs branch protection settings with wildcard all args', () => { + github.graphql = jest.fn(standardGraphQLFunction()) + github.apps.getBySlug = jest.fn().mockImplementation(() => Promise.resolve({ + id: '1234' + })) + const plugin = configure([ + { + name: 'release/*', + protection: { + required_status_checks: { + strict: true, + contexts: ['travis-ci'] + }, + dismiss_stale_reviews: true, + require_code_owner_reviews: true, + required_approving_review_count: 1, + require_last_push_approval: true, + enforce_admins: true, + required_pull_request_reviews: { + require_code_owner_reviews: true, + dismissal_restrictions: { + users: ['user-0'], + teams: ['team-0'] + }, + bypass_pull_request_allowances: { + users: ['user-1'], + teams: ['team-1'], + apps: ['app-1'] + } + }, + restrictions: { + users: ['user-2'], + teams: ['team-2'], + apps: ['app-2'] + } + } + } + ]) + return plugin.sync().then(() => { + expect(github.graphql).toHaveBeenCalledWith(expect.any(String), expect.any(Object)) + + const [branchProtectionRules] = github.graphql.mock.calls[0] + expect(branchProtectionRules).toContain('query($owner: String!, $repo: String!)') + + const [getUserLogin] = github.graphql.mock.calls[1] + expect(getUserLogin).toContain('query($login: String!)') + + const [getOrg] = github.graphql.mock.calls[2] + expect(getOrg).toContain('query($org: String!, $slug: String!)') + + const [getUser2] = github.graphql.mock.calls[3] + expect(getUser2).toContain('query($login: String!)') + + const [getOrg2] = github.graphql.mock.calls[4] + expect(getOrg2).toContain('query($org: String!, $slug: String!)') + + const [getUser3] = github.graphql.mock.calls[5] + expect(getUser3).toContain('query($login: String!)') + + const [getOrg3] = github.graphql.mock.calls[6] + expect(getOrg3).toContain('query($org: String!, $slug: String!)') + + const [updateBranchProtectionRule, updateBranchProtectionRuleVariables] = github.graphql.mock.calls[7] + expect(updateBranchProtectionRule).toContain('updateBranchProtectionRule') + + expect(updateBranchProtectionRuleVariables.protectionSettings.allowsDeletions).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.branchProtectionRuleId).toBe('BRANCHPROTECTIONID=') + expect(updateBranchProtectionRuleVariables.protectionSettings.dismissesStaleReviews).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.isAdminEnforced).toBe(true) + expect(updateBranchProtectionRuleVariables.protectionSettings.pattern).toBe('release/*') + expect(updateBranchProtectionRuleVariables.protectionSettings.requireLastPushApproval).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiredApprovingReviewCount).toBe(0) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiredPullRequestReviewCount).toBe(0) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiredStatusCheckContexts).toStrictEqual(['travis-ci']) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiresApprovingReviews).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiresCodeOwnerReviews).toBe(true) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiresConversationResolution).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiresLinearHistory).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiresStatusChecks).toBe(true) + expect(updateBranchProtectionRuleVariables.protectionSettings.bypassPullRequestActorIds).toStrictEqual(['ABC123==', 'TEAMID==', '1234']) + expect(updateBranchProtectionRuleVariables.protectionSettings.bypassForcePushActorIds).toStrictEqual(['ABC123==', 'TEAMID==', '1234']) + expect(updateBranchProtectionRuleVariables.protectionSettings.reviewDismissalActorIds).toStrictEqual(['ABC123==', 'TEAMID==']) + }) + }) + + describe('when the "protection" config is empty object', () => { + it('removes branch protection', () => { + github.graphql = jest.fn((mutation) => { + if (mutation.includes('deleteBranchProtectionRule')) { + return Promise.resolve('deleteBranchProtectionRule') + } + if (mutation.includes('branchProtectionRules')) { + return Promise.resolve({ + repository: { + branchProtectionRules: { + nodes: [ + { + id: 'BRANCHPROTECTIONID=', + pattern: 'release/*' + } + ] + } + } + }) + } + }) + + const plugin = configure([ + { + name: 'release/*', + protection: {} + } + ]) + + return plugin.sync().then(() => { + const [branchProtectionRules, branchProtectionRulesVariables] = github.graphql.mock.calls[0] + expect(branchProtectionRules).toContain('branchProtectionRules') + expect(branchProtectionRulesVariables.owner).toBe('bkeepers') + expect(branchProtectionRulesVariables.repo).toBe('test') + expect(branchProtectionRulesVariables.pattern).toBe('release/*') + + const [updateBranchProtectionRule, updateBranchProtectionRuleVariables] = github.graphql.mock.calls[1] + expect(updateBranchProtectionRule).toContain('deleteBranchProtectionRule') + expect(updateBranchProtectionRuleVariables.input.branchProtectionRuleId).toBe('BRANCHPROTECTIONID=') + }) + }) + }) + + describe('when the "protection" config is set to `null`', () => { + it('removes branch protection', () => { + github.graphql = jest.fn((mutation) => { + if (mutation.includes('deleteBranchProtectionRule')) { + return Promise.resolve('deleteBranchProtectionRule') + } + if (mutation.includes('branchProtectionRules')) { + return Promise.resolve({ + repository: { + branchProtectionRules: { + nodes: [ + { + id: 'BRANCHPROTECTIONID=', + pattern: 'release/*' + } + ] + } + } + }) + } + }) + + const plugin = configure([ + { + name: 'release/*', + protection: null + } + ]) + + return plugin.sync().then(() => { + const [branchProtectionRules, branchProtectionRulesVariables] = github.graphql.mock.calls[0] + expect(branchProtectionRules).toContain('branchProtectionRules') + expect(branchProtectionRulesVariables.owner).toBe('bkeepers') + expect(branchProtectionRulesVariables.repo).toBe('test') + expect(branchProtectionRulesVariables.pattern).toBe('release/*') + + const [updateBranchProtectionRule, updateBranchProtectionRuleVariables] = github.graphql.mock.calls[1] + expect(updateBranchProtectionRule).toContain('deleteBranchProtectionRule') + expect(updateBranchProtectionRuleVariables.input.branchProtectionRuleId).toBe('BRANCHPROTECTIONID=') + }) + }) + }) + + describe('when the "protection" config is set to an empty array', () => { + it('removes branch protection', () => { + github.graphql = jest.fn((mutation) => { + if (mutation.includes('deleteBranchProtectionRule')) { + return Promise.resolve('deleteBranchProtectionRule') + } + if (mutation.includes('branchProtectionRules')) { + return Promise.resolve({ + repository: { + branchProtectionRules: { + nodes: [ + { + id: 'BRANCHPROTECTIONID=', + pattern: 'release/*' + } + ] + } + } + }) + } + }) + + const plugin = configure([ + { + name: 'release/*', + protection: [] + } + ]) + + return plugin.sync().then(() => { + const [branchProtectionRules, branchProtectionRulesVariables] = github.graphql.mock.calls[0] + expect(branchProtectionRules).toContain('branchProtectionRules') + expect(branchProtectionRulesVariables.owner).toBe('bkeepers') + expect(branchProtectionRulesVariables.repo).toBe('test') + expect(branchProtectionRulesVariables.pattern).toBe('release/*') + + const [updateBranchProtectionRule, updateBranchProtectionRuleVariables] = github.graphql.mock.calls[1] + expect(updateBranchProtectionRule).toContain('deleteBranchProtectionRule') + expect(updateBranchProtectionRuleVariables.input.branchProtectionRuleId).toBe('BRANCHPROTECTIONID=') + }) + }) + }) + + describe('when the "protection" config is set to `false`', () => { + it('removes branch protection', () => { + github.graphql = jest.fn((mutation) => { + if (mutation.includes('deleteBranchProtectionRule')) { + return Promise.resolve('deleteBranchProtectionRule') + } + if (mutation.includes('branchProtectionRules')) { + return Promise.resolve({ + repository: { + branchProtectionRules: { + nodes: [ + { + id: 'BRANCHPROTECTIONID=', + pattern: 'release/*' + } + ] + } + } + }) + } + }) + + const plugin = configure([ + { + name: 'release/*', + protection: false + } + ]) + + return plugin.sync().then(() => { + const [branchProtectionRules, branchProtectionRulesVariables] = github.graphql.mock.calls[0] + expect(branchProtectionRules).toContain('branchProtectionRules') + expect(branchProtectionRulesVariables.owner).toBe('bkeepers') + expect(branchProtectionRulesVariables.repo).toBe('test') + expect(branchProtectionRulesVariables.pattern).toBe('release/*') + + const [updateBranchProtectionRule, updateBranchProtectionRuleVariables] = github.graphql.mock.calls[1] + expect(updateBranchProtectionRule).toContain('deleteBranchProtectionRule') + expect(updateBranchProtectionRuleVariables.input.branchProtectionRuleId).toBe('BRANCHPROTECTIONID=') + }) + }) + }) + + describe('when the "protection" key is not present', () => { + it('makes no change to branch protection', () => { + github.graphql = jest.fn((mutation) => { + if (mutation.includes('deleteBranchProtectionRule')) { + return Promise.resolve('deleteBranchProtectionRule') + } + if (mutation.includes('branchProtectionRules')) { + return Promise.resolve({ + repository: { + branchProtectionRules: { + nodes: [ + { + id: 'BRANCHPROTECTIONID=', + pattern: 'release/*' + } + ] + } + } + }) + } + }) + const plugin = configure([ + { + name: 'release/*' + } + ]) + + return plugin.sync().then(() => { + expect(github.graphql).not.toHaveBeenCalled() + }) + }) + }) + + describe('when multiple branches are configured', () => { + it('updates them each appropriately', () => { + const plugin = configure([ + { + name: 'master', + protection: { enforce_admins: true } + }, + { + name: 'release/*', + protection: { enforce_admins: false } + } + ]) + + github.graphql = jest.fn(standardGraphQLFunction()) + + return plugin.sync().then(() => { + expect(github.repos.updateBranchProtection).toHaveBeenCalledTimes(1) + + expect(github.graphql).toHaveBeenCalledWith(expect.any(String), expect.any(Object)) + + const [branchProtectionRules] = github.graphql.mock.calls[0] + expect(branchProtectionRules).toContain('branchProtectionRules') + + const [updateBranchProtectionRule, updateBranchProtectionRuleVariables] = github.graphql.mock.calls[1] + expect(updateBranchProtectionRule).toContain('updateBranchProtectionRule') + + expect(updateBranchProtectionRuleVariables.protectionSettings.allowsDeletions).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.branchProtectionRuleId).toBe('BRANCHPROTECTIONID=') + expect(updateBranchProtectionRuleVariables.protectionSettings.dismissesStaleReviews).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.isAdminEnforced).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.pattern).toBe('release/*') + expect(updateBranchProtectionRuleVariables.protectionSettings.requireLastPushApproval).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiredApprovingReviewCount).toBe(0) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiredPullRequestReviewCount).toBe(0) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiredStatusCheckContexts).toStrictEqual([]) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiresApprovingReviews).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiresCodeOwnerReviews).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiresConversationResolution).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiresLinearHistory).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.requiresStatusChecks).toBe(false) + expect(updateBranchProtectionRuleVariables.protectionSettings.bypassPullRequestActorIds).toStrictEqual([]) + expect(updateBranchProtectionRuleVariables.protectionSettings.bypassForcePushActorIds).toStrictEqual([]) + expect(updateBranchProtectionRuleVariables.protectionSettings.reviewDismissalActorIds).toStrictEqual([]) + }) + }) + }) + }) + describe('return values', () => { it('returns updateBranchProtection Promise', () => { const plugin = configure([ @@ -209,4 +642,50 @@ describe('Branches', () => { }) }) }) + + describe('return values wildcards', () => { + it('returns updateBranchProtection Promise', () => { + github.graphql = jest.fn(standardGraphQLFunction()) + + const plugin = configure([ + { + name: 'release/*', + protection: { enforce_admins: true } + } + ]) + + return plugin.sync().then(result => { + expect(result.length).toBe(1) + expect(result[0]).toBe('updateBranchProtectionRule') + }) + }) + it('returns deleteBranchProtection Promise', () => { + github.graphql = jest.fn(standardGraphQLFunction()) + + const plugin = configure([ + { + name: 'release/*', + protection: null + } + ]) + + return plugin.sync().then(result => { + expect(result.length).toBe(1) + expect(result[0]).toBe('deleteBranchProtectionRule') + }) + }) + it('returns no Promise', () => { + github.graphql = jest.fn(standardGraphQLFunction()) + + const plugin = configure([ + { + name: 'release/*' + } + ]) + + return plugin.sync().then(result => { + expect(result.length).toBe(0) + }) + }) + }) })