Skip to content

Commit a53b2c8

Browse files
committed
Refactor candidate selection tests
In preparation for adding database to them, which will be required for review preferences.
1 parent 561f872 commit a53b2c8

File tree

2 files changed

+102
-157
lines changed

2 files changed

+102
-157
lines changed
Lines changed: 95 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,70 @@
11
//! Tests for `candidate_reviewers_from_names`
22
33
use super::super::*;
4+
use crate::tests::github::{issue, user};
45

5-
/// Basic test function for testing `candidate_reviewers_from_names`.
6-
fn test_from_names(
7-
teams: Option<toml::Table>,
8-
config: toml::Table,
9-
issue: serde_json::Value,
10-
names: &[&str],
11-
expected: Result<&[&str], FindReviewerError>,
12-
) {
13-
let (teams, config, issue) = convert_simplified(teams, config, issue);
14-
let names: Vec<_> = names.iter().map(|n| n.to_string()).collect();
15-
match (
16-
candidate_reviewers_from_names(&teams, &config, &issue, &names),
17-
expected,
18-
) {
19-
(Ok(candidates), Ok(expected)) => {
20-
let mut candidates: Vec<_> = candidates.into_iter().collect();
21-
candidates.sort();
22-
let expected: Vec<_> = expected.iter().map(|x| *x).collect();
23-
assert_eq!(candidates, expected);
6+
#[must_use]
7+
struct TestCtx {
8+
teams: Teams,
9+
config: AssignConfig,
10+
issue: Issue,
11+
}
12+
13+
impl TestCtx {
14+
fn new(config: toml::Table, issue: Issue) -> Self {
15+
Self {
16+
teams: Teams {
17+
teams: Default::default(),
18+
},
19+
config: config.try_into().unwrap(),
20+
issue,
2421
}
25-
(Err(actual), Err(expected)) => {
26-
assert_eq!(actual, expected)
22+
}
23+
24+
fn teams(mut self, table: &toml::Table) -> Self {
25+
let teams: serde_json::Value = table.clone().try_into().unwrap();
26+
let mut teams_config = serde_json::json!({});
27+
for (team_name, members) in teams.as_object().unwrap() {
28+
let members: Vec<_> = members.as_array().unwrap().iter().map(|member| {
29+
serde_json::json!({"name": member, "github": member, "github_id": 100, "is_lead": false})
30+
}).collect();
31+
teams_config[team_name] = serde_json::json!({
32+
"name": team_name,
33+
"kind": "team",
34+
"members": serde_json::Value::Array(members),
35+
"alumni": [],
36+
"discord": [],
37+
"roles": [],
38+
});
2739
}
28-
(Ok(candidates), Err(_)) => panic!("expected Err, got Ok: {candidates:?}"),
29-
(Err(e), Ok(_)) => panic!("expected Ok, got Err: {e}"),
40+
self.teams = serde_json::value::from_value(teams_config).unwrap();
41+
self
3042
}
31-
}
3243

33-
/// Convert the simplified input in preparation for `candidate_reviewers_from_names`.
34-
fn convert_simplified(
35-
teams: Option<toml::Table>,
36-
config: toml::Table,
37-
issue: serde_json::Value,
38-
) -> (Teams, AssignConfig, Issue) {
39-
// Convert the simplified team config to a real team config.
40-
// This uses serde_json since it is easier to manipulate than toml.
41-
let teams: serde_json::Value = match teams {
42-
Some(teams) => teams.try_into().unwrap(),
43-
None => serde_json::json!({}),
44-
};
45-
let mut teams_config = serde_json::json!({});
46-
for (team_name, members) in teams.as_object().unwrap() {
47-
let members: Vec<_> = members.as_array().unwrap().iter().map(|member| {
48-
serde_json::json!({"name": member, "github": member, "github_id": 1, "is_lead": false})
49-
}).collect();
50-
teams_config[team_name] = serde_json::json!({
51-
"name": team_name,
52-
"kind": "team",
53-
"members": serde_json::Value::Array(members),
54-
"alumni": [],
55-
"discord": [],
56-
"roles": [],
57-
});
44+
fn run(self, names: &[&str], expected: Result<&[&str], FindReviewerError>) {
45+
let names: Vec<_> = names.iter().map(|n| n.to_string()).collect();
46+
match (
47+
candidate_reviewers_from_names(&self.teams, &self.config, &self.issue, &names),
48+
expected,
49+
) {
50+
(Ok(candidates), Ok(expected)) => {
51+
let mut candidates: Vec<_> = candidates.into_iter().collect();
52+
candidates.sort();
53+
let expected: Vec<_> = expected.iter().map(|x| *x).collect();
54+
assert_eq!(candidates, expected);
55+
}
56+
(Err(actual), Err(expected)) => {
57+
assert_eq!(actual, expected)
58+
}
59+
(Ok(candidates), Err(_)) => panic!("expected Err, got Ok: {candidates:?}"),
60+
(Err(e), Ok(_)) => panic!("expected Ok, got Err: {e}"),
61+
}
5862
}
59-
let teams = serde_json::value::from_value(teams_config).unwrap();
60-
let config = config.try_into().unwrap();
61-
let issue = serde_json::value::from_value(issue).unwrap();
62-
(teams, config, issue)
6363
}
6464

65-
fn generic_issue(author: &str, repo: &str) -> serde_json::Value {
66-
serde_json::json!({
67-
"number": 1234,
68-
"created_at": "2022-06-26T21:31:31Z",
69-
"updated_at": "2022-06-26T21:31:31Z",
70-
"title": "Example PR",
71-
"body": "PR body",
72-
"html_url": "https://github.com/rust-lang/rust/pull/1234",
73-
"user": {
74-
"login": author,
75-
"id": 583231,
76-
},
77-
"labels": [],
78-
"assignees": [],
79-
"comments_url": format!("https://api.github.com/repos/{repo}/pull/1234/comments"),
80-
"state": "open",
81-
})
65+
/// Basic test function for testing `candidate_reviewers_from_names`.
66+
fn test_candidates(config: toml::Table, issue: Issue) -> TestCtx {
67+
TestCtx::new(config, issue)
8268
}
8369

8470
#[test]
@@ -89,11 +75,7 @@ fn circular_groups() {
8975
compiler = ["other"]
9076
other = ["compiler"]
9177
);
92-
let issue = generic_issue("octocat", "rust-lang/rust");
93-
test_from_names(
94-
None,
95-
config,
96-
issue,
78+
test_candidates(config, issue().call()).run(
9779
&["compiler"],
9880
Err(FindReviewerError::NoReviewer {
9981
initial: vec!["compiler".to_string()],
@@ -110,8 +92,7 @@ fn nested_groups() {
11092
b = ["@nrc"]
11193
c = ["a", "b"]
11294
);
113-
let issue = generic_issue("octocat", "rust-lang/rust");
114-
test_from_names(None, config, issue, &["c"], Ok(&["nrc", "pnkfelix"]));
95+
test_candidates(config, issue().call()).run(&["c"], Ok(&["nrc", "pnkfelix"]));
11596
}
11697

11798
#[test]
@@ -121,11 +102,7 @@ fn candidate_filtered_author_only_candidate() {
121102
[adhoc_groups]
122103
compiler = ["nikomatsakis"]
123104
);
124-
let issue = generic_issue("nikomatsakis", "rust-lang/rust");
125-
test_from_names(
126-
None,
127-
config,
128-
issue,
105+
test_candidates(config, issue().author(user("nikomatsakis", 1)).call()).run(
129106
&["compiler"],
130107
Err(FindReviewerError::NoReviewer {
131108
initial: vec!["compiler".to_string()],
@@ -141,14 +118,8 @@ fn candidate_filtered_author() {
141118
compiler = ["user1", "user2", "user3", "group2"]
142119
group2 = ["user2", "user4"]
143120
);
144-
let issue = generic_issue("user2", "rust-lang/rust");
145-
test_from_names(
146-
None,
147-
config,
148-
issue,
149-
&["compiler"],
150-
Ok(&["user1", "user3", "user4"]),
151-
);
121+
test_candidates(config, issue().author(user("user2", 1)).call())
122+
.run(&["compiler"], Ok(&["user1", "user3", "user4"]));
152123
}
153124

154125
#[test]
@@ -158,12 +129,11 @@ fn candidate_filtered_assignee() {
158129
[adhoc_groups]
159130
compiler = ["user1", "user2", "user3", "user4"]
160131
);
161-
let mut issue = generic_issue("user2", "rust-lang/rust");
162-
issue["assignees"] = serde_json::json!([
163-
{"login": "user1", "id": 1},
164-
{"login": "user3", "id": 3},
165-
]);
166-
test_from_names(None, config, issue, &["compiler"], Ok(&["user4"]));
132+
let issue = issue()
133+
.author(user("user2", 2))
134+
.assignees(vec![user("user1", 1), user("user3", 3)])
135+
.call();
136+
test_candidates(config, issue).run(&["compiler"], Ok(&["user4"]));
167137
}
168138

169139
#[test]
@@ -177,11 +147,7 @@ fn groups_teams_users() {
177147
[adhoc_groups]
178148
group1 = ["user1", "rust-lang/team2"]
179149
);
180-
let issue = generic_issue("octocat", "rust-lang/rust");
181-
test_from_names(
182-
Some(teams),
183-
config,
184-
issue,
150+
test_candidates(config, issue().call()).teams(&teams).run(
185151
&["team1", "group1", "user3"],
186152
Ok(&["t-user1", "t-user2", "user1", "user3"]),
187153
);
@@ -195,21 +161,12 @@ fn group_team_user_precedence() {
195161
[adhoc_groups]
196162
compiler = ["user2"]
197163
);
198-
let issue = generic_issue("octocat", "rust-lang/rust");
199-
test_from_names(
200-
Some(teams.clone()),
201-
config.clone(),
202-
issue.clone(),
203-
&["compiler"],
204-
Ok(&["user2"]),
205-
);
206-
test_from_names(
207-
Some(teams.clone()),
208-
config.clone(),
209-
issue.clone(),
210-
&["rust-lang/compiler"],
211-
Ok(&["user2"]),
212-
);
164+
test_candidates(config.clone(), issue().call())
165+
.teams(&teams)
166+
.run(&["compiler"], Ok(&["user2"]));
167+
test_candidates(config, issue().call())
168+
.teams(&teams)
169+
.run(&["rust-lang/compiler"], Ok(&["user2"]));
213170
}
214171

215172
#[test]
@@ -221,22 +178,16 @@ fn what_do_slashes_mean() {
221178
compiler = ["user2"]
222179
"foo/bar" = ["foo-user"]
223180
);
224-
let issue = generic_issue("octocat", "rust-lang-nursery/rust");
181+
let issue = || issue().org("rust-lang-nursery").call();
182+
225183
// Random slash names should work from groups.
226-
test_from_names(
227-
Some(teams.clone()),
228-
config.clone(),
229-
issue.clone(),
230-
&["foo/bar"],
231-
Ok(&["foo-user"]),
232-
);
233-
test_from_names(
234-
Some(teams.clone()),
235-
config.clone(),
236-
issue.clone(),
237-
&["rust-lang-nursery/compiler"],
238-
Ok(&["user2"]),
239-
);
184+
test_candidates(config.clone(), issue())
185+
.teams(&teams)
186+
.run(&["foo/bar"], Ok(&["foo-user"]));
187+
188+
test_candidates(config, issue())
189+
.teams(&teams)
190+
.run(&["rust-lang-nursery/compiler"], Ok(&["user2"]));
240191
}
241192

242193
#[test]
@@ -246,11 +197,7 @@ fn invalid_org_doesnt_match() {
246197
[adhoc_groups]
247198
compiler = ["user2"]
248199
);
249-
let issue = generic_issue("octocat", "rust-lang/rust");
250-
test_from_names(
251-
Some(teams),
252-
config,
253-
issue,
200+
test_candidates(config, issue().call()).teams(&teams).run(
254201
&["github/compiler"],
255202
Err(FindReviewerError::TeamNotFound(
256203
"github/compiler".to_string(),
@@ -262,25 +209,18 @@ fn invalid_org_doesnt_match() {
262209
fn vacation() {
263210
let teams = toml::toml!(bootstrap = ["jyn514", "Mark-Simulacrum"]);
264211
let config = toml::toml!(users_on_vacation = ["jyn514"]);
265-
let issue = generic_issue("octocat", "rust-lang/rust");
266212

267213
// Test that `r? user` returns a specific error about the user being on vacation.
268-
test_from_names(
269-
Some(teams.clone()),
270-
config.clone(),
271-
issue.clone(),
272-
&["jyn514"],
273-
Err(FindReviewerError::ReviewerOnVacation {
274-
username: "jyn514".to_string(),
275-
}),
276-
);
277-
278-
// Test that `r? bootstrap` doesn't assign from users on vacation.
279-
test_from_names(
280-
Some(teams.clone()),
281-
config.clone(),
282-
issue,
283-
&["bootstrap"],
284-
Ok(&["Mark-Simulacrum"]),
285-
);
214+
test_candidates(config.clone(), issue().call())
215+
.teams(&teams)
216+
.run(
217+
&["jyn514"],
218+
Err(FindReviewerError::ReviewerOnVacation {
219+
username: "jyn514".to_string(),
220+
}),
221+
);
222+
223+
test_candidates(config.clone(), issue().call())
224+
.teams(&teams)
225+
.run(&["bootstrap"], Ok(&["Mark-Simulacrum"]));
286226
}

src/tests/github.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ pub fn issue(
2424
body: Option<&str>,
2525
assignees: Option<Vec<User>>,
2626
pr: Option<bool>,
27+
org: Option<&str>,
28+
repo: Option<&str>,
2729
) -> Issue {
2830
let number = number.unwrap_or(1);
2931
let state = state.unwrap_or(IssueState::Open);
@@ -35,6 +37,8 @@ pub fn issue(
3537
} else {
3638
None
3739
};
40+
let org = org.unwrap_or("rust-lang");
41+
let repo = repo.unwrap_or("rust");
3842

3943
Issue {
4044
number,
@@ -43,15 +47,16 @@ pub fn issue(
4347
updated_at: Utc::now(),
4448
merge_commit_sha: None,
4549
title: format!("Issue #{number}"),
46-
html_url: "<html-url>".to_string(),
50+
html_url: format!("https://github.com/{org}/{repo}/pull/{number}"),
4751
user: author,
4852
labels: vec![],
4953
assignees,
5054
pull_request,
5155
merged: false,
5256
draft: false,
5357
comments: None,
54-
comments_url: "".to_string(),
58+
// The repository is parsed from comments_url, so this field is important
59+
comments_url: format!("https://api.github.com/repos/{org}/{repo}/issues/{number}/comments"),
5560
repository: Default::default(),
5661
base: None,
5762
head: None,

0 commit comments

Comments
 (0)