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

necojackarc / auto-request-review / 34653997596

11 Sep 2026 10:26PM UTC coverage: 91.566% (+2.5%) from 89.076%
34653997596

Pull #159

github

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

77 of 89 branches covered (86.52%)

Branch coverage included in aggregate %.

74 of 75 new or added lines in 3 files covered. (98.67%)

4 existing lines in 1 file now uncovered.

227 of 243 relevant lines covered (93.42%)

6.92 hits per line

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

90.18
/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();
6 ✔
39

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

44
  if (context.payload.issue && context.payload.issue.pull_request) {
2 ✔
45
    return context.payload.issue.number;
1 ✔
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
/* Private */
209

210
let context_cache;
211
let token_cache;
212
let config_path_cache;
213
let use_local_cache;
214
let octokit_cache;
215

216
function get_context() {
217
  return context_cache || (context_cache = github.context);
17 ✔
218
}
219

220
function get_token() {
221
  return token_cache || (token_cache = core.getInput('token'));
8 ✔
222
}
223

224
function get_config_path() {
225
  return config_path_cache || (config_path_cache = core.getInput('config'));
1 ✔
226
}
227

228
function get_use_local() {
229
  return use_local_cache ?? (use_local_cache = core.getInput('use_local') === 'true');
1 ✔
230
}
231

232
function get_octokit() {
233
  if (octokit_cache) {
8 !
234
    return octokit_cache;
×
235
  }
236

237
  const token = get_token();
8 ✔
238
  return octokit_cache = github.getOctokit(token);
8 ✔
239
}
240

241
function clear_cache() {
242
  context_cache = undefined;
27 ✔
243
  token_cache = undefined;
27 ✔
244
  config_path_cache = undefined;
27 ✔
245
  octokit_cache = undefined;
27 ✔
246
}
247

248
module.exports = {
1 ✔
249
  get_pull_request,
250
  get_pull_request_number,
251
  has_full_pull_request_payload,
252
  fetch_config,
253
  fetch_changed_files,
254
  assign_reviewers,
255
  list_comments,
256
  list_reviews,
257
  get_permission_level,
258
  clear_cache,
259
};
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