Skip to content

Commit 64d4afd

Browse files
Add author validator (#710)
1 parent 071e6fb commit 64d4afd

File tree

7 files changed

+232
-1
lines changed

7 files changed

+232
-1
lines changed
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
const Author = require('../../../lib/validators/author')
2+
const Helper = require('../../../__fixtures__/unit/helper')
3+
const Teams = require('../../../lib/validators/options_processor/teams')
4+
5+
const authorName = 'mergeabletestauthorname'
6+
const otherAuthorName = 'someone-else'
7+
8+
test('should fail with unexpected author', async () => {
9+
const author = new Author()
10+
const settings = {
11+
do: 'author',
12+
must_include: {
13+
regex: otherAuthorName
14+
}
15+
}
16+
const validation = await author.processValidate(createMockContext(authorName), settings)
17+
expect(validation.status).toBe('fail')
18+
})
19+
20+
test('should pass with expected author', async () => {
21+
const author = new Author()
22+
const settings = {
23+
do: 'author',
24+
must_include: {
25+
regex: authorName
26+
}
27+
}
28+
const validation = await author.processValidate(createMockContext(authorName), settings)
29+
expect(validation.status).toBe('pass')
30+
})
31+
32+
test('should fail with excluded author', async () => {
33+
const author = new Author()
34+
const settings = {
35+
do: 'author',
36+
must_exclude: {
37+
regex: authorName
38+
}
39+
}
40+
const validation = await author.processValidate(createMockContext(authorName), settings)
41+
expect(validation.status).toBe('fail')
42+
})
43+
44+
test('should pass with excluded author', async () => {
45+
const author = new Author()
46+
const settings = {
47+
do: 'author',
48+
must_exclude: {
49+
regex: otherAuthorName
50+
}
51+
}
52+
const validation = await author.processValidate(createMockContext(authorName), settings)
53+
expect(validation.status).toBe('pass')
54+
})
55+
56+
test('should pass with expected author from correct team', async () => {
57+
const author = new Author()
58+
const settings = {
59+
do: 'author',
60+
must_include: {
61+
regex: authorName
62+
},
63+
team: 'org/team-slug'
64+
}
65+
Teams.extractTeamMemberships = jest.fn().mockReturnValue([authorName])
66+
const validation = await author.processValidate(createMockContext(authorName), settings)
67+
expect(validation.status).toBe('pass')
68+
})
69+
70+
test('should fail with expected author from incorrect team', async () => {
71+
const author = new Author()
72+
const settings = {
73+
do: 'author',
74+
must_include: {
75+
regex: authorName
76+
},
77+
team: 'org/team-slug'
78+
}
79+
Teams.extractTeamMemberships = jest.fn().mockReturnValue([])
80+
const validation = await author.processValidate(createMockContext(authorName), settings)
81+
expect(validation.status).toBe('fail')
82+
})
83+
84+
test('should fail with unexpected author from correct team', async () => {
85+
const author = new Author()
86+
const settings = {
87+
do: 'author',
88+
must_include: {
89+
regex: otherAuthorName
90+
},
91+
team: 'org/team-slug'
92+
}
93+
Teams.extractTeamMemberships = jest.fn().mockReturnValue([authorName])
94+
const validation = await author.processValidate(createMockContext(authorName), settings)
95+
expect(validation.status).toBe('fail')
96+
})
97+
98+
test('should pass when the author is a member of the team', async () => {
99+
const author = new Author()
100+
const settings = {
101+
do: 'author',
102+
team: 'org/team-slug'
103+
}
104+
Teams.extractTeamMemberships = jest.fn().mockReturnValue([authorName])
105+
const validation = await author.processValidate(createMockContext(authorName), settings)
106+
expect(validation.status).toBe('pass')
107+
})
108+
109+
test('should fail when the author is not a member of the team', async () => {
110+
const author = new Author()
111+
const authorName = 'mergeable'
112+
const settings = {
113+
do: 'author',
114+
team: 'org/team-slug'
115+
}
116+
Teams.extractTeamMemberships = jest.fn().mockReturnValue([otherAuthorName])
117+
const validation = await author.processValidate(createMockContext(authorName), settings)
118+
expect(validation.status).toBe('fail')
119+
})
120+
121+
const createMockContext = (author) => {
122+
return Helper.mockContext({ author })
123+
}

docs/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
CHANGELOG
22
=====================================
3+
| April 25, 2023: feat: Add author validator `#710 <https://github.com/mergeability/mergeable/pull/710>`_
34
| March 13, 2023: fix: Replace delete with remove in changeset validator `#705 <https://github.com/mergeability/mergeable/pull/705>`_
45
| March 7, 2023: fix: Extend checks supported events `#700 <https://github.com/mergeability/mergeable/pull/700>`_
56
| March 1, 2023: feat:Added user information for commit validator `#682 <https://github.com/mergeability/mergeable/pull/682>`_

docs/configuration.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ Validator List
116116
validators/age.rst
117117
validators/approval.rst
118118
validators/assignee.rst
119+
validators/author.rst
119120
validators/baseRef.rst
120121
validators/changeset.rst
121122
validators/commit.rst

docs/validators/author.rst

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
Author
2+
^^^^^^^^^^^^^^
3+
4+
::
5+
6+
- do: author
7+
must_include:
8+
regex: 'user-1'
9+
message: 'Custom include message...'
10+
must_exclude:
11+
regex: 'user-2'
12+
message: 'Custom exclude message...'
13+
team: 'org/team-slug' # verify that the author is in the team
14+
# all of the message sub-option is optional
15+
16+
you can use ``and`` and ``or`` options to create more complex filters
17+
18+
::
19+
20+
- do: author
21+
and:
22+
- must_exclude:
23+
regex: 'bot-user-1'
24+
message: 'Custom message...'
25+
or:
26+
- must_include:
27+
regex: 'user-1'
28+
message: 'Custom message...'
29+
- must_include:
30+
regex: 'user-2'
31+
message: 'Custom message...'
32+
33+
you can also nest ``and`` and ``or`` options
34+
35+
::
36+
37+
- do: author
38+
and:
39+
- or:
40+
- must_include:
41+
regex: 'user-1'
42+
message: 'Custom message...'
43+
- must_include:
44+
regex: 'user-2'
45+
message: 'Custom message...'
46+
- must_exclude:
47+
regex: 'bot-user-1'
48+
message: 'Custom message...'
49+
50+
Supported Events:
51+
::
52+
53+
'pull_request.*', 'pull_request_review.*'

lib/validators/author.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const { Validator } = require('./validator')
2+
3+
class Author extends Validator {
4+
constructor () {
5+
super('author')
6+
this.supportedEvents = [
7+
'pull_request.*',
8+
'pull_request_review.*'
9+
]
10+
this.supportedSettings = {
11+
must_include: {
12+
regex: 'string',
13+
regex_flag: 'string',
14+
message: 'string'
15+
},
16+
must_exclude: {
17+
regex: 'string',
18+
regex_flag: 'string',
19+
message: 'string'
20+
},
21+
team: 'string'
22+
}
23+
}
24+
25+
async validate (context, settings) {
26+
const payload = this.getPayload(context)
27+
return this.processOptions(settings, payload.user.login)
28+
}
29+
}
30+
31+
module.exports = Author
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const Teams = require('../teams')
2+
3+
class TeamProcessor {
4+
static async process (context, input, rule) {
5+
const userName = input
6+
const teamName = rule.team
7+
8+
const SUCCESS_MESSAGE = `'${userName}' is part of the '${teamName}' team'`
9+
const FAILURE_MESSAGE = `'${userName}' is not part of the '${teamName}' team'`
10+
11+
const teamMemberships = await Teams.extractTeamMemberships(context, [teamName], [userName])
12+
const isMember = teamMemberships.includes(userName)
13+
14+
return {
15+
status: isMember ? 'pass' : 'fail',
16+
description: isMember ? SUCCESS_MESSAGE : FAILURE_MESSAGE
17+
}
18+
}
19+
}
20+
21+
module.exports = TeamProcessor

lib/validators/validator.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ const DEFAULT_SUPPORTED_OPTIONS = [
1818
'must_include',
1919
'no_empty',
2020
'required',
21-
'jira'
21+
'jira',
22+
'team'
2223
]
2324

2425
class Validator extends EventAware {

0 commit comments

Comments
 (0)