• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

necojackarc / auto-request-review / 34822949994

14 Sep 2026 08:29AM UTC coverage: 92.023% (+2.9%) from 89.076%
34822949994

Pull #159

github

web-flow
Merge 8ee886827 into 9a4b11888
Pull Request #159: Add require-review comment directive (#130)

81 of 93 branches covered (87.1%)

Branch coverage included in aggregate %.

89 of 90 new or added lines in 3 files covered. (98.89%)

5 existing lines in 1 file now uncovered.

242 of 258 relevant lines covered (93.8%)

6.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.27
/src/github.js
1
'use strict';
2

3
const core = require('@actions/core');
1 ✔
4
const fs = require('fs');
1 ✔
5
const github = require('@actions/github');
1 ✔
6
const partition = require('lodash/partition');
1 ✔
7
const yaml = require('yaml');
1 ✔
8
const { LOCAL_FILE_MISSING } = require('./constants');
1 ✔
9

10
class PullRequest {
11
  // ref: https://developer.github.com/v3/pulls/#get-a-pull-request
12
  constructor(pull_request_paylaod) {
13
    // "ncc" doesn't yet support private class fields as of 29 Aug. 2020
14
    // ref: https://github.com/vercel/ncc/issues/499
15
    this._pull_request_paylaod = pull_request_paylaod;
1 ✔
16
  }
17

18
  get author() {
19
    return this._pull_request_paylaod.user.login;
1 ✔
20
  }
21

22
  get title() {
23
    return this._pull_request_paylaod.title;
1 ✔
24
  }
25

26
  get is_draft() {
27
    return this._pull_request_paylaod.draft;
1 ✔
28
  }
29
}
30

31
function get_pull_request() {
32
  const context = get_context();
1 ✔
33

34
  return new PullRequest(context.payload.pull_request);
1 ✔
35
}
36

37
function get_pull_request_number() {
38
  const context = get_context();
7 ✔
39

40
  if (context.payload.pull_request) {
7 ✔
41
    return context.payload.pull_request.number;
4 ✔
42
  }
43

44
  if (context.payload.issue && context.payload.issue.pull_request) {
3 ✔
45
    return context.payload.issue.number;
2 ✔
46
  }
47

48
  return undefined;
1 ✔
49
}
50

51
function has_full_pull_request_payload() {
52
  const context = get_context();
2 ✔
53

54
  return Boolean(context.payload.pull_request);
2 ✔
55
}
56

57
async function fetch_config() {
58
  const context = get_context();
1 ✔
59
  const octokit = get_octokit();
1 ✔
60
  const config_path = get_config_path();
1 ✔
61
  const useLocal = get_use_local();
1 ✔
62
  let content = '';
1 ✔
63

64
  if (!useLocal) {
1 !
65
    const { data: response_body } = await octokit.rest.repos.getContent({
1 ✔
66
      owner: context.repo.owner,
67
      repo: context.repo.repo,
68
      path: config_path,
69
      ref: context.ref,
70
    });
71

72
    content = Buffer.from(response_body.content, response_body.encoding).toString();
1 ✔
73
  } else {
UNCOV
74
    try {
×
UNCOV
75
      content = fs.readFileSync(config_path).toString();
×
76

77
      if (!content) {
×
UNCOV
78
        throw new Error();
×
79
      }
80
    } catch (error) {
UNCOV
81
      core.debug(`Error when reading local file: ${error}`);
×
82

83
      throw new Error(LOCAL_FILE_MISSING);
×
84
    }
85
  }
86

87
  return yaml.parse(content);
1 ✔
88
}
89

90
async function fetch_changed_files() {
91
  const context = get_context();
2 ✔
92
  const octokit = get_octokit();
2 ✔
93

94
  const changed_files = [];
2 ✔
95

96
  const per_page = 100;
2 ✔
97
  let page = 0;
2 ✔
98
  let number_of_files_in_current_page;
99

100
  do {
2 ✔
101
    page += 1;
4 ✔
102

103
    const { data: response_body } = await octokit.rest.pulls.listFiles({
4 ✔
104
      owner: context.repo.owner,
105
      repo: context.repo.repo,
106
      pull_number: context.payload.pull_request.number,
107
      page,
108
      per_page,
109
    });
110

111
    number_of_files_in_current_page = response_body.length;
4 ✔
112
    changed_files.push(...response_body.map((file) => file.filename));
224 ✔
113

114
  } while (number_of_files_in_current_page === per_page);
115

116
  return changed_files;
2 ✔
117
}
118

119
async function assign_reviewers(reviewers) {
120
  const context = get_context();
1 ✔
121
  const octokit = get_octokit();
1 ✔
122

123
  const [ teams_with_prefix, individuals ] = partition(reviewers, (reviewer) => reviewer.startsWith('team:'));
3 ✔
124
  const teams = teams_with_prefix.map((team_with_prefix) => team_with_prefix.replace('team:', ''));
1 ✔
125

126
  return octokit.rest.pulls.requestReviewers({
1 ✔
127
    owner: context.repo.owner,
128
    repo: context.repo.repo,
129
    pull_number: context.payload.pull_request.number,
130
    reviewers: individuals,
131
    team_reviewers: teams,
132
  });
133
}
134

135
async function list_comments() {
136
  const context = get_context();
2 ✔
137
  const octokit = get_octokit();
2 ✔
138
  const pull_request_number = get_pull_request_number();
2 ✔
139

140
  const comments = [];
2 ✔
141

142
  const per_page = 100;
2 ✔
143
  let page = 0;
2 ✔
144
  let number_of_comments_in_current_page;
145

146
  do {
2 ✔
147
    page += 1;
4 ✔
148

149
    const { data: response_body } = await octokit.rest.issues.listComments({
4 ✔
150
      owner: context.repo.owner,
151
      repo: context.repo.repo,
152
      issue_number: pull_request_number,
153
      page,
154
      per_page,
155
    });
156

157
    number_of_comments_in_current_page = response_body.length;
4 ✔
158
    comments.push(...response_body);
4 ✔
159

160
  } while (number_of_comments_in_current_page === per_page);
161

162
  return comments;
2 ✔
163
}
164

165
async function list_reviews() {
166
  const context = get_context();
1 ✔
167
  const octokit = get_octokit();
1 ✔
168
  const pull_request_number = get_pull_request_number();
1 ✔
169

170
  const reviews = [];
1 ✔
171

172
  const per_page = 100;
1 ✔
173
  let page = 0;
1 ✔
174
  let number_of_reviews_in_current_page;
175

176
  do {
1 ✔
177
    page += 1;
1 ✔
178

179
    const { data: response_body } = await octokit.rest.pulls.listReviews({
1 ✔
180
      owner: context.repo.owner,
181
      repo: context.repo.repo,
182
      pull_number: pull_request_number,
183
      page,
184
      per_page,
185
    });
186

187
    number_of_reviews_in_current_page = response_body.length;
1 ✔
188
    reviews.push(...response_body);
1 ✔
189

190
  } while (number_of_reviews_in_current_page === per_page);
191

192
  return reviews;
1 ✔
193
}
194

195
async function get_permission_level(username) {
196
  const context = get_context();
1 ✔
197
  const octokit = get_octokit();
1 ✔
198

199
  const { data: response_body } = await octokit.rest.repos.getCollaboratorPermissionLevel({
1 ✔
200
    owner: context.repo.owner,
201
    repo: context.repo.repo,
202
    username,
203
  });
204

205
  return response_body.permission;
1 ✔
206
}
207

208
async function get_pull_request_head_sha() {
209
  const context = get_context();
2 ✔
210

211
  if (context.payload.pull_request) {
2 ✔
212
    return context.payload.pull_request.head.sha;
1 ✔
213
  }
214

215
  const octokit = get_octokit();
1 ✔
216
  const pull_request_number = get_pull_request_number();
1 ✔
217

218
  const { data: response_body } = await octokit.rest.pulls.get({
1 ✔
219
    owner: context.repo.owner,
220
    repo: context.repo.repo,
221
    pull_number: pull_request_number,
222
  });
223

224
  return response_body.head.sha;
1 ✔
225
}
226

227
async function create_check_run({ head_sha, conclusion, summary }) {
228
  const context = get_context();
2 ✔
229
  const octokit = get_octokit();
2 ✔
230

231
  return octokit.rest.checks.create({
2 ✔
232
    owner: context.repo.owner,
233
    repo: context.repo.repo,
234
    name: 'require-review',
235
    head_sha,
236
    status: 'completed',
237
    conclusion,
238
    output: {
239
      title: conclusion === 'success' ? 'All required reviews are approved' : 'Missing required approving review(s)',
2 ✔
240
      summary,
241
    },
242
  });
243
}
244

245
/* Private */
246

247
let context_cache;
248
let token_cache;
249
let config_path_cache;
250
let use_local_cache;
251
let octokit_cache;
252

253
function get_context() {
254
  return context_cache || (context_cache = github.context);
22 ✔
255
}
256

257
function get_token() {
258
  return token_cache || (token_cache = core.getInput('token'));
11 ✔
259
}
260

261
function get_config_path() {
262
  return config_path_cache || (config_path_cache = core.getInput('config'));
1 ✔
263
}
264

265
function get_use_local() {
266
  return use_local_cache ?? (use_local_cache = core.getInput('use_local') === 'true');
1 ✔
267
}
268

269
function get_octokit() {
270
  if (octokit_cache) {
11 !
UNCOV
271
    return octokit_cache;
×
272
  }
273

274
  const token = get_token();
11 ✔
275
  return octokit_cache = github.getOctokit(token);
11 ✔
276
}
277

278
function clear_cache() {
279
  context_cache = undefined;
31 ✔
280
  token_cache = undefined;
31 ✔
281
  config_path_cache = undefined;
31 ✔
282
  octokit_cache = undefined;
31 ✔
283
}
284

285
module.exports = {
1 ✔
286
  get_pull_request,
287
  get_pull_request_number,
288
  has_full_pull_request_payload,
289
  fetch_config,
290
  fetch_changed_files,
291
  assign_reviewers,
292
  list_comments,
293
  list_reviews,
294
  get_permission_level,
295
  get_pull_request_head_sha,
296
  create_check_run,
297
  clear_cache,
298
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc