johnandersen777.bsky.social/knotless
git that lives in your Atmosphere account
Forked from chadtmiller.com/knotless
0
0
//! Owners delegate their writes and share repositories from the pages,
//! and make, share and land private ones there, through the same code as
//! the command line.
use super::*;
/// Runs the browser that approves as `who` on the page at `url`.
fn approve(who: &Account, url: &str) {
let command = format!("{} {url}", browser(who, ""));
let mut words = command.split_whitespace();
let out = std::process::Command::new(words.next().unwrap())
.args(words)
.output()
.unwrap();
assert!(
out.status.success(),
"{}",
String::from_utf8_lossy(&out.stderr)
);
}
/// An owner delegates their writes to the server from the pages, as
/// `knotless account delegate` does, and can then add a collaborator there; and a
/// pull into a repository with a runner but no workflows waits for no
/// checks.
#[test]
fn delegating_writes_from_the_pages() {
// Started first, so its index sees the repositories made after.
let server = gateway_with(None, true);
let owner = person(account());
let collab = account();
let url = owner.url("shared");
let work = owner.work("w", 1);
owner.git(&work, &["push", "-q", &url, "main"]);
owner.git(&work, &["checkout", "-q", "-b", "x"]);
owner.commit_file(&work, "x", "x\n");
owner.git(&work, &["push", "-q", &url, "x"]);
let (code, pull) = owner.cli(&["pull", "create", &url, "main", &url, "x", "X"], b"");
assert_eq!(code, 0, "{pull}");
let mine = session_cookie(&server, &owner.account);
let me = Some(mine.as_str());
let xrpc = |nsid: &str| format!("{}/xrpc/dev.knotless.{nsid}", server.base);
let write = |nsid: &str, cookie: Option<&str>, body: Value| {
browse("POST", &xrpc(nsid), cookie, None, Some(body))
};
let repo = format!("at://{}/dev.knotless.repo/shared", owner.account.did);
let ask = |nsid: &str, query: &str| {
let url = format!("{}?repo={repo}{query}", xrpc(nsid));
browse("GET", &url, me, None, None).2
};
// Not yet delegated, sharing for writing says to delegate first.
assert_eq!(ask("getRepoSettings", "").get("delegated"), None);
let share = json!({"repo": repo, "with": collab.handle});
let (code, _, said) = write("shareRepo", me, share.clone());
assert_eq!(code, 400, "{said}");
assert!(
said["message"]
.as_str()
.unwrap()
.contains("Delegate writes to this server"),
"{said}"
);
// Only someone logged in delegates; nothing waits under a state never
// given.
assert_eq!(write("delegateWrites", None, json!({})).0, 401);
let nope = json!({"state": "nope"});
assert_eq!(write("awaitDelegation", None, nope.clone()).0, 401);
assert_eq!(write("awaitDelegation", me, nope).0, 400);
// Started, approved at the PDS, and waited for.
let (code, _, started) = write("delegateWrites", me, json!({}));
assert_eq!(code, 200, "{started}");
approve(&owner.account, started["url"].as_str().unwrap());
let state = json!({"state": started["state"]});
let mut said = json!({"done": false});
while said["done"] == false {
let (code, _, now) = write("awaitDelegation", me, state.clone());
assert_eq!(code, 200, "{now}");
said = now;
}
assert_eq!(said["gateway"], server.did);
assert_eq!(ask("getRepoSettings", "")["delegated"], server.did);
// Now a collaborator is added, through this server.
let (code, _, said) = write("shareRepo", me, share);
assert_eq!(code, 200, "{said}");
assert_eq!(said["collaborators"][0]["did"], collab.did);
assert_eq!(ask("getRepoSettings", "")["gateway"], server.did);
// With a runner but no workflow a pull runs, nothing waits for checks.
let runner = json!({"repo": repo, "runner": collab.handle});
assert_eq!(write("addRunner", me, runner).0, 200);
let said = ask("getMergeStatus", &format!("&pull={pull}"));
assert_eq!(
(&said["state"], &said["checks"]),
(&json!("clean"), &json!("none")),
"{said}"
);
}
/// A private repository made from the pages, shared there for reading,
/// and its pull landed there, each read and written as the account
/// logged in, never as the server.
#[test]
fn private_repositories_made_and_shared_from_the_pages() {
let server = gateway_with(None, true);
let [owner, member] = people();
let mine = session_cookie(&server, &owner.account);
let theirs = session_cookie(&server, &member.account);
let (me, them) = (Some(mine.as_str()), Some(theirs.as_str()));
let xrpc = |nsid: &str| format!("{}/xrpc/dev.knotless.{nsid}", server.base);
let write = |nsid: &str, cookie: Option<&str>, body: Value| {
browse("POST", &xrpc(nsid), cookie, None, Some(body))
};
let made = write("createRepo", me, json!({"name": "secret", "private": true}));
if !net().spaces {
// Without spaces, there is no private repository to make.
assert_eq!(made.0, 400, "{}", made.2);
return;
}
assert_eq!(made.0, 200, "{}", made.2);
let (_, listed) = owner.cli(&["repo", "list", &owner.account.handle], b"");
let private = listed.lines().any(|l| l == "secret\t\tprivate");
assert!(private, "{listed}");
let url = owner.url("secret");
let work = owner.work("w", 1);
owner.git(&work, &["push", "-q", &url, "main"]);
owner.git(&work, &["checkout", "-q", "-b", "x"]);
let head = owner.commit_file(&work, "x", "x\n");
owner.git(&work, &["push", "-q", &url, "x"]);
let (code, pull) = owner.cli(&["pull", "create", &url, "main", &url, "x", "X"], b"");
assert_eq!(code, 0, "{pull}");
let repo = format!("at://{}/dev.knotless.repo/secret", owner.account.did);
let ask = |nsid: &str, query: &str, cookie: Option<&str>| {
let url = format!("{}?repo={repo}{query}", xrpc(nsid));
browse("GET", &url, cookie, None, None)
};
let readers = || ask("getRepoSettings", "", me).2["readers"].clone();
// Shared for reading, the member is listed to its owner, and reads it.
assert_eq!(readers(), json!([]));
assert_eq!(ask("getRepo", "", them).0, 404);
let share = json!({"repo": repo, "with": member.account.handle, "write": false});
let (code, _, said) = write("shareRepo", me, share);
assert_eq!((code, &said), (200, &json!({"collaborators": []})));
assert_eq!(readers()[0]["did"], member.account.did);
let (code, _, shown) = ask("getRepo", "", them);
assert_eq!((code, &shown["private"]), (200, &json!(true)), "{shown}");
// Only its owner sees who reads it.
assert_eq!(ask("getRepoSettings", "", them).2["readers"], json!([]));
// Its owner changes it, which no public index holds.
let described = json!({"repo": repo, "description": "Hush"});
assert_eq!(write("updateRepo", me, described).0, 200);
// Its pull's merge box, workflows and runners read as each asker.
let status = |cookie| ask("getMergeStatus", &format!("&pull={pull}"), cookie);
let (code, _, said) = status(them);
assert_eq!(code, 200, "{said}");
assert_eq!(
(&said["state"], &said["may"]["merge"]),
(&json!("clean"), &json!(false))
);
assert_eq!(status(me).2["may"]["merge"], true);
let listed = ask("listWorkflows", "", them);
assert_eq!(listed.0, 200, "{}", listed.2);
assert_eq!(ask("listRunners", "", them).2, json!({"runners": []}));
let one = json!({"repo": repo, "pull": pull});
assert_eq!(write("mergePull", them, one.clone()).0, 400);
let (code, _, merged) = write("mergePull", me, one);
assert_eq!(code, 200, "{merged}");
assert!(
merged["commit"].is_string() && merged["commit"] != json!(head),
"{merged}"
);
// Unshared, they read it no more.
let unshare = json!({"repo": repo, "with": member.account.did});
assert_eq!(write("unshareRepo", me, unshare).0, 200);
assert_eq!(readers(), json!([]));
}