hive-forge: 10 behaviour tests for lint's pure helpers, which had none
WIP commit so the mutation run below has a clean base to restore to.
This commit is contained in:
parent
54daf9ac60
commit
a78e783c3b
1 changed files with 160 additions and 0 deletions
|
|
@ -497,6 +497,166 @@ fn num(it: &Value) -> i64 {
|
||||||
it.get("number").and_then(Value::as_i64).unwrap_or(0)
|
it.get("number").and_then(Value::as_i64).unwrap_or(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// Every field on the forgejo structs is `Option`, so a fixture names
|
||||||
|
/// only what a case is about. Going through `from_value` rather than a
|
||||||
|
/// struct literal means these exercise the same deserialisation a real
|
||||||
|
/// API response does — a wire-shape change breaks the tests too.
|
||||||
|
fn issue(v: Value) -> Issue {
|
||||||
|
serde_json::from_value(v).expect("fixture is a valid Issue")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pull(v: Value) -> PullRequest {
|
||||||
|
serde_json::from_value(v).expect("fixture is a valid PullRequest")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `exclusive` is Forgejo's real scoped-label marker. A label that
|
||||||
|
/// merely *looks* scoped (a `/` in a plain label's name) must not
|
||||||
|
/// count, or `lint unlabeled` silently stops reporting an item that
|
||||||
|
/// genuinely has no `type/*`.
|
||||||
|
#[test]
|
||||||
|
fn only_an_exclusive_label_counts_as_scoped() {
|
||||||
|
let scoped = issue(json!({ "labels": [{ "name": "area/ops", "exclusive": true }] }));
|
||||||
|
assert!(has_scoped_label(&scoped, "area/"));
|
||||||
|
|
||||||
|
let lookalike = issue(json!({ "labels": [{ "name": "area/ops", "exclusive": false }] }));
|
||||||
|
assert!(
|
||||||
|
!has_scoped_label(&lookalike, "area/"),
|
||||||
|
"a non-exclusive label with a slash in it is not a scoped label"
|
||||||
|
);
|
||||||
|
|
||||||
|
let unset = issue(json!({ "labels": [{ "name": "area/ops" }] }));
|
||||||
|
assert!(!has_scoped_label(&unset, "area/"), "absent means not exclusive");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The caller builds the prefix as `"<scope>/"`, which is what stops
|
||||||
|
/// `--scope area` from matching a label called `areaology`.
|
||||||
|
#[test]
|
||||||
|
fn the_scope_prefix_stops_at_the_slash() {
|
||||||
|
let other = issue(json!({ "labels": [{ "name": "areaology", "exclusive": true }] }));
|
||||||
|
assert!(!has_scoped_label(&other, "area/"));
|
||||||
|
|
||||||
|
let wrong_scope = issue(json!({ "labels": [{ "name": "type/bug", "exclusive": true }] }));
|
||||||
|
assert!(!has_scoped_label(&wrong_scope, "area/"));
|
||||||
|
assert!(has_scoped_label(&wrong_scope, "type/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn an_item_with_no_labels_has_no_scoped_label() {
|
||||||
|
assert!(!has_scoped_label(&issue(json!({ "labels": [] })), "area/"));
|
||||||
|
assert!(!has_scoped_label(&issue(json!({})), "area/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `--reviewer NAME` and a bare `no-reviewer` are different questions:
|
||||||
|
/// "is X on it" versus "is anyone on it".
|
||||||
|
#[test]
|
||||||
|
fn requested_reviewer_asks_a_different_question_with_and_without_a_name() {
|
||||||
|
let pr = pull(json!({ "requested_reviewers": [{ "login": "argus" }] }));
|
||||||
|
assert!(has_requested_reviewer(&pr, None), "someone is requested");
|
||||||
|
assert!(has_requested_reviewer(&pr, Some("argus")));
|
||||||
|
assert!(!has_requested_reviewer(&pr, Some("iris")));
|
||||||
|
|
||||||
|
let bare = pull(json!({ "requested_reviewers": [] }));
|
||||||
|
assert!(!has_requested_reviewer(&bare, None));
|
||||||
|
assert!(!has_requested_reviewer(&bare, Some("argus")));
|
||||||
|
|
||||||
|
// `null`, not an omitted key: `requested_reviewers` carries a
|
||||||
|
// `deserialize_with`, so serde requires it to be present even though
|
||||||
|
// its type is `Option`. The forge sends the key with a null value.
|
||||||
|
let absent = pull(json!({ "requested_reviewers": null }));
|
||||||
|
assert!(!has_requested_reviewer(&absent, None), "null is not a reviewer");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn logins_drops_users_that_have_none() {
|
||||||
|
let users: Vec<forgejo_api::structs::User> =
|
||||||
|
serde_json::from_value(json!([{ "login": "atlas" }, {}, { "login": "mara" }]))
|
||||||
|
.expect("fixture");
|
||||||
|
assert_eq!(logins(Some(&users)), vec!["atlas", "mara"]);
|
||||||
|
assert!(logins(None).is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Issues and PRs come back from the *same* endpoint; the only thing
|
||||||
|
/// telling them apart is whether `pull_request` is present. Getting
|
||||||
|
/// this wrong mislabels every row `lint` prints.
|
||||||
|
#[test]
|
||||||
|
fn is_pr_is_decided_by_the_pull_request_object() {
|
||||||
|
let plain = trim_issue(&issue(json!({ "number": 7, "title": "t" })));
|
||||||
|
assert_eq!(plain["is_pr"], json!(false));
|
||||||
|
assert_eq!(kind_label(&plain), "issue");
|
||||||
|
|
||||||
|
let pr = trim_issue(&issue(json!({
|
||||||
|
"number": 8, "title": "t", "pull_request": { "merged": false },
|
||||||
|
})));
|
||||||
|
assert_eq!(pr["is_pr"], json!(true));
|
||||||
|
assert_eq!(kind_label(&pr), "pr");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn trim_issue_keeps_the_fields_the_output_prints() {
|
||||||
|
let v = trim_issue(&issue(json!({
|
||||||
|
"number": 42,
|
||||||
|
"title": "a title",
|
||||||
|
"assignees": [{ "login": "atlas" }, { "login": "argus" }],
|
||||||
|
})));
|
||||||
|
assert_eq!(num(&v), 42);
|
||||||
|
assert_eq!(title(&v), "a title");
|
||||||
|
assert_eq!(v["assignees"], json!(["atlas", "argus"]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn accessors_survive_a_row_that_is_missing_everything() {
|
||||||
|
let empty = json!({});
|
||||||
|
assert_eq!(num(&empty), 0);
|
||||||
|
assert_eq!(title(&empty), "");
|
||||||
|
assert_eq!(kind_label(&empty), "issue");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `All` is **absence** of the filter, not a third value — the forge
|
||||||
|
/// returns both kinds when `type` is omitted. Turning it into a value
|
||||||
|
/// would silently narrow every `--type all` query.
|
||||||
|
#[test]
|
||||||
|
fn the_both_kinds_filter_is_an_absent_query_param() {
|
||||||
|
assert!(Kind::All.query_type().is_none());
|
||||||
|
assert!(matches!(
|
||||||
|
Kind::Issues.query_type(),
|
||||||
|
Some(IssueListIssuesQueryType::Issues)
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
Kind::Pulls.query_type(),
|
||||||
|
Some(IssueListIssuesQueryType::Pulls)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn state_maps_onto_both_query_enums() {
|
||||||
|
assert!(matches!(
|
||||||
|
State::Open.issue_state(),
|
||||||
|
IssueListIssuesQueryState::Open
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
State::Closed.issue_state(),
|
||||||
|
IssueListIssuesQueryState::Closed
|
||||||
|
));
|
||||||
|
assert!(matches!(State::All.issue_state(), IssueListIssuesQueryState::All));
|
||||||
|
assert!(matches!(
|
||||||
|
State::Open.pull_state(),
|
||||||
|
RepoListPullRequestsQueryState::Open
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
State::Closed.pull_state(),
|
||||||
|
RepoListPullRequestsQueryState::Closed
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
State::All.pull_state(),
|
||||||
|
RepoListPullRequestsQueryState::All
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn title(it: &Value) -> &str {
|
fn title(it: &Value) -> &str {
|
||||||
it.get("title").and_then(Value::as_str).unwrap_or("")
|
it.get("title").and_then(Value::as_str).unwrap_or("")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue