Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion __fixtures__/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ module.exports = {
},
getReviews: async () => {
return { data: (options.reviews) ? options.reviews : [] }
}
},
get: jest.fn()
},
paginate: jest.fn(async (fn, cb) => {
return fn.then(cb)
Expand Down
12 changes: 8 additions & 4 deletions __tests__/actions/checks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ test('check that checks created when doPostAction is called with proper paramete
expect(context.github.checks.create.mock.calls.length).toBe(1)
})

test('check that checks created when doPostAction is called with proper parameter', async () => {
test('that afterValidate is called with properly and output is correct', async () => {
const checks = new Checks()
const context = createMockContext()
const result = {
Expand All @@ -40,14 +40,18 @@ test('check that checks created when doPostAction is called with proper paramete
}

await checks.afterValidate(context, settings, result)
let output = context.github.checks.update.mock.calls[0][0].output
expect(context.github.checks.update.mock.calls.length).toBe(1)
expect(context.github.checks.update.mock.calls[0][0].output.summary).toBe('This is the summary')
expect(context.github.checks.update.mock.calls[0][0].output.title).toBe('Your run has returned the following status: pass')
expect(output.summary).toBe('This is the summary')
expect(output.title).toBe('Your run has returned the following status: pass')
expect(
output.text.indexOf(`<!-- #mergeable-data {"id":"3","event":"pull_request","action":"actionName"} #mergeable-data -->`) !== -1
).toBe(true)
})

const createMockContext = () => {
let context = Helper.mockContext()

context.payload.action = 'actionName'
context.github.checks.create = jest.fn()
context.github.checks.update = jest.fn()
return context
Expand Down
49 changes: 49 additions & 0 deletions __tests__/interceptors/checkReRun.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const CheckReRun = require('../../lib/interceptors/checkReRun')
const Helper = require('../../__fixtures__/helper')
require('object-dot').extend()

test('#parseEventAction', () => {
let checkReRun = new CheckReRun()
let result = checkReRun.parseEventAction(mockOutput())

expect(result.id).toBe('123')
expect(result.event).toBe('pull_request')
expect(result.action).toBe('unlabeled')
})

test('#possibleInjection', () => {
let checkReRun = new CheckReRun()

expect(
checkReRun.possibleInjection(Helper.mockContext(), {id: 1}, {id: 1})
).toBe(false)
expect(
checkReRun.possibleInjection(Helper.mockContext(), {id: 1}, {id: 2})
).toBe(true)
})

test('#process', async () => {
let checkReRun = new CheckReRun()
let context = Helper.mockContext()

context.event = 'check_run'
context.payload.action = 'rerequested'
Object.set(context, 'payload.check_run.output.text', mockOutput())
context.payload.check_run.pull_requests = [{number: 1}]
context.payload.check_run.id = '123'
context.github.pullRequests.get.mockReturnValue({ data: { number: 456 } })
let newContext = await checkReRun.process(context)

expect(newContext.payload.pull_request.number).toBe(456)
expect(newContext.event).toBe('pull_request')
expect(newContext.payload.action).toBe('unlabeled')
})

const mockOutput = () => {
return `
#### :x: Validator: TITLE * :x:
***title must begins with "feat,test,chore"
*** Input : use-case: title Settings : \`\`\`{"begins_with":{"match":["feat","test","chore"]}}\`\`\`
<!-- #mergeable-data { "id": "123", "event": "pull_request", "action": "unlabeled" } #mergeable-data -->
`
}
8 changes: 7 additions & 1 deletion lib/actions/checks.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,13 @@ class Checks extends Action {
}

async afterValidate (context, settings, results) {
const payload = this.populatePayloadWithResult(settings.payload, results)
let payload = this.populatePayloadWithResult(settings.payload, results)
let json = JSON.stringify({
id: this.checkRunResult.data.id,
event: context.event,
action: context.payload.action
})
payload.text += `<!-- #mergeable-data ${json} #mergeable-data -->`

await updateChecks(
context,
Expand Down
5 changes: 5 additions & 0 deletions lib/flex.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const Configuration = require('./configuration/configuration')
const extractValidationStats = require('./stats/extractValidationStats')
const Checks = require('./actions/checks')
const interceptors = require('./interceptors')

// Main logic Processor of mergeable
const executeMergeable = async (context, registry) => {
Expand All @@ -11,6 +12,9 @@ const executeMergeable = async (context, registry) => {
// check for special Cases with anti-pattern
context = await checkAndProcessSpecialCases(context)

// interceptors
await interceptors(context)

// first fetch the configuration
let config = await Configuration.instanceWithContext(context)

Expand Down Expand Up @@ -132,6 +136,7 @@ const createPromises = (arrayToIterate, registryName, funcCall, context, registr
return promises
}

// TODO: Move this to the interceptor registry.
const checkAndProcessSpecialCases = async (context) => {
let processedContext = context
// check if issues.milestoned/demilestoned is actually from a pull_request
Expand Down
52 changes: 52 additions & 0 deletions lib/interceptors/checkReRun.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const Interceptor = require('./interceptor')

const DATA_START = '<!-- #mergeable-data'
const DATA_END = '#mergeable-data -->'

/**
* Checks the event for a re-requested check_run. This GH event is triggered when the user
* clicks on "Re-run" or "Re-run failed checks" in the UI and expects conditions to be re-validated. Fetch the PR and it's stored condition from
* the check run text.
*
* Set the context up with the appropriate PR payload, event and action for a validation and check run.
*
* NOTE: "Re-run all checks" generates a different event and is not taken care of in this interceptor.
*/
class CheckReRun extends Interceptor {
async process (context) {
if (context.event !== 'check_run' && context.payload.action !== 'rerequested') return context

let checkRun = context.payload.check_run
if (!checkRun) return context

let meta = this.parseEventAction(checkRun.output.text)
if (this.possibleInjection(context, checkRun, meta)) return context

let pr = await context.github.pullRequests.get(context.repo({number: checkRun.pull_requests[0].number}))
context.payload.action = meta.action
context.event = meta.event
context.payload.pull_request = pr.data
return context
}

possibleInjection (context, checkRun, meta) {
let isInjection = checkRun.id !== meta.id
if (isInjection) context.log.child('mergeable').warn('ids in payload do not match. Potential injection.')
return isInjection
}

/**
* Parses the chekrun output text for the JSON
* i.e. <!-- #mergeable-data { "id": "123", "event": "pull_request", "action": "unlabeled" } #mergeable-data -->
*
* @return JSON object of mergeable data.
*/
parseEventAction (text) {
let begin = text.indexOf(DATA_START) + DATA_START.length
let end = text.indexOf(DATA_END)
let jsonString = text.substring(begin, end)
return JSON.parse(jsonString.trim())
}
}

module.exports = CheckReRun
10 changes: 10 additions & 0 deletions lib/interceptors/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const REGISTRY = [
new (require('./checkReRun'))() // eslint-disable-line
]

/**
* Processes all the interceptors in the order of the registry array.
*/
module.exports = async (context) => {
await Promise.all(REGISTRY.map(interceptor => interceptor.process(context)))
}
21 changes: 21 additions & 0 deletions lib/interceptors/interceptor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* The Interceptor class defines the interface for all inheriting interceptors that
* mutates the probot context with additional meta data or changes existing property Values
* depending on certain criterias.
*
* This is used to filter by event and manipulate the context such that the flex workflow engine appropriately
* with the correct data depending on certain scenarios.
*
* Interceptors are cached instances and should be treated as singletons. Instance variables should be treated as constants.
*/
class Interceptor {
/**
* All Interceptors should overwrite this method and mutate the context as needed.
* By default returns the context unchanged.
*/
async process (context) {
return context
}
}

module.exports = Interceptor
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"codecov": "^3.1.0",
"jest": "^23.6.0",
"nodemon": "^1.18.3",
"object-dot": "^1.7.0",
"smee-client": "^1.0.1",
"standard": "^10.0.3"
},
Expand All @@ -39,6 +40,8 @@
},
"jest": {
"coverageDirectory": "./coverage/",
"collectCoverageFrom": ["lib/**/*.js"]
"collectCoverageFrom": [
"lib/**/*.js"
]
}
}