Skip to content

Commit

Permalink
feat: Add more supported events to baseRef validator (#598)
Browse files Browse the repository at this point in the history
- The auto-merge workflow (using the `merge` action) listens to
  `check_suite.*` events in addition to the more standard
  `pull-request.*` and `pull_request_review.*`.
  This workflow relies on branch protections to stop the merge while
  the check_suite is running.
  Since branch protections are usually setup **only** only on the
  default branch it means PRs that are targetting non-default
  branches are auto-merged by the workflow sometime before the check
  suite has even completed (if `check_suite.*` is used).
- Add `check_suite.*` event support to the baseRef validator so that
  auto-merge can be limited to the default branch.
- If more than one pull request is associated with a check_suite the
  validator will validate ALL of them and only pass if ALL of them
  pass
- If NO pull requests are associated with a check_suite the validator
  considers this to be a failure since there is no baseRef to
  validate
- Add unit tests for check_suite events.
  • Loading branch information
abid-mujtaba authored Dec 2, 2021
1 parent 577cc12 commit a430400
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 6 deletions.
98 changes: 98 additions & 0 deletions __tests__/unit/validators/baseRef.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,34 @@ test('checks that it fail when exclude regex is in baseRef', async () => {
expect(baseRefValidation.status).toBe('fail')
})

test('checks that it passes when exclude regex is not in baseRef', async () => {
const baseRef = new BaseRef()

const settings = {
do: 'baseRef',
must_exclude: {
regex: 'wip'
}
}

const baseRefValidation = await baseRef.processValidate(mockContext('foo'), settings)
expect(baseRefValidation.status).toBe('pass')
})

test('checks that it passes when include regex is in baseRef', async () => {
const baseRef = new BaseRef()

const settings = {
do: 'baseRef',
must_include: {
regex: '^\\(feat\\)|^\\(doc\\)|^\\(fix\\)'
}
}

const baseRefValidation = await baseRef.processValidate(mockContext('(feat) foo'), settings)
expect(baseRefValidation.status).toBe('pass')
})

test('checks that advance setting of must_include works', async () => {
const baseRef = new BaseRef()

Expand All @@ -61,7 +89,77 @@ test('checks that advance setting of must_include works', async () => {
expect(baseRefValidation.status).toBe('fail')
})

test('fail when exclude regex is in baseRef of single check_suite pull request', async () => {
const baseRef = new BaseRef()

const settings = {
do: 'baseRef',
must_exclude: {
regex: 'wip'
}
}

const context = mockCheckSuiteContext(['WIP foo'])

const baseRefValidation = await baseRef.processValidate(context, settings)
expect(baseRefValidation.status).toBe('fail')
})

test('fail when exclude regex is in one baseRef of multiple check_suite pull requests', async () => {
const baseRef = new BaseRef()

const settings = {
do: 'baseRef',
must_exclude: {
regex: 'wip'
}
}

const context = mockCheckSuiteContext(['foo', 'WIP bar', 'baz'])

const baseRefValidation = await baseRef.processValidate(context, settings)
expect(baseRefValidation.status).toBe('fail')
})

test('pass when exclude regex is not in any baseRef of multiple check_suite pull requests', async () => {
const baseRef = new BaseRef()

const settings = {
do: 'baseRef',
must_exclude: {
regex: 'wip'
}
}

const context = mockCheckSuiteContext(['foo', 'bar', 'baz'])

const baseRefValidation = await baseRef.processValidate(context, settings)
expect(baseRefValidation.status).toBe('pass')
})

test('fail when include regex exists and there are no pull requests in check_suite', async () => {
const baseRef = new BaseRef()

const settings = {
do: 'baseRef',
must_include: {
regex: 'foo'
}
}

const context = mockCheckSuiteContext([])

const baseRefValidation = await baseRef.processValidate(context, settings)
expect(baseRefValidation.status).toBe('fail')
})

const mockContext = baseRef => {
const context = Helper.mockContext({ baseRef: baseRef })
return context
}

const mockCheckSuiteContext = baseRefs => {
const context = Helper.mockContext({ eventName: 'check_suite' })
context.payload.check_suite.pull_requests = baseRefs.map(baseRef => ({ base: { ref: baseRef } }))
return context
}
1 change: 1 addition & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
CHANGELOG
=====================================
| November 25, 2021: feat: Add more supported events to baseRef validator `#395 <https://github.com/mergeability/mergeable/issues/395#issuecomment-975763927>` _
| November 12, 2021 : feat: Add baseRef filter `#596 <https://github.com/mergeability/mergeable/pull/596>`_
| October 19, 2021 : feat: Add validator approval option to exclude users `#594 <https://github.com/mergeability/mergeable/pull/594>`_
| October 12, 2021 : feat: Add boolean option for payload filter `#583 <https://github.com/mergeability/mergeable/pull/583>`_
Expand Down
2 changes: 1 addition & 1 deletion docs/validators/baseRef.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ Simple example:
Supported Events:
::

'pull_request.*', 'pull_request_review.*'
'pull_request.*', 'pull_request_review.*', 'check_suite.*'
35 changes: 30 additions & 5 deletions lib/validators/baseRef.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
const { Validator } = require('./validator')
const consolidateResults = require('./options_processor/options/lib/consolidateResults')
const constructOutput = require('./options_processor/options/lib/constructOutput')

class BaseRef extends Validator {
constructor () {
super('baseRef')
this.supportedEvents = [
'pull_request.*',
'pull_request_review.*'
'pull_request_review.*',
'check_suite.*'
]
this.supportedSettings = {
must_include: {
Expand All @@ -22,10 +25,32 @@ class BaseRef extends Validator {
}

async validate (context, validationSettings) {
return this.processOptions(
validationSettings,
this.getPayload(context).base.ref
)
const payload = this.getPayload(context)

if (context.eventName === 'check_suite') {
return this.validateCheckSuite(payload, validationSettings)
}

return this.processOptions(validationSettings, payload.base.ref)
}

async validateCheckSuite (payload, validationSettings) {
// A check_suite's payload contains multiple pull_requests
// Need to make sure that each pull_request's base ref is valid
const validatorContext = { name: 'baseRef' }
const baseRefs = payload.pull_requests.map(pullRequest => pullRequest.base.ref)

// If a check_suite has NO associated pull requests it is considered
// a failed validation since there is no baseRef to validate
if (baseRefs.length === 0) {
return constructOutput({ name: 'baseRef' }, undefined, validationSettings, { status: 'fail', description: 'No pull requests associated with check_suite' })
}

const results = await Promise.all(baseRefs.map(
baseRef => this.processOptions(validationSettings, baseRef)
))

return consolidateResults(results, validatorContext)
}
}

Expand Down

0 comments on commit a430400

Please # to comment.