Skip to content

Commit 8317573

Browse files
committed
feat(bypass): prompt to confirm if admins should be able to bypass required checks for user repo
1 parent a9438fd commit 8317573

File tree

4 files changed

+77
-31
lines changed

4 files changed

+77
-31
lines changed

src/prompt/constants.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
export const ids = {REQUIRED_CHECK_BYPASS: 'REQUIRED_CHECK_BYPASS'};
22

3-
export const questionNames = {[ids.REQUIRED_CHECK_BYPASS]: {CHECK_BYPASS_TEAM: 'CHECK_BYPASS_TEAM'}};
3+
export const questionNames = {
4+
[ids.REQUIRED_CHECK_BYPASS]: {
5+
CHECK_BYPASS_TEAM: 'CHECK_BYPASS_TEAM',
6+
ADMIN_BYPASS: 'ADMIN_BYPASS'
7+
}
8+
};

src/rulesets/verification/bypass-actors/prompt.js

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,31 @@ import {ids, questionNames} from '../../../prompt/constants.js';
22

33
export const ADMIN_ROLE_ID = 5;
44

5+
async function defineQuestionToChooseBypassTeam(checkBypassTeamQuestionName, vcs, octokit) {
6+
const {data: teams} = await octokit.request('GET /orgs/{org}/teams', {org: vcs.owner});
7+
8+
return {
9+
type: 'list',
10+
name: checkBypassTeamQuestionName,
11+
message: 'Which team should be able to bypass the required checks?',
12+
choices: teams.map(({name, slug, id}) => ({name, value: id, short: slug}))
13+
};
14+
}
15+
16+
async function defineQuestionToConfirmAdminBypass(adminBypassQuestionName) {
17+
return {
18+
type: 'confirm',
19+
name: adminBypassQuestionName,
20+
message: 'Should repository admins be able to bypass the required checks?'
21+
};
22+
}
23+
524
export default async function promptForCheckBypass(vcs, {prompt, octokit}) {
625
const promptId = ids.REQUIRED_CHECK_BYPASS;
7-
const checkBypassTeamQuestionName = questionNames[promptId].CHECK_BYPASS_TEAM;
26+
const {
27+
[questionNames[promptId].CHECK_BYPASS_TEAM]: checkBypassTeamQuestionName,
28+
[questionNames[promptId].ADMIN_BYPASS]: adminBypassQuestionName
29+
} = questionNames[promptId];
830

931
if (!octokit) {
1032
const missingOctokitError = new Error('No octokit instance provided');
@@ -15,23 +37,20 @@ export default async function promptForCheckBypass(vcs, {prompt, octokit}) {
1537

1638
const {data: {login: authenticatedUser}} = await octokit.request('GET /user');
1739

18-
if (vcs.owner === authenticatedUser) {
19-
return {role: ADMIN_ROLE_ID};
20-
}
21-
22-
const {data: teams} = await octokit.request('GET /orgs/{org}/teams', {org: vcs.owner});
23-
24-
const {[checkBypassTeamQuestionName]: teamId} = await prompt({
40+
const {
41+
[checkBypassTeamQuestionName]: teamId,
42+
[adminBypassQuestionName]: adminBypass
43+
} = await prompt({
2544
id: promptId,
2645
questions: [
27-
{
28-
type: 'list',
29-
name: checkBypassTeamQuestionName,
30-
message: 'Which team should be able to bypass the required checks?',
31-
choices: teams.map(({name, slug, id}) => ({name, value: id, short: slug}))
32-
}
46+
vcs.owner === authenticatedUser
47+
? await defineQuestionToConfirmAdminBypass(adminBypassQuestionName)
48+
: await defineQuestionToChooseBypassTeam(checkBypassTeamQuestionName, vcs, octokit)
3349
]
3450
});
3551

36-
return {team: teamId};
52+
return {
53+
team: teamId,
54+
role: adminBypass && ADMIN_ROLE_ID
55+
};
3756
}

src/rulesets/verification/bypass-actors/prompt.test.js

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import promptForCheckBypass, {ADMIN_ROLE_ID} from './prompt.js';
77

88
describe('required check bypass prompt', () => {
99
const promptId = ids.REQUIRED_CHECK_BYPASS;
10-
const checkBypassTeamQuestionName = questionNames[promptId].CHECK_BYPASS_TEAM;
10+
const {
11+
CHECK_BYPASS_TEAM: checkBypassTeamQuestionName,
12+
ADMIN_BYPASS: adminBypassQuestionName
13+
} = questionNames[promptId];
1114
const teamId = any.word();
1215

1316
it('should enable choosing a team to bypass the required check for an organization owned repository', async () => {
@@ -22,14 +25,12 @@ describe('required check bypass prompt', () => {
2225
when(prompt)
2326
.calledWith({
2427
id: promptId,
25-
questions: [
26-
{
27-
type: 'list',
28-
name: checkBypassTeamQuestionName,
29-
message: 'Which team should be able to bypass the required checks?',
30-
choices
31-
}
32-
]
28+
questions: [{
29+
type: 'list',
30+
name: checkBypassTeamQuestionName,
31+
message: 'Which team should be able to bypass the required checks?',
32+
choices
33+
}]
3334
})
3435
.thenResolve({[checkBypassTeamQuestionName]: teamId});
3536

@@ -42,6 +43,16 @@ describe('required check bypass prompt', () => {
4243
const user = any.word();
4344
const vcs = {...any.simpleObject(), owner: user};
4445
when(octokit.request).calledWith('GET /user').thenResolve({data: {login: user}});
46+
when(prompt)
47+
.calledWith({
48+
id: promptId,
49+
questions: [{
50+
type: 'confirm',
51+
name: adminBypassQuestionName,
52+
message: 'Should repository admins be able to bypass the required checks?'
53+
}]
54+
})
55+
.thenResolve({[adminBypassQuestionName]: true});
4556

4657
expect(await promptForCheckBypass(vcs, {prompt, octokit})).toEqual({role: ADMIN_ROLE_ID});
4758
});

test/integration/features/step_definitions/common-steps.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import any from '@travi/any';
77

88
// eslint-disable-next-line import/no-extraneous-dependencies,import/no-unresolved
99
import {lift, promptConstants, scaffold, test} from '@form8ion/repository-settings';
10+
import {questionNames} from '../../../../src/prompt/constants.js';
1011

1112
const __dirname = dirname(fileURLToPath(import.meta.url)); // eslint-disable-line no-underscore-dangle
1213
const stubbedNodeModules = stubbedFs.load(resolve(__dirname, '..', '..', '..', '..', 'node_modules'));
@@ -68,16 +69,26 @@ When('scaffolder results are processed', async function () {
6869
octokit: this.octokit,
6970
prompt: ({id, questions}) => {
7071
let chosenTeamId;
71-
const checkBypassTeamQuestionName = promptConstants.questionNames[id].CHECK_BYPASS_TEAM;
72+
const {
73+
[promptConstants.questionNames[id].CHECK_BYPASS_TEAM]: checkBypassTeamQuestionName,
74+
[promptConstants.questionNames[id].ADMIN_BYPASS]: adminBypassQuestionName
75+
} = questionNames[id];
7276

73-
if (this.octokit) {
74-
const {choices: checkBypassTeamChoices} = questions.find(({name}) => name === checkBypassTeamQuestionName);
77+
const checkBypassTeamQuestion = questions.find(({name}) => name === checkBypassTeamQuestionName);
78+
const adminBypassQuestion = questions.find(({name}) => name === adminBypassQuestionName);
79+
80+
if (checkBypassTeamQuestion) {
81+
const {choices: checkBypassTeamChoices} = checkBypassTeamQuestion;
7582
({value: chosenTeamId} = checkBypassTeamChoices.find(team => team.name === this.maintenanceTeamName));
76-
} else {
77-
chosenTeamId = this.maintenanceTeamName;
83+
84+
return {[checkBypassTeamQuestionName]: chosenTeamId};
85+
}
86+
87+
if (adminBypassQuestion) {
88+
return {[adminBypassQuestionName]: true};
7889
}
7990

80-
return {[checkBypassTeamQuestionName]: chosenTeamId};
91+
throw new Error('Expected a question to be asked for the required check bypass');
8192
}
8293
}
8394
);

0 commit comments

Comments
 (0)