git-ents.gitmain
⌘K
foforge
login.rs117 lines · 4.7 KB · rusthistorycomment on this file
1//! End-to-end coverage of `git ents login` (`roots.web-signin`) against
2//! the real hosted-shaped web state served on a real loopback socket:
3//! the same `ents_web::router` the Fly deployment proxies to, the same
4//! `commands::login::run` a member's own machine executes. This doubles
5//! as the hosted-mount integration test — `build_hosted_state` wired and
6//! served end to end, no container needed.
7#![allow(clippy::expect_used, reason = "integration test")]
8
9use std::path::Path;
10use std::process::Command;
11
12use git_ents::root::LocalRoot;
13
14/// A bare repository with `username`'s freshly-written key enrolled,
15/// returning the key path.
16fn enrolled_bare(dir: &Path, username: &str, seed: u8) -> std::path::PathBuf {
17 let bare = dir.join("repo.git");
18 let output = Command::new("git")
19 .args(["init", "--bare"])
20 .arg(&bare)
21 .output()
22 .expect("git runs");
23 assert!(output.status.success(), "{output:?}");
24
25 use ssh_key::private::{Ed25519Keypair, KeypairData};
26 let key_path = dir.join(format!("key_{username}"));
27 let pair = Ed25519Keypair::from_seed(&[seed; 32]);
28 let key = ssh_key::PrivateKey::new(KeypairData::from(pair), username).expect("well-formed");
29 key.write_openssh_file(&key_path, ssh_key::LineEnding::LF)
30 .expect("writes");
31
32 let local = LocalRoot::open(&bare).expect("opens");
33 git_ents::commands::members::add(&local, username, None, Some(key_path.clone()))
34 .expect("enrolls");
35 key_path
36}
37
38// @relation(roots.web-signin, roots.single-node-hosted, scope=function, role=Verifies)
39#[tokio::test(flavor = "multi_thread")]
40async fn git_ents_login_signs_a_hosted_browser_session_in() {
41 let dir = tempfile::tempdir().expect("tempdir");
42 let server_key = enrolled_bare(dir.path(), "server", 42);
43 // Enroll the human member too, with their own distinct key.
44 use ssh_key::private::{Ed25519Keypair, KeypairData};
45 let member_key = dir.path().join("key_joey");
46 let pair = Ed25519Keypair::from_seed(&[7; 32]);
47 let key = ssh_key::PrivateKey::new(KeypairData::from(pair), "joey").expect("well-formed");
48 key.write_openssh_file(&member_key, ssh_key::LineEnding::LF)
49 .expect("writes");
50 let bare = dir.path().join("repo.git");
51 let local = LocalRoot::open(&bare).expect("opens");
52 git_ents::commands::members::add(&local, "joey", None, Some(member_key.clone()))
53 .expect("enrolls");
54
55 // Bind first so the realm's host names the real ephemeral port —
56 // `git ents login` refuses a host disagreement by design.
57 let listener = ents_web::bind("127.0.0.1:0".parse().expect("addr"))
58 .await
59 .expect("binds");
60 let host = listener.local_addr().expect("bound").to_string();
61 let root = git_ents::root::HostedRoot::open(&bare).expect("opens");
62 let state = git_ents::commands::serve::build_hosted_state(root, server_key, host.clone())
63 .expect("boots");
64 tokio::spawn(ents_web::serve_on(listener, state));
65
66 let url = format!("http://{host}");
67 let outcome = tokio::task::spawn_blocking(move || {
68 // The browser half: GET /login mints a session and displays the
69 // one-time code.
70 let agent = ureq::agent();
71 let mut page = agent
72 .get(format!("{url}/login"))
73 .call()
74 .expect("login page");
75 let cookie = page
76 .headers()
77 .get("set-cookie")
78 .expect("a fresh session cookie")
79 .to_str()
80 .expect("ascii")
81 .split(';')
82 .next()
83 .expect("cookie pair")
84 .to_owned();
85 let body = page.body_mut().read_to_string().expect("html");
86 let code: String = body
87 .split(&format!("{host} "))
88 .nth(1)
89 .expect("the page displays the login command")
90 .chars()
91 .take(9)
92 .collect();
93
94 // The CLI half: the real command, against the real socket.
95 let mut out = Vec::new();
96 git_ents::commands::login::run(&url, &code, Some(member_key), &mut out).expect("signs in");
97 let printed = String::from_utf8(out).expect("utf8");
98 assert!(
99 printed.contains("authenticated as joey"),
100 "reports the member: {printed}"
101 );
102
103 // The browser half again: the same session now reads signed in.
104 let mut page = agent
105 .get(format!("{url}/login"))
106 .header("cookie", &cookie)
107 .call()
108 .expect("login page");
109 let body = page.body_mut().read_to_string().expect("html");
110 assert!(
111 body.contains("Signed in as") && body.contains("joey"),
112 "the browser session is authenticated: {body}"
113 );
114 })
115 .await;
116 outcome.expect("blocking half succeeds");
117}