Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/monetize-shop-pairing/src/sale_check/tests.rs

use super::*;
#[allow(unused_imports)]
use crate::test_fixtures::*;
use std::sync::Mutex;

const HOST: &str = "acme.test2.gunnar.rs";
const INVITE: &str = "0f1e2d3c4b5a69788796a5b4c3d2e1f00f1e2d3c4b5a69788796a5b4c3d2e1f0";
const BEARER: &str = "b7e1a0c9d8f7e6d5c4b3a29180f7e6d5c4b3a29180f7e6d5c4b3a29180f7e6d5";

/// **A sold front's console, faked** as gunnar-ui-server answers: its login page, its status,
/// the first-login door (a challenge for an invite-shaped `?invite=`), and its repositories
/// (to the console's bearer only). The default serves `alice/welcome`.
#[derive(Clone)]
struct Console {
    login: Option<(u16, String)>,
    passkey_enabled: bool,
    bootstrap_spent: bool,
    door: (u16, String),
    repos: Vec<&'static str>,
    truncated: bool,
    asked: Arc<Mutex<Vec<Asked>>>,
}

/// One request the console was asked: its path and query, and its bearer.
type Asked = (String, Option<String>);

impl Default for Console {
    fn default() -> Self {
        Console {
            login: Some((200, format!("<html><title>Gunnar — log in</title><script>fetch('{}')</script></html>", console::LOGIN_PAGE_MARK))),
            passkey_enabled: true,
            bootstrap_spent: false,
            door: (200, r#"{"publicKey":{"challenge":"c2FsZS1jaGVjaw","rp":{"name":"Gunnar"}},"ceremony":"x"}"#.into()),
            repos: vec!["alice/welcome"],
            truncated: false,
            asked: Arc::default(),
        }
    }
}

impl Front for Console {
    fn get(&self, path_and_query: &str, bearer: Option<&str>) -> Result<(u16, String), String> {
        self.asked
            .lock()
            .unwrap()
            .push((path_and_query.to_string(), bearer.map(str::to_string)));
        let (path, q) = path_and_query
            .split_once('?')
            .unwrap_or((path_and_query, ""));
        Ok(match path {
            console::LOGIN_PATH => return self.login.clone().ok_or_else(|| "connection refused".to_string()),
            console::AUTH_STATUS_PATH => (200, serde_json::json!({"passkey_enabled": self.passkey_enabled, "bootstrap_spent": self.bootstrap_spent}).to_string()),
            console::REGISTER_START_PATH => {
                let invite = q.split('&').find_map(|kv| kv.strip_prefix("invite=")).unwrap_or_default();
                if (16..=128).contains(&invite.len()) {
                    self.door.clone()
                } else {
                    (400, "that is not an invite this console can register a passkey with".into())
                }
            }
            console::REPOS_PATH if bearer == Some(BEARER) => {
                let items: Vec<_> = self
                    .repos
                    .iter()
                    .map(|p| {
                        let (ns, name) = p.split_once('/').unwrap();
                        serde_json::json!({"path": p, "namespace": ns, "name": name, "visibility": "private"})
                    })
                    .collect();
                (200, serde_json::json!({"items": items, "total": items.len(), "truncated": self.truncated}).to_string())
            }
            console::REPOS_PATH => (401, "unauthorized".into()),
            _ => (404, "not found".into()),
        })
    }
}

fn judged(console: &Console) -> Result<String, String> {
    judge(console, HOST, "alice", BEARER, INVITE)
}

/// ★ **S4**: a front that serves its login page, its door and exactly `alice/welcome` is
/// proven — and the check asked each door as the buyer and the console do: the login page and
/// the door with no bearer, the door with the mail's own invite for the buyer's account, the
/// repositories with the console's bearer.
#[test]
fn a_front_that_serves_what_was_sold_is_proven_by_its_public_doors() {
    let console = Console::default();
    let proven = judged(&console).expect("a serving front is proven");
    assert!(
        proven.contains("alice/welcome") && proven.contains(HOST),
        "{proven}"
    );
    let asked = console.asked.lock().unwrap().clone();
    let paths: Vec<&str> = asked
        .iter()
        .map(|(p, _)| p.split('?').next().unwrap())
        .collect();
    assert_eq!(
        paths,
        [
            console::LOGIN_PATH,
            console::AUTH_STATUS_PATH,
            console::REGISTER_START_PATH,
            console::REPOS_PATH
        ]
    );
    assert!(
        asked[..3].iter().all(|(_, b)| b.is_none()),
        "the public doors are asked as the buyer: no bearer"
    );
    assert_eq!(
        asked[2].0,
        format!(
            "{}?invite={INVITE}&user=alice",
            console::REGISTER_START_PATH
        ),
        "the mail's own link"
    );
    assert_eq!(
        asked[3],
        (
            format!("{}?limit={}", console::REPOS_PATH, console::REPOS_MAX),
            Some(BEARER.to_string())
        )
    );
}

/// ★ **S4**: no sale is proven by a front that only half serves — each case is refused and
/// says which door did not answer.
#[test]
fn a_front_missing_its_login_page_its_door_or_exactly_one_welcome_is_refused() {
    let d = Console::default;
    let cases: Vec<(&str, Console, &str)> = vec![
        (
            "login down",
            Console { login: None, ..d() },
            "the login page",
        ),
        (
            "login 502",
            Console {
                login: Some((502, "bad gateway".into())),
                ..d()
            },
            "HTTP 502",
        ),
        (
            "not the console",
            Console {
                login: Some((200, "<html>nginx welcome</html>".into())),
                ..d()
            },
            "does not hold /auth/passkey/login/start",
        ),
        (
            "no passkeys",
            Console {
                passkey_enabled: false,
                ..d()
            },
            "/passkey_enabled is false",
        ),
        (
            "door spent",
            Console {
                bootstrap_spent: true,
                ..d()
            },
            "spent",
        ),
        (
            "door 503",
            Console {
                door: (503, "gunnar is unavailable".into()),
                ..d()
            },
            "the first-login door",
        ),
        (
            "door no challenge",
            Console {
                door: (200, "{}".into()),
                ..d()
            },
            "no passkey challenge",
        ),
        (
            "no welcome",
            Console {
                repos: vec!["alice/other"],
                ..d()
            },
            "holds 0 items named \"welcome\"",
        ),
        (
            "two welcome",
            Console {
                repos: vec!["alice/welcome", "operator/welcome"],
                ..d()
            },
            "holds 2 items named \"welcome\"",
        ),
        (
            "another's welcome",
            Console {
                repos: vec!["bob/welcome"],
                ..d()
            },
            "not exactly alice/welcome",
        ),
        (
            "truncated",
            Console {
                truncated: true,
                ..d()
            },
            "/truncated is true",
        ),
    ];
    for (case, console, says) in cases {
        let why = judged(&console).expect_err(case);
        assert!(why.contains(says), "{case}: {why}");
        assert!(why.contains(HOST), "{case}: names the public front: {why}");
    }
    let why = judge(&Console::default(), HOST, "alice", "not-the-bearer", INVITE)
        .expect_err("a bearer the console refuses");
    assert!(why.contains("HTTP 401") && why.contains("welcome"), "{why}");
    assert!(
        judge(&Console::default(), HOST, " ", BEARER, INVITE).is_err(),
        "no account sold, nothing to prove"
    );
}

/// ★ The invite and the bearer are never in a word the check says, even from a front that
/// echoes what it was asked.
#[test]
fn the_check_never_says_the_invite_or_the_bearer() {
    struct Echo;
    impl Front for Echo {
        fn get(&self, p: &str, b: Option<&str>) -> Result<(u16, String), String> {
            if p.starts_with(console::LOGIN_PATH) || p.starts_with(console::AUTH_STATUS_PATH) {
                return Console::default().get(p, b);
            }
            Ok((418, format!("you asked {p} with {b:?}")))
        }
    }
    let why = judge(&Echo, HOST, "alice", BEARER, INVITE).unwrap_err();
    assert!(!why.contains(INVITE) && !why.contains(BEARER), "{why}");
    let why = judge(
        &Console {
            repos: vec![],
            ..Console::default()
        },
        HOST,
        "alice",
        BEARER,
        INVITE,
    )
    .unwrap_err();
    assert!(!why.contains(INVITE) && !why.contains(BEARER), "{why}");
}

#[test]
fn an_answer_is_read_whole_or_refused() {
    assert_eq!(
        parse_answer(b"HTTP/1.1 200 OK\r\ncontent-length: 5\r\n\r\nhello").unwrap(),
        (200, "hello".into())
    );
    assert_eq!(parse_answer(b"HTTP/1.1 404 Not Found\r\nTransfer-Encoding: chunked\r\n\r\n3\r\nabc\r\n2;x=y\r\nde\r\n0\r\n\r\n").unwrap(), (404, "abcde".into()));
    assert!(
        parse_answer(b"HTTP/1.1 200 OK\r\ncontent-length: 9\r\n\r\nhello").is_err(),
        "cut short"
    );
    assert!(
        parse_answer(b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n5\r\nab").is_err(),
        "cut short"
    );
    assert!(parse_answer(b"SSH-2.0-OpenSSH\r\n\r\n").is_err());
}

// ── over real HTTPS, in this process ───────────────────────────────────

/// **The console behind TLS** for `HOST` with `cert`/`key`, on a loopback port; each request
/// (its path and bearer, from its own head) answered by `console`.
fn serve(console: Console, cert: &str, key: &str) -> SocketAddr {
    use rustls::pki_types::pem::PemObject as _;
    use rustls::pki_types::{CertificateDer, PrivateKeyDer};
    let certs = CertificateDer::pem_slice_iter(cert.as_bytes())
        .collect::<Result<Vec<_>, _>>()
        .unwrap();
    let key = PrivateKeyDer::from_pem_slice(key.as_bytes()).unwrap();
    let cfg = Arc::new(
        rustls::ServerConfig::builder_with_provider(Arc::new(
            rustls::crypto::ring::default_provider(),
        ))
        .with_safe_default_protocol_versions()
        .unwrap()
        .with_no_client_auth()
        .with_single_cert(certs, key)
        .unwrap(),
    );
    let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
    let addr = listener.local_addr().unwrap();
    std::thread::spawn(move || {
        for tcp in listener.incoming().flatten() {
            let (cfg, console) = (Arc::clone(&cfg), console.clone());
            std::thread::spawn(move || {
                let mut tls =
                    rustls::StreamOwned::new(rustls::ServerConnection::new(cfg).unwrap(), tcp);
                let mut head = Vec::new();
                let mut b = [0u8; 1];
                while !head.ends_with(b"\r\n\r\n") {
                    match tls.read(&mut b) {
                        Ok(1) => head.push(b[0]),
                        _ => return,
                    }
                }
                let head = String::from_utf8_lossy(&head).to_string();
                let path = head
                    .split_whitespace()
                    .nth(1)
                    .unwrap_or_default()
                    .to_string();
                let host_ok = head
                    .lines()
                    .any(|l| l.eq_ignore_ascii_case(&format!("host: {HOST}")));
                let bearer = head
                    .lines()
                    .find_map(|l| l.strip_prefix("Authorization: Bearer "))
                    .map(|s| s.trim().to_string());
                let (status, body) = if host_ok {
                    console
                        .get(&path, bearer.as_deref())
                        .unwrap_or((502, String::new()))
                } else {
                    (421, "misdirected".into())
                };
                let _ = write!(tls, "HTTP/1.1 {status} X\r\ncontent-type: text/plain\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{body}", body.len());
                tls.conn.send_close_notify();
                let _ = tls.flush();
            });
        }
    });
    addr
}

/// ★ **S4, the real client**: the shop reaches the sold front over HTTPS, trusting the
/// certificate it minted for that front (kept at the install, [`keep_front_cert`]) — and
/// proves the sale; a front whose welcome repository is missing is refused over the wire too.
#[test]
fn the_shop_proves_the_sale_over_https_trusting_the_certificate_it_minted() {
    let (cert, key) = crate::self_signed(&[HOST.to_string()]).unwrap();
    let run = tempfile::tempdir().unwrap();
    let feed = std::collections::BTreeMap::from([
        (format!("tls.{HOST}.cert"), cert.clone()),
        (format!("tls.{HOST}.key"), key.clone()),
    ]);
    keep_front_cert(run.path(), HOST, &feed).unwrap();
    assert_eq!(
        std::fs::read_to_string(run.path().join(FRONT_CERT)).unwrap(),
        cert,
        "the certificate alone is kept"
    );
    assert!(!std::fs::read_to_string(run.path().join(FRONT_CERT))
        .unwrap()
        .contains("PRIVATE KEY"));

    let serving = serve(Console::default(), &cert, &key);
    let facts = monetize_server::testing::lifecycles::gunnar_facts();
    let at = SaleCheckAt {
        facts: &facts,
        company: "acme",
        host: HOST,
        account: "alice",
        front: None,
        run_dir: run.path(),
        console_token: BEARER,
        first_login: INVITE,
    };
    let proven = check_reaching(&at, &|_| Ok(serving)).expect("the sale is proven over HTTPS");
    assert!(proven.contains("alice/welcome"), "{proven}");

    let no_welcome = serve(
        Console {
            repos: vec![],
            ..Console::default()
        },
        &cert,
        &key,
    );
    let why = check_reaching(&at, &|_| Ok(no_welcome)).expect_err("no welcome repository, no sale");
    assert!(why.contains("holds 0 items"), "{why}");
}

/// **The console behind TLS at its canonical www name** (WWW-CANON, edda estate-front 0.3.3): a request
/// to the bare `HOST` is a 301 (GET/HEAD) or a 308 (any other method) to `https://www.HOST` with the same
/// path; the console answers at `www.HOST` alone. Every head is kept in `seen`.
fn serve_www(console: Console, cert: &str, key: &str, seen: Arc<Mutex<Vec<String>>>) -> SocketAddr {
    use rustls::pki_types::pem::PemObject as _;
    use rustls::pki_types::{CertificateDer, PrivateKeyDer};
    let certs = CertificateDer::pem_slice_iter(cert.as_bytes())
        .collect::<Result<Vec<_>, _>>()
        .unwrap();
    let key = PrivateKeyDer::from_pem_slice(key.as_bytes()).unwrap();
    let cfg = Arc::new(
        rustls::ServerConfig::builder_with_provider(Arc::new(
            rustls::crypto::ring::default_provider(),
        ))
        .with_safe_default_protocol_versions()
        .unwrap()
        .with_no_client_auth()
        .with_single_cert(certs, key)
        .unwrap(),
    );
    let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
    let addr = listener.local_addr().unwrap();
    std::thread::spawn(move || {
        for tcp in listener.incoming().flatten() {
            let (cfg, console, seen) = (Arc::clone(&cfg), console.clone(), Arc::clone(&seen));
            std::thread::spawn(move || {
                let mut tls =
                    rustls::StreamOwned::new(rustls::ServerConnection::new(cfg).unwrap(), tcp);
                let mut head = Vec::new();
                let mut b = [0u8; 1];
                while !head.ends_with(b"\r\n\r\n") {
                    match tls.read(&mut b) {
                        Ok(1) => head.push(b[0]),
                        _ => return,
                    }
                }
                let head = String::from_utf8_lossy(&head).to_string();
                seen.lock().unwrap().push(head.clone());
                let method = head
                    .split_whitespace()
                    .next()
                    .unwrap_or_default()
                    .to_string();
                let path = head
                    .split_whitespace()
                    .nth(1)
                    .unwrap_or_default()
                    .to_string();
                let www = head
                    .lines()
                    .any(|l| l.eq_ignore_ascii_case(&format!("host: www.{HOST}")));
                let bare = head
                    .lines()
                    .any(|l| l.eq_ignore_ascii_case(&format!("host: {HOST}")));
                let bearer = head
                    .lines()
                    .find_map(|l| l.strip_prefix("Authorization: Bearer "))
                    .map(|s| s.trim().to_string());
                let answer = if bare {
                    let code = if matches!(method.as_str(), "GET" | "HEAD") {
                        301
                    } else {
                        308
                    };
                    format!("HTTP/1.1 {code} X\r\nlocation: https://www.{HOST}{path}\r\ncontent-length: 0\r\nconnection: close\r\n\r\n")
                } else {
                    let (status, body) = if www {
                        console
                            .get(&path, bearer.as_deref())
                            .unwrap_or((502, String::new()))
                    } else {
                        (421, "misdirected".into())
                    };
                    format!("HTTP/1.1 {status} X\r\ncontent-type: text/plain\r\ncontent-length: {}\r\nconnection: close\r\n\r\n{body}", body.len())
                };
                let _ = tls.write_all(answer.as_bytes());
                tls.conn.send_close_notify();
                let _ = tls.flush();
            });
        }
    });
    addr
}

/// ★ **WWW-CANON: the sale check follows the bare host's one hop to its www name** (HOL-PRODUCTS'
/// gate 2026-10-31: test3.holger.rs stayed "installing (paired)" for 90 min, its sale check got
/// the front's 301). A 301/308 from `https://<host>` to `https://www.<host>` with the same path is
/// followed once, under the www name; any other redirect is the answer, as before.
#[test]
fn the_sale_check_follows_the_bare_hosts_one_hop_to_www() {
    let (cert, key) = crate::self_signed(&[HOST.to_string(), format!("www.{HOST}")]).unwrap();
    let seen = Arc::new(Mutex::new(Vec::new()));
    let addr = serve_www(Console::default(), &cert, &key, Arc::clone(&seen));
    let front = Https::new(HOST, addr, Some(cert.as_bytes())).unwrap();
    let proven = judge(&front, HOST, "alice", BEARER, INVITE).expect("the sale is proven at www");
    assert!(proven.contains("alice/welcome"), "{proven}");
    let heads = seen.lock().unwrap().clone();
    assert!(
        heads
            .iter()
            .any(|h| h.contains(&format!("Host: www.{HOST}\r\n"))),
        "asked at www: {heads:?}"
    );
    // A POST keeps its method and body through the 308.
    assert_eq!(front.post("/x", "{}", None).unwrap().0, 404);
    assert!(
        seen.lock()
            .unwrap()
            .iter()
            .any(|h| h.starts_with("POST /x HTTP/1.1")
                && h.contains(&format!("Host: www.{HOST}\r\n")))
    );
    // A redirect anywhere else is the answer itself.
    assert!(super::www_hop(
        HOST,
        "/a",
        301,
        Some(&format!("https://www.{HOST}/a"))
    ));
    assert!(!super::www_hop(
        HOST,
        "/a",
        302,
        Some(&format!("https://www.{HOST}/a"))
    ));
    assert!(!super::www_hop(
        HOST,
        "/a",
        301,
        Some("https://evil.example/a")
    ));
    assert!(!super::www_hop(
        HOST,
        "/a",
        301,
        Some(&format!("https://www.{HOST}/b"))
    ));
    assert!(!super::www_hop(
        &format!("www.{HOST}"),
        "/a",
        301,
        Some(&format!("https://www.www.{HOST}/a"))
    ));
}

/// ★ A front the shop did not mint for and no root vouches for is not believed; nor is an
/// address that does not answer.
#[test]
fn a_front_with_a_certificate_nobody_vouches_for_is_not_believed() {
    let (cert, key) = crate::self_signed(&[HOST.to_string()]).unwrap();
    let (other, _) = crate::self_signed(&[HOST.to_string()]).unwrap();
    let addr = serve(Console::default(), &cert, &key);
    let unpinned = Https::new(HOST, addr, None).unwrap();
    let why = judge(&unpinned, HOST, "alice", BEARER, INVITE).expect_err("no root, no pin");
    assert!(
        why.contains("the login page") && why.to_lowercase().contains("certificate"),
        "{why}"
    );
    let wrong = Https::new(HOST, addr, Some(other.as_bytes())).unwrap();
    assert!(
        judge(&wrong, HOST, "alice", BEARER, INVITE).is_err(),
        "another certificate pinned"
    );
    let shut = std::net::TcpListener::bind("127.0.0.1:0")
        .unwrap()
        .local_addr()
        .unwrap();
    let nobody = Https::new(HOST, shut, Some(cert.as_bytes())).unwrap();
    assert!(
        judge(&nobody, HOST, "alice", BEARER, INVITE).is_err(),
        "nobody answers"
    );

    // No certificate minted for the front: none kept, an old one taken away.
    let run = tempfile::tempdir().unwrap();
    std::fs::write(run.path().join(FRONT_CERT), &cert).unwrap();
    keep_front_cert(run.path(), HOST, &Default::default()).unwrap();
    assert!(!run.path().join(FRONT_CERT).exists());
}

/// ★ **S4: the shop's installer proves the sale itself** — `ShopPairing` answers the shop's
/// sale check (the seam's default proves nothing and captures nothing), here at an address
/// nothing listens on: refused, and it says the public front.
#[test]
fn the_shops_pairing_answers_the_sale_check_from_the_public_front() {
    use monetize_server::estate_order::Installer as _;
    let run = tempfile::tempdir().unwrap();
    let shop = crate::ShopPairing {
        sale_feed: Default::default(),
        budget_s: 600,
        fleet_late_s: 0,
        installer_link_psk: true,
    };
    let facts = monetize_server::testing::lifecycles::gunnar_facts();
    let at = SaleCheckAt {
        facts: &facts,
        company: "acme",
        host: "acme.invalid",
        account: "alice",
        front: None,
        run_dir: run.path(),
        console_token: BEARER,
        first_login: INVITE,
    };
    let why = shop
        .sale_check(&at)
        .expect_err("nothing serves acme.invalid");
    assert!(
        why.contains("acme.invalid") && !why.contains("proves no sale check"),
        "{why}"
    );
}

/// ★ **The first-admin door** (owner 2026-10-22, `site first-link`): open to the custody's
/// invite, spent once the first admin signed in, closed when the invite no longer opens it.
#[test]
fn the_first_admin_door_is_open_spent_or_closed() {
    use monetize_server::estate_order::FirstDoor;
    assert_eq!(
        first_door_for(&gunnar(), &Console::default(), HOST, "alice", INVITE),
        Ok(FirstDoor::Open)
    );
    let spent = Console {
        bootstrap_spent: true,
        ..Console::default()
    };
    assert_eq!(
        first_door_for(&gunnar(), &spent, HOST, "alice", INVITE),
        Ok(FirstDoor::Spent)
    );
    let closed = Console {
        door: (401, "this invite has expired".into()),
        ..Console::default()
    };
    match first_door_for(&gunnar(), &closed, HOST, "alice", INVITE) {
        Ok(FirstDoor::Closed(why)) => {
            assert!(why.contains("HTTP 401") && !why.contains(INVITE), "{why}")
        }
        other => panic!("{other:?}"),
    }
    let down = Console {
        passkey_enabled: false,
        ..Console::default()
    };
    assert!(matches!(
        first_door_for(&gunnar(), &down, HOST, "alice", INVITE),
        Ok(FirstDoor::Closed(_))
    ));
}

/// **A front that takes the request and then drips its answer**, one byte every 100 ms,
/// for as long as anyone reads: every read is answered within [`PATIENCE`], the answer
/// never ends.
fn dripping(cert: &str, key: &str) -> SocketAddr {
    use rustls::pki_types::pem::PemObject as _;
    use rustls::pki_types::{CertificateDer, PrivateKeyDer};
    let certs = CertificateDer::pem_slice_iter(cert.as_bytes())
        .collect::<Result<Vec<_>, _>>()
        .unwrap();
    let key = PrivateKeyDer::from_pem_slice(key.as_bytes()).unwrap();
    let cfg = Arc::new(
        rustls::ServerConfig::builder_with_provider(Arc::new(
            rustls::crypto::ring::default_provider(),
        ))
        .with_safe_default_protocol_versions()
        .unwrap()
        .with_no_client_auth()
        .with_single_cert(certs, key)
        .unwrap(),
    );
    let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
    let addr = listener.local_addr().unwrap();
    std::thread::spawn(move || {
        for tcp in listener.incoming().flatten() {
            let cfg = Arc::clone(&cfg);
            std::thread::spawn(move || {
                let mut tls =
                    rustls::StreamOwned::new(rustls::ServerConnection::new(cfg).unwrap(), tcp);
                let mut head = Vec::new();
                let mut b = [0u8; 1];
                while !head.ends_with(b"\r\n\r\n") {
                    match tls.read(&mut b) {
                        Ok(1) => head.push(b[0]),
                        _ => return,
                    }
                }
                if write!(
                    tls,
                    "HTTP/1.1 200 X\r\ncontent-type: text/plain\r\ncontent-length: 1000000\r\n\r\n"
                )
                .and_then(|()| tls.flush())
                .is_err()
                {
                    return;
                }
                while tls.write_all(b"a").and_then(|()| tls.flush()).is_ok() {
                    std::thread::sleep(std::time::Duration::from_millis(100));
                }
            });
        }
    });
    addr
}

/// ★ A22 #6 (F-T-09): **a front that drips its answer cannot hold the sale check**: each
/// request has a total deadline, not only a deadline per read, and the check says so.
#[test]
fn a_front_that_drips_its_answer_is_refused_within_the_deadline() {
    let (cert, key) = crate::self_signed(&[HOST.to_string()]).unwrap();
    let addr = dripping(&cert, &key);
    let mut front = Https::new(HOST, addr, Some(cert.as_bytes())).unwrap();
    assert_eq!(front.within, ANSWER_WITHIN);
    front.within = std::time::Duration::from_secs(1);
    let started = std::time::Instant::now();
    let (tx, rx) = std::sync::mpsc::channel();
    std::thread::spawn(move || {
        let _ = tx.send(front.get("/login", None));
    });
    let got = rx
        .recv_timeout(std::time::Duration::from_secs(45))
        .expect("the request ends within its deadline, not when the front stops dripping");
    let why = got.expect_err("a dripped answer is no answer");
    assert!(
        why.contains("did not answer GET /login within 1 s"),
        "{why}"
    );
    assert!(
        started.elapsed() < std::time::Duration::from_secs(5),
        "{:?}",
        started.elapsed()
    );
}

/// The exchange's deadline grows with the body it sends: 32 MiB at a 256 KiB/s floor.
#[test]
fn a_large_body_has_the_time_to_be_sent() {
    let w = std::time::Duration::from_secs(30);
    assert_eq!(within_for(w, 0), w);
    assert_eq!(within_for(w, 100), w);
    assert_eq!(
        within_for(w, 32 << 20),
        w + std::time::Duration::from_secs(128)
    );
}