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)
|
||||
}
|
||||
|
||||
#[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 {
|
||||
it.get("title").and_then(Value::as_str).unwrap_or("")
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue