johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
//! Opening pulls and issues from the pages, as the account logged in to
//! them: the templates they are asked to follow, what a branch would bring
//! in, and the writes, through the same code as the command line.
use super::*;
#[test]
fn opening_pulls_and_issues_from_the_pages() {
// Started first, so its index sees the repositories made after.
let server = gateway_with(None, true);
let owner = person(account());
let url = owner.url("open");
let work = owner.work("w", 1);
std::fs::create_dir_all(work.join(".knotless")).unwrap();
owner.commit_file(
&work,
".knotless/issue.md",
"## What\n\n<!-- What is wrong. -->\n",
);
owner.commit_file(&work, ".knotless/pull.md", "## Why\n");
owner.git(&work, &["push", "-q", &url, "main"]);
owner.git(&work, &["checkout", "-q", "-b", "feature"]);
owner.commit_file(&work, "g", "feature\n");
owner.git(&work, &["push", "-q", &url, "feature"]);
let fan = person(account());
assert_eq!(fan.cli(&["repo", "fork", &url, "open-too"], b"").0, 0);
let fork = fan.url("open-too");
let mine = fan.work("mine", 0);
fan.git(&mine, &["fetch", "-q", &fork, "main"]);
fan.git(&mine, &["checkout", "-q", "-b", "fix", "FETCH_HEAD"]);
fan.commit_file(&mine, "h", "fixed\n");
fan.git(&mine, &["push", "-q", &fork, "fix"]);
let repo = format!("at://{}/dev.knotless.repo/open", owner.account.did);
let forked = format!("at://{}/dev.knotless.repo/open-too", fan.account.did);
let xrpc = |nsid: &str| format!("{}/xrpc/dev.knotless.{nsid}", server.base);
let get = |nsid: &str, query: &str| {
let url = format!("{}?repo={repo}{query}", xrpc(nsid));
browse("GET", &url, None, None, None)
};
// What each kind is asked to follow.
let (status, _, issue) = get("getTemplate", "&kind=issue");
assert_eq!(status, 200, "{issue}");
assert_eq!(issue["template"], "## What\n\n<!-- What is wrong. -->\n");
assert_eq!(
get("getTemplate", "&kind=pull&base=main").2["template"],
"## Why\n"
);
assert_eq!(get("getTemplate", "&kind=pull&base=nope").2, json!({}));
assert_eq!(get("getTemplate", "&kind=wiki").0, 400);
// Where a pull can come from, and what a branch brings in.
wait_until(|| {
get("getCompare", "").2["heads"]
.as_array()
.map_or(0, Vec::len)
== 2
});
let heads = get("getCompare", "").2["heads"].clone();
assert_eq!(heads[0]["name"], "open");
assert_eq!(
(&heads[1]["did"], &heads[1]["name"]),
(&json!(fan.account.did), &json!("open-too"))
);
let (status, _, feature) = get("getCompare", "&base=main&branch=feature");
assert_eq!(status, 200, "{feature}");
assert_eq!(feature["changes"]["commits"].as_array().unwrap().len(), 1);
assert_eq!(feature["changes"]["files"][0]["new"], "g");
wait_until(|| get("getCompare", &format!("&head={forked}&branch=fix")).0 == 200);
let fix = get("getCompare", &format!("&head={forked}&branch=fix")).2;
assert_eq!(fix["changes"]["files"][0]["new"], "h");
assert_eq!(get("getCompare", "&branch=nope").0, 404);
// Opening them, as the account logged in, as the templates ask.
let set = log_in(&server, &fan.account, "");
let found = set.iter().find(|c| c.starts_with("knotless_session="));
let cookie = pair(found.unwrap()).to_string();
let me = Some(cookie.as_str());
let write = |nsid: &str, cookie: Option<&str>, body: Value| {
browse("POST", &xrpc(nsid), cookie, None, Some(body))
};
let bare = json!({"repo": repo, "title": "Broken", "body": "It is."});
let (status, _, refused) = write("createIssue", me, bare.clone());
assert_eq!(status, 400);
assert!(
refused["message"].as_str().unwrap().contains("## What"),
"{refused}"
);
assert_eq!(write("createIssue", None, bare).0, 401);
let asked = json!({"repo": repo, "title": "Broken", "body": "## What\n\nIt is."});
let (status, _, opened) = write("createIssue", me, asked);
assert_eq!(status, 200, "{opened}");
let issue = opened["uri"].as_str().unwrap().to_string();
assert!(issue.starts_with(&format!("at://{}/dev.knotless.issue/", fan.account.did)));
let thread = |uri: &str| {
let url = format!("{}?repo={repo}&uri={uri}", xrpc("getThread"));
browse("GET", &url, None, None, None).2
};
assert_eq!(thread(&issue)["item"]["title"], "Broken");
let pull = |head: &str, branch: &str| json!({"repo": repo, "base": "main", "head": head, "branch": branch, "title": "Fix it", "body": "## Why\n\nIt was broken."});
let (status, _, opened) = write("createPull", me, pull(&forked, "fix"));
assert_eq!(status, 200, "{opened}");
let shown = thread(opened["uri"].as_str().unwrap());
assert_eq!(shown["head"]["branch"], "fix");
assert_eq!(shown["head"]["repo"]["name"], "open-too");
assert_eq!(write("createPull", me, pull(&forked, "nope")).0, 400);
let nowhere = format!("at://{}/dev.knotless.repo/nothing", fan.account.did);
assert_eq!(write("createPull", me, pull(&nowhere, "fix")).0, 404);
// Reviewers asked for as it opens.
let mut asking = pull(&forked, "fix");
asking["reviewers"] = json!([{"did": owner.account.did}]);
let (status, _, opened) = write("createPull", me, asking);
assert_eq!(status, 200, "{opened}");
let asked_pull = opened["uri"].as_str().unwrap().to_string();
let shown = thread(&asked_pull);
assert_eq!(shown["item"]["reviewers"][0]["did"], owner.account.did);
// And asked for, or no longer, once it is open, as its author; an
// issue asks no one.
let them = json!([{"did": owner.account.did}]);
let stop = json!({"repo": repo, "subject": asked_pull, "removeReviewers": them});
let (status, _, stopped) = write("editItem", me, stop);
assert_eq!(status, 200, "{stopped}");
// None asked: the view leaves the list out.
let none = thread(&asked_pull)["item"]["reviewers"].clone();
assert!(none.as_array().is_none_or(Vec::is_empty), "{none}");
let again = json!({"repo": repo, "subject": asked_pull, "addReviewers": them});
assert_eq!(write("editItem", me, again).0, 200);
let shown = thread(&asked_pull);
assert_eq!(shown["item"]["reviewers"][0]["did"], owner.account.did);
let of_issue = json!({"repo": repo, "subject": issue, "addReviewers": them});
assert_eq!(write("editItem", me, of_issue).0, 400);
// Labels put on as it opens, by those who may put them on: the
// owner, whose pages offer them, and not the fan, who is refused
// before anything is written.
let add = ["label", "define", &url, "kind/bug"];
assert_eq!(owner.cli(&add, b"").0, 0);
let set = log_in(&server, &owner.account, "");
let found = set.iter().find(|c| c.starts_with("knotless_session="));
let owns = pair(found.unwrap()).to_string();
let offered = |cookie: &str| {
let url = format!("{}?repo={repo}", xrpc("getRepo"));
browse("GET", &url, Some(cookie), None, None).2["viewer"]["labels"].clone()
};
wait_until(|| offered(&owns)[0]["name"] == "kind/bug");
assert_eq!(offered(&cookie), Value::Null);
let labels = json!([{"name": "kind/bug"}]);
let body = "## What\n\nStill broken.";
let labelled = json!({"repo": repo, "title": "Broken", "body": body, "labels": labels});
let (status, _, refused) = write("createIssue", me, labelled.clone());
assert_eq!(status, 400);
assert!(
refused["message"].as_str().unwrap().contains("kind/bug"),
"{refused}"
);
let (status, _, opened) = write("createIssue", Some(&owns), labelled);
assert_eq!(status, 200, "{opened}");
let shown = thread(opened["uri"].as_str().unwrap());
assert_eq!(shown["item"]["labels"][0]["name"], "kind/bug");
let mut onto = pull(&forked, "fix");
onto["labels"] = labels;
let (status, _, opened) = write("createPull", Some(&owns), onto);
assert_eq!(status, 200, "{opened}");
let shown = thread(opened["uri"].as_str().unwrap());
assert_eq!(shown["item"]["labels"][0]["name"], "kind/bug");
// Assigned by the owner, who may, and not by the fan; and asked, by
// the owner too, to review the fan's pull.
let viewing = |cookie: &str, uri: &str| {
let url = format!("{}?repo={repo}&uri={uri}", xrpc("getThread"));
browse("GET", &url, Some(cookie), None, None).2["viewer"].clone()
};
assert_eq!(viewing(&owns, &issue)["assign"], true);
assert_eq!(viewing(&cookie, &issue)["assign"], Value::Null);
let fan_did = fan.account.did.as_str();
let assigning = json!({"repo": repo, "subject": issue, "did": fan_did});
assert_eq!(write("assignItem", me, assigning.clone()).0, 400);
let (status, _, assigned) = write("assignItem", Some(&owns), assigning);
assert_eq!(status, 200, "{assigned}");
assert_eq!(assigned["assignees"][0]["did"], fan_did);
assert_eq!(thread(&issue)["item"]["assignees"][0]["did"], fan_did);
let owner_did = owner.account.did.as_str();
let mut asking =
json!({"repo": repo, "subject": asked_pull, "did": owner_did, "role": "reviewer"});
let (status, _, asked) = write("assignItem", Some(&owns), asking.clone());
assert_eq!(status, 200, "{asked}");
assert_eq!(asked["reviewers"][0]["did"], owner_did);
// Its author asks in the pull itself.
let (status, _, asked) = write("assignItem", me, asking.clone());
assert_eq!(status, 200, "{asked}");
let mine = thread(&asked_pull)["item"]["reviewers"].clone();
assert_eq!(mine.as_array().map(Vec::len), Some(1), "{mine}");
asking["role"] = "boss".into();
assert_eq!(write("assignItem", Some(&owns), asking).0, 400);
// The owner asks for changes to the fan's pull, then dismisses that
// review, which the fan may not. main waits for the fan's review too,
// which the owner's is not.
let gated = [
"branch",
"protect",
&url,
"main",
"--gate",
&fan.account.handle,
];
assert_eq!(owner.cli(&gated, b"").0, 0);
assert_eq!(viewing(&owns, &asked_pull)["dismiss"], true);
assert_eq!(viewing(&owns, &issue)["dismiss"], Value::Null);
let review =
json!({"repo": repo, "subject": asked_pull, "body": "Not yet", "verdict": "changes"});
let (status, _, reviewed) = write("createComment", Some(&owns), review);
assert_eq!(status, 200, "{reviewed}");
let dismissing =
json!({"repo": repo, "pull": asked_pull, "reviewer": owner_did, "reason": "Moot"});
assert_eq!(write("dismissReview", me, dismissing.clone()).0, 400);
let (status, _, dismissed) = write("dismissReview", Some(&owns), dismissing);
assert_eq!(status, 200, "{dismissed}");
let shown = thread(&asked_pull);
assert_eq!(shown["reviews"][0]["dismissed"], true, "{shown}");
assert_eq!(shown["reviews"][0].get("gate"), None, "{shown}");
let said = shown["comments"].as_array().unwrap();
let dismissal = said.iter().find(|c| c["uri"] == dismissed["uri"]).unwrap();
assert_eq!(dismissal["dismisses"], reviewed["uri"]);
}