~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
229 lines · 10.9 KB Raw History
//! 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"]);
}