Skip to content

Commit

Permalink
Add author validator (#710)
Browse files Browse the repository at this point in the history
  • Loading branch information
robinmayerhofer committed Apr 25, 2023
1 parent 071e6fb commit 64d4afd
Show file tree
Hide file tree
Showing 7 changed files with 232 additions and 1 deletion.
123 changes: 123 additions & 0 deletions __tests__/unit/validators/author.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
const Author = require('../../../lib/validators/author')
const Helper = require('../../../__fixtures__/unit/helper')
const Teams = require('../../../lib/validators/options_processor/teams')

const authorName = 'mergeabletestauthorname'
const otherAuthorName = 'someone-else'

test('should fail with unexpected author', async () => {
const author = new Author()
const settings = {
do: 'author',
must_include: {
regex: otherAuthorName
}
}
const validation = await author.processValidate(createMockContext(authorName), settings)
expect(validation.status).toBe('fail')
})

test('should pass with expected author', async () => {
const author = new Author()
const settings = {
do: 'author',
must_include: {
regex: authorName
}
}
const validation = await author.processValidate(createMockContext(authorName), settings)
expect(validation.status).toBe('pass')
})

test('should fail with excluded author', async () => {
const author = new Author()
const settings = {
do: 'author',
must_exclude: {
regex: authorName
}
}
const validation = await author.processValidate(createMockContext(authorName), settings)
expect(validation.status).toBe('fail')
})

test('should pass with excluded author', async () => {
const author = new Author()
const settings = {
do: 'author',
must_exclude: {
regex: otherAuthorName
}
}
const validation = await author.processValidate(createMockContext(authorName), settings)
expect(validation.status).toBe('pass')
})

test('should pass with expected author from correct team', async () => {
const author = new Author()
const settings = {
do: 'author',
must_include: {
regex: authorName
},
team: 'org/team-slug'
}
Teams.extractTeamMemberships = jest.fn().mockReturnValue([authorName])
const validation = await author.processValidate(createMockContext(authorName), settings)
expect(validation.status).toBe('pass')
})

test('should fail with expected author from incorrect team', async () => {
const author = new Author()
const settings = {
do: 'author',
must_include: {
regex: authorName
},
team: 'org/team-slug'
}
Teams.extractTeamMemberships = jest.fn().mockReturnValue([])
const validation = await author.processValidate(createMockContext(authorName), settings)
expect(validation.status).toBe('fail')
})

test('should fail with unexpected author from correct team', async () => {
const author = new Author()
const settings = {
do: 'author',
must_include: {
regex: otherAuthorName
},
team: 'org/team-slug'
}
Teams.extractTeamMemberships = jest.fn().mockReturnValue([authorName])
const validation = await author.processValidate(createMockContext(authorName), settings)
expect(validation.status).toBe('fail')
})

test('should pass when the author is a member of the team', async () => {
const author = new Author()
const settings = {
do: 'author',
team: 'org/team-slug'
}
Teams.extractTeamMemberships = jest.fn().mockReturnValue([authorName])
const validation = await author.processValidate(createMockContext(authorName), settings)
expect(validation.status).toBe('pass')
})

test('should fail when the author is not a member of the team', async () => {
const author = new Author()
const authorName = 'mergeable'
const settings = {
do: 'author',
team: 'org/team-slug'
}
Teams.extractTeamMemberships = jest.fn().mockReturnValue([otherAuthorName])
const validation = await author.processValidate(createMockContext(authorName), settings)
expect(validation.status).toBe('fail')
})

const createMockContext = (author) => {
return Helper.mockContext({ author })
}
1 change: 1 addition & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
CHANGELOG
=====================================
| April 25, 2023: feat: Add author validator `#710 <https://github.com/mergeability/mergeable/pull/710>`_
| March 13, 2023: fix: Replace delete with remove in changeset validator `#705 <https://github.com/mergeability/mergeable/pull/705>`_
| March 7, 2023: fix: Extend checks supported events `#700 <https://github.com/mergeability/mergeable/pull/700>`_
| March 1, 2023: feat:Added user information for commit validator `#682 <https://github.com/mergeability/mergeable/pull/682>`_
Expand Down
1 change: 1 addition & 0 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ Validator List
validators/age.rst
validators/approval.rst
validators/assignee.rst
validators/author.rst
validators/baseRef.rst
validators/changeset.rst
validators/commit.rst
Expand Down
53 changes: 53 additions & 0 deletions docs/validators/author.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
Author
^^^^^^^^^^^^^^

::

- do: author
must_include:
regex: 'user-1'
message: 'Custom include message...'
must_exclude:
regex: 'user-2'
message: 'Custom exclude message...'
team: 'org/team-slug' # verify that the author is in the team
# all of the message sub-option is optional

you can use ``and`` and ``or`` options to create more complex filters

::

- do: author
and:
- must_exclude:
regex: 'bot-user-1'
message: 'Custom message...'
or:
- must_include:
regex: 'user-1'
message: 'Custom message...'
- must_include:
regex: 'user-2'
message: 'Custom message...'

you can also nest ``and`` and ``or`` options

::

- do: author
and:
- or:
- must_include:
regex: 'user-1'
message: 'Custom message...'
- must_include:
regex: 'user-2'
message: 'Custom message...'
- must_exclude:
regex: 'bot-user-1'
message: 'Custom message...'

Supported Events:
::

'pull_request.*', 'pull_request_review.*'
31 changes: 31 additions & 0 deletions lib/validators/author.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const { Validator } = require('./validator')

class Author extends Validator {
constructor () {
super('author')
this.supportedEvents = [
'pull_request.*',
'pull_request_review.*'
]
this.supportedSettings = {
must_include: {
regex: 'string',
regex_flag: 'string',
message: 'string'
},
must_exclude: {
regex: 'string',
regex_flag: 'string',
message: 'string'
},
team: 'string'
}
}

async validate (context, settings) {
const payload = this.getPayload(context)
return this.processOptions(settings, payload.user.login)
}
}

module.exports = Author
21 changes: 21 additions & 0 deletions lib/validators/options_processor/options/team.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const Teams = require('../teams')

class TeamProcessor {
static async process (context, input, rule) {
const userName = input
const teamName = rule.team

const SUCCESS_MESSAGE = `'${userName}' is part of the '${teamName}' team'`
const FAILURE_MESSAGE = `'${userName}' is not part of the '${teamName}' team'`

const teamMemberships = await Teams.extractTeamMemberships(context, [teamName], [userName])
const isMember = teamMemberships.includes(userName)

return {
status: isMember ? 'pass' : 'fail',
description: isMember ? SUCCESS_MESSAGE : FAILURE_MESSAGE
}
}
}

module.exports = TeamProcessor
3 changes: 2 additions & 1 deletion lib/validators/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ const DEFAULT_SUPPORTED_OPTIONS = [
'must_include',
'no_empty',
'required',
'jira'
'jira',
'team'
]

class Validator extends EventAware {
Expand Down

0 comments on commit 64d4afd

Please sign in to comment.