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

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
4304 lines · 168.7 KB Raw History
//! What the web pages show, as knotless-serve answers it in XRPC queries,
//! over a reference PDS with an index following its firehose; and the
//! pages themselves, handed to the app that draws them.

#[path = "../../knotless/tests/common/mod.rs"]
mod common;
use common::*;

use knotless_serve::code::Code;
use knotless_serve::index::Index;
use knotless_serve::{App, router};
use reqwest::blocking::{Client, Response};
use serde_json::Value;
use std::sync::Arc;
use std::time::Duration;

struct Site {
    base: String,
    app: Arc<App>,
    _rt: tokio::runtime::Runtime,
    _sandbox: Sandbox,
}

fn serve(indexed: bool) -> Site {
    serve_as(Sandbox::new(), indexed)
}

/// A server reading as `s`.
fn serve_as(s: Sandbox, indexed: bool) -> Site {
    serve_with(s, indexed, false)
}

/// A server reading as `s`, searching code too if `code`, though not
/// yet indexing it ([`knotless_serve::code::follow`]).
fn serve_with(s: Sandbox, indexed: bool, code: bool) -> Site {
    let rt = tokio::runtime::Runtime::new().unwrap();
    let listener = rt
        .block_on(tokio::net::TcpListener::bind("127.0.0.1:0"))
        .unwrap();
    let base = format!("http://{}", listener.local_addr().unwrap());
    let index = indexed.then(|| Index::open(&s.path("index.db")).unwrap());
    let mut app: Arc<App> = App::build(ctx(&s), Duration::ZERO, None, index);
    if code {
        let started = rt.block_on(Code::start(&s.path("code"))).unwrap();
        Arc::get_mut(&mut app).unwrap().code = Some(Arc::new(started));
    }
    let router = router(app.clone());
    rt.spawn(async move { axum::serve(listener, router).await.unwrap() });
    if indexed {
        rt.spawn(knotless_serve::firehose::watch(
            app.clone(),
            net().pds.replace("http://", "ws://"),
        ));
    }
    Site {
        base,
        app,
        _rt: rt,
        _sandbox: s,
    }
}

impl Site {
    fn get(&self, path: &str) -> Response {
        Client::new()
            .get(format!("{}{path}", self.base))
            .send()
            .unwrap()
    }

    /// The answer at `path`, which must be there. An XRPC answer is kept
    /// too, where the run names a directory for them, for harness/
    /// answers.mjs to hold to its lexicon.
    fn json(&self, path: &str) -> Value {
        let res = self.get(path);
        assert_eq!(res.status().as_u16(), 200, "{path}");
        let answer: Value = res.json().unwrap();
        if let Some(dir) = std::env::var_os("KNOTLESS_ANSWERS")
            && path.starts_with("/xrpc/")
        {
            keep(&dir, path, &answer);
        }
        answer
    }

    /// What the page at `path` shows.
    fn data(&self, path: &str) -> Value {
        let mut shown = self.json(&at(path));
        if let Some(repo) = repo_query(path) {
            let (uri, _) = repo;
            shown["repo"] = self.json(&xrpc("getRepo", &[("repo", &uri)]));
        }
        shown
    }

    /// What the account `actor`'s page shows: who it is, as
    /// `dev.knotless.getProfile` says, and its repositories, as
    /// `dev.knotless.getActorRepos` does.
    fn profile(&self, actor: &str) -> Value {
        let mut shown = self.json(&format!("/xrpc/dev.knotless.getProfile?actor={actor}"));
        let held = self.json(&format!("/xrpc/dev.knotless.getActorRepos?actor={actor}"));
        shown["repos"] = held["repos"].clone();
        shown
    }

    /// The repositories the index finds for `q`.
    fn search(&self, q: &str) -> Value {
        self.json(&format!("/xrpc/dev.knotless.listRepos?q={q}"))
    }

    /// What each of `actors`, by DID, did, back to `since`, page by page.
    /// Other tests write meanwhile, and pushes close together are one
    /// event only within a page, so the feed of all would cut them apart
    /// where its pages end.
    fn feed(&self, since: &str, actors: &[&str]) -> Value {
        let mut events = Vec::new();
        for actor in actors {
            let mut cursor = String::new();
            loop {
                let page = self.json(&format!(
                    "/xrpc/dev.knotless.getFeed?limit=100&actor={actor}&cursor={}",
                    cursor.replace(' ', "%20")
                ));
                let shown = page["events"].as_array().unwrap();
                events.extend(shown.iter().cloned());
                let last = shown.last().map(|e| e["createdAt"].as_str().unwrap());
                match page["cursor"].as_str() {
                    Some(next) if last.is_some_and(|at| at >= since) => cursor = next.into(),
                    _ => break,
                }
            }
        }
        Value::Array(events)
    }

    /// What the procedure `nsid` of `dev.knotless`'s answers `input` with,
    /// and its status. An answer is kept as `json` keeps one.
    fn post(&self, nsid: &str, input: Value) -> (u16, Value) {
        let path = format!("/xrpc/dev.knotless.{nsid}");
        let res = Client::new()
            .post(format!("{}{path}", self.base))
            .json(&input)
            .send()
            .unwrap();
        let status = res.status().as_u16();
        let answer: Value = res.json().unwrap();
        if let Some(dir) = std::env::var_os("KNOTLESS_ANSWERS")
            && status == 200
        {
            keep(&dir, &path, &answer);
        }
        (status, answer)
    }

    fn status(&self, path: &str) -> (u16, String) {
        let res = self.get(path);
        (res.status().as_u16(), res.text().unwrap())
    }
}

/// A card's PNG, which must be there, with its cache control.
fn card(site: &Site, path: &str) -> (Vec<u8>, String) {
    let res = site.get(path);
    assert_eq!(res.status().as_u16(), 200, "{path}");
    assert_eq!(res.headers()["content-type"], "image/png");
    let cache = res.headers()["cache-control"].to_str().unwrap().to_string();
    let png = res.bytes().unwrap().to_vec();
    assert_eq!(&png[..8], b"\x89PNG\r\n\x1a\n");
    // The width and height, from the header chunk.
    assert_eq!(png[16..24], [0, 0, 4, 176, 0, 0, 2, 118]);
    (png, cache)
}

/// The XRPC query `nsid` of `dev.knotless`'s, given `params`.
/// Adds the answer at `path` to this process's file of them under `dir`,
/// a line at a time, as the tests' threads answer.
fn keep(dir: &std::ffi::OsStr, path: &str, answer: &Value) {
    use std::io::Write;
    static FILE: std::sync::Mutex<()> = std::sync::Mutex::new(());
    let _held = FILE.lock().unwrap();
    std::fs::create_dir_all(dir).unwrap();
    let file = std::path::Path::new(dir).join(format!("{}.jsonl", std::process::id()));
    let mut out = std::fs::OpenOptions::new()
        .create(true)
        .append(true)
        .open(file)
        .unwrap();
    let line = serde_json::json!({ "path": path, "body": answer });
    writeln!(out, "{line}").unwrap();
}

fn xrpc(nsid: &str, params: &[(&str, &str)]) -> String {
    let url =
        reqwest::Url::parse_with_params(&format!("http://x/xrpc/dev.knotless.{nsid}"), params);
    let url = url.unwrap();
    format!("{}?{}", url.path(), url.query().unwrap_or_default())
}

/// For the page of a repository at `page`, `/<owner>/<name>` and what
/// follows, the repository's record, by its owner as the page names it,
/// and the query that page asks: its tree, a file, its commits or one,
/// its branches, its pulls or issues, one's thread, its merge queue, its
/// runs, a check, or its runners. None for any other page.
fn repo_query(page: &str) -> Option<(String, String)> {
    let (path, query) = page.split_once('?').unwrap_or((page, ""));
    let asked: Vec<(&str, &str)> = query.split('&').filter_map(|p| p.split_once('=')).collect();
    let mut parts = path.trim_start_matches('/').splitn(4, '/');
    let (owner, name) = (parts.next()?, parts.next()?);
    let (kind, rest) = (parts.next(), parts.next());
    let mut item = rest.into_iter().flat_map(|r| r.splitn(2, '/'));
    let uri = format!("at://{owner}/dev.knotless.repo/{name}");
    let repo = ("repo", uri.as_str());
    let with = |nsid: &str, params: &[(&str, &str)]| xrpc(nsid, &[params, &asked].concat());
    let query = match (kind, rest, (item.next(), item.next())) {
        (None, ..) => xrpc("getTree", &[repo]),
        (Some("tree"), Some(r), _) => xrpc("getTree", &[repo, ("ref", r)]),
        (Some("blob"), Some(r), _) => xrpc("getFile", &[repo, ("ref", r)]),
        (Some("commits"), Some(r), _) => with("listCommits", &[repo, ("ref", r)]),
        (Some("commit"), Some(sha), _) => {
            xrpc("getCommit", &[repo, ("sha", sha), ("firstRows", "true")])
        }
        (Some(kind @ ("pulls" | "issues")), None, _) => {
            with("listItems", &[repo, ("kind", kind.trim_end_matches('s'))])
        }
        (Some(kind @ ("pull" | "issue")), _, (Some(did), Some(rkey))) => {
            let item = format!("at://{did}/dev.knotless.{kind}/{rkey}");
            xrpc("getThread", &[repo, ("uri", &item)])
        }
        (Some("branches"), None, _) => xrpc("listBranches", &[repo, ("commits", "true")]),
        (Some("queue"), None, _) => xrpc("getQueue", &[repo]),
        (Some("checks"), None, _) => with("listRuns", &[repo]),
        (Some("settings"), Some("actions"), _) => xrpc("listRunners", &[repo]),
        (Some("check"), _, (Some(did), Some(rkey))) => {
            let check = format!("at://{did}/dev.knotless.check/{rkey}");
            xrpc("getCheck", &[repo, ("uri", &check)])
        }
        _ => return None,
    };
    Some((uri, query))
}

/// The page of the check `c`, as a view has it.
fn check_page(c: &Value) -> String {
    let uri = c["uri"].as_str().unwrap_or_default();
    let (runner, _, rkey) = knotless::forge::parse_uri(uri).unwrap();
    let repo = &c["repo"];
    format!(
        "/{}/{}/check/{runner}/{rkey}",
        repo["handle"].as_str().unwrap(),
        repo["name"].as_str().unwrap()
    )
}

/// Where the page at `page` reads what it shows.
fn at(page: &str) -> String {
    repo_query(page).expect("a repository's page").1
}

/// A PNG of one color.
fn one_color(rgb: [u8; 3]) -> Vec<u8> {
    let mut pixmap = resvg::tiny_skia::Pixmap::new(8, 8).unwrap();
    pixmap.fill(resvg::tiny_skia::Color::from_rgba8(
        rgb[0], rgb[1], rgb[2], 255,
    ));
    knotless_serve::card::encode(&pixmap)
}

/// The color a PNG has at `x`, `y`.
fn color_at(png: &[u8], x: u32, y: u32) -> [u8; 3] {
    let pixmap = resvg::tiny_skia::Pixmap::decode_png(png).unwrap();
    let p = pixmap.pixel(x, y).unwrap();
    [p.red(), p.green(), p.blue()]
}

fn person(account: Account, site: &Site) -> Sandbox {
    let mut s = Sandbox::with(account);
    s.set("KNOTLESS_PASSWORD", "");
    s.set("KNOTLESS_APPVIEW", &site.base);
    let password = s.account.password.clone();
    let handle = s.account.handle.clone();
    assert_eq!(
        s.cli(&["login", &handle, "--password-stdin"], password.as_bytes())
            .0,
        0
    );
    s
}

fn ok(s: &Sandbox, args: &[&str]) -> String {
    let (code, out) = s.cli(args, b"");
    assert_eq!(code, 0, "{args:?}: {out}");
    out
}

fn write(dir: &std::path::Path, path: &str, bytes: &[u8]) {
    let path = dir.join(path);
    std::fs::create_dir_all(path.parent().unwrap()).unwrap();
    std::fs::write(path, bytes).unwrap();
}

/// The entry named `name` among `entries`.
fn named<'a>(entries: &'a Value, name: &str) -> &'a Value {
    entries
        .as_array()
        .unwrap()
        .iter()
        .find(|e| e["name"] == name)
        .unwrap_or_else(|| panic!("no {name} in {entries}"))
}

fn names(entries: &Value) -> Vec<&str> {
    entries
        .as_array()
        .unwrap()
        .iter()
        .map(|e| e["name"].as_str().unwrap())
        .collect()
}

#[test]
fn browsing_a_repository() {
    let site = serve(true);
    let s = person(account(), &site);
    let url = s.url("browsed");
    let work = s.work("w", 51);
    write(&work, "README.md", b"# Browsed\n\n<script>alert(1)</script>\n\n[guide](docs/guide.md) [docs](docs) [x](javascript:alert(1)) ![logo](logo.png)\n");
    write(&work, "docs/README", b"plain <words>\n");
    write(
        &work,
        "docs/guide.md",
        b"# Guide\n\n<img src=x onerror=alert(1)>\n\n[home](../README.md) [up](../../../x) [top](/src/main.rs#L2) [src](../src/) ![shot](shot.png)\n",
    );
    write(
        &work,
        "src/main.rs",
        "// héllo\nfn main() {\n    println!(\"hi\");\n}\n".as_bytes(),
    );
    write(&work, "logo.png", b"\x89PNG\r\n\x1a\n\0\0\0\rIHDR");
    write(&work, "data.bin", &[0, 1, 2, 3]);
    write(&work, "big.txt", &vec![b'a'; 1100 * 1024]);
    write(&work, "notes.unknown-kind", b"no grammar\n");
    s.git(&work, &["add", "-A"]);
    s.git(
        &work,
        &[
            "commit",
            "-q",
            "-m",
            "Lay out the tree\n\nWith a body that says why.",
        ],
    );
    let tip = s.git(&work, &["rev-parse", "HEAD"]);
    s.git(&work, &["push", "-q", &url, "main", "main:feature/x"]);
    // Tags: one plain, one annotated, and one of a tree, which names no
    // commit.
    s.git(&work, &["tag", "v2"]);
    s.git(&work, &["tag", "-a", "v1", "-m", "First"]);
    s.git(&work, &["tag", "tree", "HEAD^{tree}"]);
    s.git(&work, &["push", "-q", &url, "--tags"]);
    let h = &s.account.handle;
    let repo = format!("/{h}/browsed");

    // The search finds it once the index has seen it.
    wait_until(|| names(&site.search("")["repos"]).contains(&"browsed"));
    let upper = site.search("BROWS");
    assert!(names(&upper["repos"]).contains(&"browsed"));
    assert_eq!(upper["repos"][0]["owner"]["handle"], h.as_str());
    assert_eq!(site.search("zzzz")["repos"], serde_json::json!([]));

    let res = site.get(&at(&repo));
    assert_eq!(res.headers()["cache-control"], "no-cache");
    let top = site.data(&repo);
    assert_eq!(top["empty"], Value::Null);
    assert_eq!(top["ref"], "main");
    assert_eq!(top["repo"]["defaultBranch"], "main");
    assert_eq!(top["repo"]["owner"]["handle"], h.as_str());
    assert_eq!(
        top["repo"]["uri"],
        format!("at://{}/dev.knotless.repo/browsed", s.account.did)
    );
    assert!(
        top["lastCommit"]["message"]
            .as_str()
            .unwrap()
            .starts_with("Lay out the tree")
    );
    // Folders first, then files, each by name.
    let listed = names(&top["entries"]);
    assert!(
        listed.iter().position(|n| *n == "src") < listed.iter().position(|n| *n == "README.md")
    );
    assert_eq!(named(&top["entries"], "docs")["kind"], "tree");
    assert_eq!(named(&top["entries"], "data.bin")["size"], 4);
    // Written in Rust alone: the README, the docs and what no language
    // names are not counted.
    let rust = "// héllo\nfn main() {\n    println!(\"hi\");\n}\n".len();
    assert_eq!(
        top["languages"],
        serde_json::json!([{"name": "Rust", "color": "#dea584", "bytes": rust, "permille": 1000}])
    );
    let readme = top["readme"]["html"].as_str().unwrap();
    for want in [
        "<h1>Browsed</h1>",
        "&lt;script&gt;alert(1)&lt;/script&gt;",
        &format!(r#"href="{repo}/blob/main/docs/guide.md""#),
        &format!(r#"href="{repo}/tree/main/docs""#),
        r##"href="#""##,
        &format!(r#"src="{repo}/raw/main/logo.png""#),
    ] {
        assert!(readme.contains(want), "{want} not in\n{readme}");
    }

    // Each page names a card for whatever unfurls a link to it: cached
    // for good under the version it names, checked again under any other.
    assert_eq!(top["repo"]["description"], Value::Null);
    let named_card = top["repo"]["card"].as_str().unwrap().to_string();
    let unversioned = format!("{repo}/card.png");
    assert!(
        named_card.starts_with(&format!("{unversioned}?v=")),
        "{named_card}"
    );
    let (png, cache) = card(&site, &named_card);
    assert_eq!(cache, "public, max-age=31536000, immutable");
    assert_eq!(card(&site, &unversioned), (png, "no-cache".to_string()));
    assert_eq!(
        site.data(&format!("{repo}/commits/main"))["repo"]["card"],
        named_card.as_str()
    );
    let tag = site.get(&named_card).headers()["etag"].clone();
    let again = Client::new()
        .get(format!("{}{named_card}", site.base))
        .header("if-none-match", tag.clone())
        .send()
        .unwrap();
    assert_eq!(again.status().as_u16(), 304);
    assert_eq!(again.headers()["etag"], tag);
    let (_, cache) = card(&site, "/card.png");
    assert_eq!(cache, "no-cache");

    // Compressed for those that ask.
    let res = Client::builder()
        .no_gzip()
        .no_brotli()
        .build()
        .unwrap()
        .get(format!("{}{}", site.base, at(&repo)))
        .header("accept-encoding", "br")
        .send()
        .unwrap();
    assert_eq!(res.headers()["content-encoding"], "br");

    // A branch with a slash in its name, and a pinned commit, whose answer is
    // still checked again: it shows the repository as it is now.
    let sub = site.data(&format!("{repo}/tree/feature/x/src"));
    assert_eq!(
        (sub["ref"].as_str(), sub["path"].as_str()),
        (Some("feature/x"), Some("src"))
    );
    assert_eq!(names(&sub["entries"]), ["main.rs"]);
    // As a query names them apart, the ref and the path within it.
    let uri = top["repo"]["uri"].as_str().unwrap();
    let apart = [("repo", uri), ("ref", "feature/x"), ("path", "src")];
    let apart = site.json(&xrpc("getTree", &apart));
    assert_eq!((&apart["ref"], &apart["path"]), (&sub["ref"], &sub["path"]));
    assert_eq!(sub["languages"], serde_json::json!([]));
    // The commit that last changed each entry, asked apart from the tree.
    let last = site.json(&xrpc("getLastCommits", &[("repo", uri)]));
    assert_eq!((&last["ref"], &last["path"]), (&top["ref"], &top["path"]));
    let sorted = |entries: &Value| {
        let mut all: Vec<String> = names(entries).into_iter().map(String::from).collect();
        all.sort();
        all
    };
    // In the tree's order, not the page's.
    assert_eq!(sorted(&last["entries"]), sorted(&top["entries"]));
    assert_eq!(named(&last["entries"], "src")["sha"], tip.as_str());
    assert_eq!(last["commits"][0]["sha"], tip.as_str());
    assert_eq!(last["commits"][0]["message"], top["lastCommit"]["message"]);
    // At the root, how long its history is; below it, not said.
    assert!(last["commitCount"].as_u64().is_some_and(|n| n >= 1));
    let src = [("repo", uri), ("ref", "feature/x"), ("path", "src")];
    let last = site.json(&xrpc("getLastCommits", &src));
    assert_eq!(names(&last["entries"]), ["main.rs"]);
    assert!(last.get("commitCount").is_none());
    let file = [("repo", uri), ("ref", "main"), ("path", "src/main.rs")];
    let file = site.get(&xrpc("getLastCommits", &file));
    assert_eq!(file.status().as_u16(), 404);
    // Every path, folders and files, to find a file by: the root's
    // first, so a limit leaves out the deepest.
    let paths = site.json(&xrpc("listPaths", &[("repo", uri)]));
    assert_eq!(paths["ref"], "main");
    assert!(paths.get("truncated").is_none());
    let all: Vec<(&str, &str)> = paths["paths"]
        .as_array()
        .unwrap()
        .iter()
        .map(|p| (p["path"].as_str().unwrap(), p["kind"].as_str().unwrap()))
        .collect();
    for want in [
        ("docs", "tree"),
        ("src/main.rs", "blob"),
        ("README.md", "blob"),
    ] {
        assert!(all.contains(&want), "{want:?} not in {all:?}");
    }
    let deep = all.iter().position(|(p, _)| p.contains('/')).unwrap();
    assert!(all[deep..].iter().all(|(p, _)| p.contains('/')), "{all:?}");
    let some = [("repo", uri), ("ref", "feature/x"), ("limit", "3")];
    let some = site.json(&xrpc("listPaths", &some));
    assert_eq!(some["ref"], "feature/x");
    assert_eq!(some["truncated"], true);
    assert_eq!(some["paths"].as_array().unwrap().len(), 3);
    let nowhere = [("repo", uri), ("ref", "no-such-branch")];
    assert_eq!(
        site.get(&xrpc("listPaths", &nowhere)).status().as_u16(),
        404
    );
    // Every branch, the default first, a page at a time.
    let branches = |params: &[(&str, &str)]| {
        site.json(&xrpc("listBranches", &[&[("repo", uri)], params].concat()))
    };
    let all = branches(&[]);
    assert_eq!(
        all,
        serde_json::json!({"branches": [{"name": "main"}, {"name": "feature/x"}]})
    );
    let first = branches(&[("limit", "1")]);
    assert_eq!(first["branches"], serde_json::json!([{"name": "main"}]));
    assert_eq!(first["cursor"], "1");
    let rest = branches(&[("limit", "1"), ("cursor", "1")]);
    assert_eq!(rest["branches"], serde_json::json!([{"name": "feature/x"}]));
    assert_eq!(rest["cursor"], Value::Null);
    let bad = site.get(&xrpc("listBranches", &[("repo", uri), ("cursor", "x")]));
    assert_eq!(bad.status().as_u16(), 400);
    // Asked, each with its tip and its checks, as the branches page shows.
    let shown = branches(&[("commits", "true")]);
    assert_eq!(shown["branches"][1]["name"], "feature/x");
    assert_eq!(shown["branches"][1]["commit"]["sha"], tip.as_str());
    assert_eq!(shown["branches"][1]["checks"], serde_json::json!([]));
    // Its tags that name a commit, an annotated one peeled: by name, as
    // both are of one commit.
    let tags = |params: &[(&str, &str)]| {
        site.json(&xrpc("listTags", &[&[("repo", uri)], params].concat()))
    };
    assert_eq!(
        tags(&[]),
        serde_json::json!({"tags": [{"name": "v1"}, {"name": "v2"}]})
    );
    let first = tags(&[("commits", "true"), ("limit", "1")]);
    assert_eq!(first["tags"][0]["commit"]["sha"], tip.as_str());
    assert_eq!(first["tags"][0]["checks"], serde_json::json!([]));
    assert_eq!(first["cursor"], "1");
    let res = site.get(&at(&format!("{repo}/tree/{tip}/docs")));
    assert_eq!(res.headers()["cache-control"], "no-cache");
    let docs: Value = res.json().unwrap();
    assert_eq!(docs["readme"]["text"], "plain <words>\n");
    assert_eq!(docs["readme"]["html"], Value::Null);

    let blob = |path: &str| site.data(&format!("{repo}/blob/main/{path}"));
    let file = blob("src/main.rs");
    assert_eq!(file["kind"], "text");
    assert_eq!(file["lines"], 4);
    assert!(
        file["ranges"].as_str().unwrap().starts_with("comment:0,8;"),
        "{file}"
    );
    assert!(
        file["text"]
            .as_str()
            .unwrap()
            .starts_with("// héllo\nfn main()")
    );
    assert_eq!(blob("notes.unknown-kind")["ranges"], "");
    assert_eq!(file["html"], Value::Null);
    // A Markdown file comes made into HTML, its links from its own folder
    // and never out of the repository, and as text for the plain view.
    let guide = blob("docs/guide.md");
    assert!(guide["text"].as_str().unwrap().starts_with("# Guide"));
    let html = guide["html"].as_str().unwrap();
    for want in [
        "<h1>Guide</h1>",
        "&lt;img src=x onerror=alert(1)&gt;",
        &format!(r#"href="{repo}/blob/main/README.md""#),
        &format!(r#"href="{repo}/blob/main/x""#),
        &format!(r#"href="{repo}/blob/main/src/main.rs#L2""#),
        &format!(r#"href="{repo}/tree/main/src""#),
        &format!(r#"src="{repo}/raw/main/docs/shot.png""#),
    ] {
        assert!(html.contains(want), "{want} not in\n{html}");
    }
    assert_eq!(blob("logo.png")["kind"], "image");
    assert_eq!(blob("data.bin")["kind"], "binary");
    let big = blob("big.txt");
    assert_eq!(big["kind"], "large");
    assert_eq!(big["text"], Value::Null);
    assert_eq!(big["size"], 1100 * 1024);

    for (path, kind) in [
        ("logo.png", "image/png"),
        ("data.bin", "application/octet-stream"),
        ("src/main.rs", "text/plain; charset=utf-8"),
    ] {
        let res = site.get(&format!("{repo}/raw/main/{path}"));
        assert_eq!(res.headers()["content-type"], kind);
        assert!(
            res.headers()["content-security-policy"]
                .to_str()
                .unwrap()
                .starts_with("sandbox")
        );
    }
    // A file at a commit's hash is the same forever; at a branch, it can move.
    let cached = |rev: &str| {
        site.get(&format!("{repo}/raw/{rev}/src/main.rs")).headers()["cache-control"].clone()
    };
    assert_eq!(cached(tip.trim()), "public, max-age=31536000, immutable");
    assert_eq!(cached("main"), "no-cache");

    // The files at a revision as one download, in a folder named for the
    // repository and the revision.
    let archive = |rest: &str| site.get(&format!("{repo}/archive/{rest}"));
    let header = |res: &Response, name: &str| res.headers()[name].to_str().unwrap().to_string();
    let tarred = archive("main.tar.gz");
    assert_eq!(tarred.status(), 200);
    assert_eq!(header(&tarred, "content-type"), "application/gzip");
    assert_eq!(header(&tarred, "cache-control"), "no-cache");
    assert_eq!(
        header(&tarred, "content-disposition"),
        "attachment; filename=\"browsed-main.tar.gz\""
    );
    let mut tar = Vec::new();
    std::io::Read::read_to_end(
        &mut flate2::read::GzDecoder::new(&tarred.bytes().unwrap()[..]),
        &mut tar,
    )
    .unwrap();
    let holds = |bytes: &[u8], name: &str| bytes.windows(name.len()).any(|w| w == name.as_bytes());
    assert!(holds(&tar, "browsed-main/src/main.rs"));
    assert!(holds(&tar, "println!(\"hi\");"));
    // A branch's `/` is left out of the names; a tag's archive, as a
    // commit's, never changes.
    let branch = archive("feature/x.zip");
    assert_eq!(
        header(&branch, "content-disposition"),
        "attachment; filename=\"browsed-feature-x.zip\""
    );
    let zip = branch.bytes().unwrap();
    assert!(zip.starts_with(b"PK\x03\x04"));
    assert!(holds(&zip, "browsed-feature-x/src/main.rs"));
    let pinned = archive(&format!("{}.zip", tip.trim()));
    assert_eq!(header(&pinned, "content-type"), "application/zip");
    assert_eq!(
        header(&pinned, "cache-control"),
        "public, max-age=31536000, immutable"
    );
    assert_eq!(archive("v1.tar.gz").status(), 200);
    // Only the two kinds, of a revision naming a commit.
    for missing in ["main.rar", "main", "nope.zip", "tree.zip", "-x.zip"] {
        assert_eq!(archive(missing).status(), 404, "{missing}");
    }
    // Its commits and tags as Atom feeds, the newest first, linked to
    // their pages in full.
    let feed = |name: &str| {
        let res = site.get(&format!("{repo}/{name}.atom"));
        assert_eq!(res.status(), 200, "{name}");
        let kind = res.headers()["content-type"].to_str().unwrap().to_string();
        assert_eq!(kind, "application/atom+xml; charset=utf-8");
        res.text().unwrap()
    };
    let commits = feed("commits");
    assert_eq!(commits.matches("<entry>").count(), 30);
    assert!(commits.contains(&format!("<title>Commits to {h}/browsed</title>")));
    assert!(commits.contains(&format!("{}{repo}/commits/main\"", site.base)));
    let first = &commits[commits.find("<entry>").unwrap()..];
    assert!(first.contains("<title>Lay out the tree</title>"));
    assert!(first.contains(&format!(
        "<id>{}{repo}/commit/{}</id>",
        site.base,
        tip.trim()
    )));
    assert!(first.contains(
        "<content type=\"text\">Lay out the tree\n\nWith a body that says why.</content>"
    ));
    let tagged = feed("tags");
    assert_eq!(tagged.matches("<entry>").count(), 2);
    assert!(tagged.contains("<title>v1</title>"));
    assert!(tagged.contains(&format!("href=\"{}{repo}/tree/v2\"", site.base)));
    // v1 and v2 tag one commit, and are two entries all the same.
    for tag in ["v1", "v2"] {
        let id = format!("<id>{}{repo}/tree/{tag}</id>", site.base);
        assert!(tagged.contains(&id), "{tag}");
    }
    // With nothing released, a feed of no entries.
    let none = feed("releases");
    assert!(!none.contains("<entry>"));
    assert!(none.contains("<updated>1970-01-01T00:00:00Z</updated>"));

    let log = site.data(&format!("{repo}/commits/main"));
    assert_eq!(log["commits"].as_array().unwrap().len(), 50);
    assert_eq!(log["cursor"], "50");
    assert_eq!(
        log["commits"][0]["message"],
        "Lay out the tree\n\nWith a body that says why."
    );
    // The commits page asks for each message's subject alone.
    let subjects = site.data(&format!("{repo}/commits/main?subjectOnly=true"));
    assert_eq!(subjects["commits"][0]["message"], "Lay out the tree");
    let older = site.data(&format!("{repo}/commits/main?cursor=50"));
    assert_eq!(older["commits"].as_array().unwrap().len(), 2);
    assert_eq!(older["cursor"], Value::Null);
    // As many as asked, from 1 to 100.
    let limited = |limit: &str| {
        let uri = top["repo"]["uri"].as_str().unwrap();
        site.json(&xrpc("listCommits", &[("repo", uri), ("limit", limit)]))
    };
    let one = limited("0");
    assert_eq!(one["commits"].as_array().unwrap().len(), 1);
    assert_eq!(one["cursor"], "1");
    let all = limited("1000");
    assert_eq!(all["commits"].as_array().unwrap().len(), 52);
    assert_eq!(all["cursor"], Value::Null);
    let bad = site.get(&format!("{}&cursor=x", at(&format!("{repo}/commits/main"))));
    assert_eq!(bad.status().as_u16(), 400);
    assert_eq!(bad.json::<Value>().unwrap()["error"], "InvalidRequest");
    let src = site.data(&format!("{repo}/commits/main/src"));
    assert_eq!(src["commits"].as_array().unwrap().len(), 1);
    assert_eq!(src["path"], "src");
    assert_eq!(
        site.data(&format!("{repo}/commits/main/nowhere"))["commits"],
        serde_json::json!([])
    );

    let commit = site.data(&format!("{repo}/commit/{tip}"));
    assert_eq!(commit["commit"]["sha"], tip.as_str());
    assert_eq!(
        commit["commit"]["message"],
        "Lay out the tree\n\nWith a body that says why."
    );
    assert_eq!(commit["commit"]["parents"].as_array().unwrap().len(), 1);
    let files = commit["files"].as_array().unwrap();
    let main = files.iter().find(|f| f["new"] == "src/main.rs").unwrap();
    assert_eq!(
        (main["status"].as_str(), main["added"].as_u64()),
        (Some("added"), Some(4))
    );
    assert!(main["ranges"].as_str().unwrap().starts_with("comment:0,8;"));
    let last = main["rows"].as_array().unwrap().last().unwrap();
    assert_eq!(
        (
            last["kind"].as_str(),
            last["new"].as_u64(),
            last["text"].as_str()
        ),
        (Some("a"), Some(4), Some("}"))
    );
    // A commit shows as its author's account when its address is one the
    // repository's own people claim: the first push claimed git's. One
    // knotless made names its account; one by any other address is no
    // one's. So with co-authors, whom its trailers name.
    assert_eq!(commit["commit"]["author"]["actor"]["handle"], h.as_str());
    assert_eq!(top["lastCommit"]["author"]["actor"]["handle"], h.as_str());
    let made = |message: &str, author: &[&str]| {
        let args = [
            &[
                "repo", "commit", &url, "-b", "made", "-m", message, "--put", "made", "-",
            ],
            author,
        ]
        .concat();
        let (code, sha) = s.cli(&args, message.as_bytes());
        assert_eq!(code, 0, "{sha}");
        let at = format!("{repo}/commit/{}", sha.trim());
        wait_until(|| site.status(&self::at(&at)).0 == 200);
        site.data(&at)["commit"].clone()
    };
    let ours = made("Made", &[]);
    assert_eq!(ours["author"]["actor"]["handle"], h.as_str());
    assert_eq!(ours["coauthors"], serde_json::json!([]));
    // Made and written by the same one, it names no committer of its own.
    assert_eq!(ours["committer"], Value::Null);
    assert!(ours["committedAt"].as_str().unwrap().contains('T'));
    let together = "Together\n\nCo-authored-by: Claude <noreply@anthropic.com>\nCo-authored-by: Me <t@example.com>";
    let theirs = made(together, &["--author", "Someone", "someone@example.org"]);
    let (author, helped) = (&theirs["author"], &theirs["coauthors"]);
    assert_eq!(
        (&author["name"], &author["actor"]),
        (&Value::from("Someone"), &Value::Null)
    );
    assert_eq!(
        (&helped[0]["name"], &helped[0]["actor"], &helped[0]["agent"]),
        (&Value::from("Claude"), &Value::Null, &Value::from("claude"))
    );
    assert_eq!(helped[1]["agent"], Value::Null);
    assert_eq!(
        (&helped[1]["name"], &helped[1]["actor"]["handle"]),
        (&Value::from("Me"), &Value::from(h.as_str()))
    );

    let first = s.git(&work, &["rev-list", "--max-parents=0", "HEAD"]);
    assert_eq!(
        site.data(&format!("{repo}/commit/{first}"))["commit"]["parents"],
        serde_json::json!([])
    );

    for missing in [
        at(&format!(
            "{repo}/commit/0000000000000000000000000000000000000000"
        )),
        at(&format!("{repo}/tree/main/nope")),
        at(&format!("{repo}/blob/main/nope")),
        format!("{repo}/raw/main/nope"),
        at(&format!("{repo}/commits/nope")),
        at(&format!("{repo}/tree/-x")),
        at(&format!("{repo}/commit/-x")),
        at(&format!("{repo}/pull/did:plc:x/y")),
        at(&format!("/{h}/missing")),
    ] {
        let res = site.get(&missing);
        let status = res.status().as_u16();
        assert!(status == 404 || status == 502, "{missing}: {status}");
        assert_eq!(res.headers()["cache-control"], "no-store", "{missing}");
        // As XRPC has a failure: its name, and why.
        let failure: Value = res.json().unwrap();
        let named = if status == 404 {
            "NotFound"
        } else {
            "UpstreamFailure"
        };
        assert_eq!(failure["error"], named, "{missing}");
        assert!(failure["message"].is_string(), "{missing}");
    }
    // A scanner's guess at a page names no one, and is not the PDS failing.
    let probe = at("/wp-admin/install.php");
    let res = site.get(&probe);
    assert_eq!(res.status().as_u16(), 404, "{probe}");
    assert_eq!(res.json::<Value>().unwrap()["error"], "NotFound");

    // A repository with nothing in it yet.
    ok(&s, &["repo", "create", &s.url("empty")]);
    let empty = site.data(&format!("/{h}/empty"));
    assert_eq!(empty["empty"], true);
    assert_eq!(empty["entries"], serde_json::json!([]));
    // Its default branch, which the pages link to before it exists, is
    // as empty, not missing.
    let tree = site.data(&format!("/{h}/empty/tree/main"));
    assert_eq!(tree["empty"], true);
    let history = site.data(&format!("/{h}/empty/commits/main"));
    assert_eq!(history["commits"], serde_json::json!([]));
    assert_eq!(history["ref"], "main");
    assert_eq!(history.get("cursor"), None);
    let feed = site
        .get(&format!("/{h}/empty/commits.atom"))
        .text()
        .unwrap();
    assert!(feed.contains("<feed") && !feed.contains("<entry>"));
    // Anything else there is not found.
    let other = site.get(&at(&format!("/{h}/empty/commits/other")));
    assert_eq!(other.status().as_u16(), 404);

    // A query of a repository names it by its record, and asks what it
    // holds.
    let browsed = format!("at://{h}/dev.knotless.repo/browsed");
    for bad in [
        xrpc("getRepo", &[("repo", "knotless://x/y")]),
        xrpc(
            "getTree",
            &[("repo", &format!("at://{h}/dev.knotless.pull/x"))],
        ),
        xrpc(
            "getCommit",
            &[("repo", &format!("at://{h}/dev.knotless.repo/browsed"))],
        ),
        xrpc("listItems", &[("repo", &browsed), ("kind", "wiki")]),
        xrpc(
            "listItems",
            &[("repo", &browsed), ("kind", "pull"), ("cursor", "x")],
        ),
        xrpc("getThread", &[("repo", &browsed), ("uri", &browsed)]),
        xrpc("getCheck", &[("repo", &browsed), ("uri", &browsed)]),
    ] {
        let res = site.get(&bad);
        assert_eq!(res.status().as_u16(), 400, "{bad}");
        assert_eq!(res.json::<Value>().unwrap()["error"], "InvalidRequest");
    }
}

/// A merge commit's page shows what it brought in, against its first
/// parent, and not what its branch did meanwhile.
#[test]
fn a_merge_commit_shows_what_it_brought_in() {
    let site = serve(true);
    let s = person(account(), &site);
    let url = s.url("merged");
    let work = s.work("w", 0);
    write(&work, "begun.txt", b"begun\n");
    s.git(&work, &["add", "-A"]);
    s.git(&work, &["commit", "-q", "-m", "Begin"]);
    s.git(&work, &["checkout", "-q", "-b", "topic"]);
    write(&work, "brought.txt", b"brought\n");
    s.git(&work, &["add", "-A"]);
    s.git(&work, &["commit", "-q", "-m", "Bring"]);
    s.git(&work, &["checkout", "-q", "main"]);
    write(&work, "meanwhile.txt", b"meanwhile\n");
    s.git(&work, &["add", "-A"]);
    s.git(&work, &["commit", "-q", "-m", "Meanwhile"]);
    s.git(
        &work,
        &["merge", "-q", "--no-ff", "-m", "Merge topic", "topic"],
    );
    let merge = s.git(&work, &["rev-parse", "HEAD"]);
    s.git(&work, &["push", "-q", &url, "main"]);

    let uri = format!("at://{}/dev.knotless.repo/merged", s.account.did);
    let commit = xrpc("getCommit", &[("repo", &uri), ("sha", &merge)]);
    wait_until(|| site.status(&commit).0 == 200);
    let change = site.json(&commit);
    let files = change["files"].as_array().unwrap();
    assert_eq!(files.len(), 1, "{change}");
    assert_eq!(files[0]["new"], "brought.txt");
    assert_eq!(files[0]["added"], 1);

    // Asked for its first rows, a few files come with them, as a page
    // draws them at once, and the rest without, but with how many.
    for i in 0..12 {
        write(&work, &format!("many/{i:02}.txt"), b"one\n");
    }
    s.git(&work, &["add", "-A"]);
    s.git(&work, &["commit", "-q", "-m", "Many"]);
    let many = s.git(&work, &["rev-parse", "HEAD"]);
    s.git(&work, &["push", "-q", &url, "main"]);
    let first = xrpc(
        "getCommit",
        &[("repo", &uri), ("sha", &many), ("firstRows", "true")],
    );
    wait_until(|| site.status(&first).0 == 200);
    let first = site.json(&first);
    let rows = |f: &Value| f["rows"].as_array().unwrap().len();
    let files = first["files"].as_array().unwrap();
    assert_eq!(files.len(), 12, "{first}");
    assert!(files[..10].iter().all(|f| rows(f) > 0), "{first}");
    assert_eq!(rows(&files[10]), 0);
    assert!(files[10]["rowCount"].as_u64().unwrap() > 0, "{first}");
    // Those it named, rows and all.
    let named = [
        ("repo", uri.as_str()),
        ("sha", many.as_str()),
        ("paths", "many/10.txt"),
        ("paths", "many/11.txt"),
        ("firstRows", "true"),
    ];
    let named = site.json(&xrpc("getCommit", &named));
    let files = named["files"].as_array().unwrap();
    assert_eq!(files.len(), 2, "{named}");
    assert_eq!(files[1]["new"], "many/11.txt");
    assert!(rows(&files[1]) > 0, "{named}");
    // No more of them than a page of them.
    let path = ("paths", "many/00.txt");
    let too_many = [
        &[("repo", uri.as_str()), ("sha", many.as_str())][..],
        &[path; 51][..],
    ]
    .concat();
    let (code, body) = site.status(&xrpc("getCommit", &too_many));
    assert_eq!(code, 400, "{body}");
}

/// Who last changed each line of a file, as the blame page shows it.
#[test]
fn blaming_a_file() {
    use serde_json::json;
    let site = serve(true);
    let s = person(account(), &site);
    let url = s.url("blamed");
    let work = s.work("w", 0);
    write(&work, "src/lib.rs", b"fn a() {}\nfn b() {}\nfn c() {}\n");
    write(&work, "data.bin", &[0, 1, 2, 3]);
    write(&work, "big.txt", "line\n".repeat(60 * 1024).as_bytes());
    s.git(&work, &["add", "-A"]);
    s.git(&work, &["commit", "-q", "-m", "Begin\n\nWith a body."]);
    let first = s.git(&work, &["rev-parse", "HEAD"]);
    write(&work, "src/lib.rs", b"fn a() {}\nfn B() {}\nfn c() {}\n");
    let someone = ["--author", "Someone <someone@example.org>"];
    s.git(
        &work,
        &[&["commit", "-q", "-am", "Shout b"][..], &someone[..]].concat(),
    );
    let second = s.git(&work, &["rev-parse", "HEAD"]);
    s.git(&work, &["push", "-q", &url, "main"]);
    let h = &s.account.handle;
    let uri = format!("at://{}/dev.knotless.repo/blamed", s.account.did);
    let blame =
        |params: &[(&str, &str)]| xrpc("getBlame", &[&[("repo", uri.as_str())], params].concat());

    let lib = blame(&[("ref", "main"), ("path", "src/lib.rs")]);
    wait_until(|| site.status(&lib).0 == 200);
    let b = site.json(&lib);
    assert_eq!(
        (&b["ref"], &b["path"]),
        (&json!("main"), &json!("src/lib.rs"))
    );
    assert_eq!(b["lines"], 3);
    assert!(b["text"].as_str().unwrap().starts_with("fn a() {}\n"));
    assert!(!b["ranges"].as_str().unwrap().is_empty(), "{b}");
    // In runs, from the first line on: the line changed since, between
    // two from where the file began, which blame goes on back past.
    assert_eq!(
        b["hunks"],
        json!([
            {"sha": first, "start": 1, "count": 1},
            {"sha": second, "start": 2, "count": 1, "previous": {"sha": first, "path": "src/lib.rs"}},
            {"sha": first, "start": 3, "count": 1},
        ])
    );
    // Each commit once, as the file first names it, by account where its
    // address is one the repository's own people claim.
    let commits = b["commits"].as_array().unwrap();
    assert_eq!(commits.len(), 2);
    assert_eq!(commits[0]["sha"], first.as_str());
    assert_eq!(commits[0]["message"], "Begin\n\nWith a body.");
    assert_eq!(commits[0]["author"]["actor"]["handle"], h.as_str());
    let them = &commits[1]["author"];
    assert_eq!(
        (&them["name"], &them["actor"]),
        (&json!("Someone"), &Value::Null)
    );

    // Before the change, as the page links it, the ref running on into
    // the path; and once more, as kept.
    let at_first = format!("{first}/src/lib.rs");
    let before = blame(&[("ref", at_first.as_str())]);
    let whole = json!([{"sha": first, "start": 1, "count": 3}]);
    assert_eq!(site.json(&before)["hunks"], whole);
    assert_eq!(site.json(&before)["hunks"], whole);

    // Not a binary, nor a file too large, and not what is not there.
    for (path, status, why) in [
        ("data.bin", 400, "binary"),
        ("big.txt", 400, "too large"),
        ("nope", 404, "nope"),
    ] {
        let res = site.get(&blame(&[("ref", "main"), ("path", path)]));
        assert_eq!(res.status().as_u16(), status, "{path}");
        let failure: Value = res.json().unwrap();
        let message = failure["message"].as_str().unwrap();
        assert!(message.contains(why), "{path}: {message}");
    }
}

#[test]
fn a_copy_shows_whom_the_original_vouched_for() {
    let site = serve(true);
    let owner = person(account(), &site);
    let url = owner.url("original");
    ok(&owner, &["create", &url]);
    let work = owner.work("w", 2);
    // The first push claims git's address as the owner's.
    owner.git(&work, &["push", "-q", &url, "main"]);
    let first = owner.git(&work, &["rev-parse", "HEAD^"]);
    let o = Value::from(owner.account.handle.as_str());
    let (copier, taker) = (person(account(), &site), person(account(), &site));
    let author = |who: &Sandbox, name: &str, sha: &str| {
        let at = format!("/{}/{name}/commit/{sha}", who.account.handle);
        wait_until(|| site.status(&self::at(&at)).0 == 200);
        site.data(&at)["commit"]["author"]["actor"]["handle"].clone()
    };

    // A copy, pushed as a repository of its own rather than forked, by
    // one who claims another address, shows the owner's commits as no
    // one's until the original's pages have walked them, then as the
    // owner's: the original's owner vouched for them there.
    ok(&copier, &["email", "add", "copier@example.com"]);
    let copy = copier.url("copy");
    ok(&copier, &["create", &copy]);
    copier.git(&work, &["push", "-q", &copy, "main"]);
    assert_eq!(author(&copier, "copy", &first), Value::Null);
    assert_eq!(author(&owner, "original", &first), o);
    wait_until(|| author(&copier, "copy", &first) == o);
    // Shown again with nothing new, the walk has nothing to do; a commit
    // pushed since is walked on from where the last stopped.
    assert_eq!(author(&owner, "original", &first), o);
    let tip = owner.commit_file(&work, "g", "g\n");
    owner.git(&work, &["push", "-q", &url, "main"]);
    copier.git(&work, &["push", "-q", &copy, "main"]);
    assert_eq!(author(&owner, "original", &tip), o);
    wait_until(|| author(&copier, "copy", &tip) == o);

    // One who copies them and claims the owner's address, as their own
    // first push does, has them shown as theirs only in their own
    // repository. Elsewhere the two disagree, and the copy shows the
    // commits as no one's rather than either's.
    let taken = taker.url("taken");
    ok(&taker, &["create", &taken]);
    taker.git(&work, &["push", "-q", &taken, "main"]);
    let t = Value::from(taker.account.handle.as_str());
    wait_until(|| author(&taker, "taken", &first) == t);
    wait_until(|| author(&copier, "copy", &first) == Value::Null);
    assert_eq!(author(&owner, "original", &first), o);
}

#[test]
fn comments_on_lines() {
    let site = serve(true);
    let owner = person(account(), &site);
    let other = person(account(), &site);
    let url = owner.url("lined");
    ok(&owner, &["repo", "create", &url]);
    let work = owner.work("w", 1);
    let numbered = |five: &str| format!("one\ntwo\nthree\nfour\n{five}\nsix\n");
    owner.commit_file(&work, "a.rs", &numbered("five"));
    owner.git(&work, &["push", "-q", &url, "main"]);
    let fork = ok(&other, &["repo", "fork", &url]);
    let clone = other.path("clone");
    other.git(other.dir.path(), &["clone", "-q", &fork, "clone"]);
    other.commit_file(&clone, "a.rs", &numbered("FIVE"));
    other.git(&clone, &["push", "-q", "origin", "main:topic"]);
    let open = ["pull", "create", &url, "main", &fork, "topic", "Shout"];
    let pull = ok(&other, &open);
    let pull = pull.trim();
    let issue = ok(&other, &["issue", "create", &url, "Quiet"]);

    // Its author says something of a line of their own, on its own.
    let own = ok(
        &other,
        &[
            "comment", "add", &url, pull, "", "--line", "a.rs:5", "Louder",
        ],
    );
    assert_eq!(own.lines().count(), 1);
    // A review writes itself and its comments on lines at once.
    let review = [
        "comment",
        "add",
        &url,
        pull,
        "A few things",
        "--changes",
        "--line",
        "a.rs:4-5",
        "These",
        "--line",
        "a.rs:5:old",
        "Was quiet",
        "--line",
        "gone.rs:1",
        "Where",
    ];
    let wrote = ok(&owner, &review);
    let wrote: Vec<&str> = wrote.lines().collect();
    let [said, these, was, gone] = wrote[..] else {
        panic!("{wrote:?}")
    };
    let fails = |s: &Sandbox, args: &[&str]| assert_ne!(s.cli(args, b"").0, 0, "{args:?}");
    fails(
        &owner,
        &[
            "comment", "add", &url, pull, "", "--line", "a.rs", "No line",
        ],
    );
    fails(
        &owner,
        &[
            "comment",
            "add",
            &url,
            pull,
            "",
            "--line",
            "a.rs:0",
            "Before it",
        ],
    );
    fails(
        &owner,
        &["comment", "add", &url, pull, "", "--line", "a.rs:1", " "],
    );
    fails(
        &owner,
        &[
            "comment",
            "add",
            &url,
            issue.trim(),
            "",
            "--line",
            "a.rs:1",
            "An issue",
        ],
    );
    fails(&owner, &["comment", "add", &url, these, "No", "--approve"]);
    let unknown = format!(
        "at://{}/dev.knotless.comment/3zzzzzzzzzzzz",
        owner.account.did
    );
    fails(&owner, &["comment", "add", &url, &unknown, "Who?"]);

    // An answer to an answer goes in the thread it answers.
    let answer = ok(&other, &["comment", "add", &url, these, "Done"]);
    let again = ok(&owner, &["comment", "add", &url, answer.trim(), "Thanks"]);
    // The pull's author resolves a thread; none but a thread is resolved.
    assert_eq!(
        ok(&other, &["comment", "resolve", &url, these]).trim(),
        "resolved"
    );
    assert_eq!(
        ok(&owner, &["comment", "resolve", &url, was]).trim(),
        "resolved"
    );
    assert_eq!(ok(&owner, &["pull", "reopen", &url, was]).trim(), "open");
    for not in [said, answer.trim(), pull, &unknown] {
        fails(&owner, &["comment", "resolve", &url, not]);
    }
    fails(&owner, &["pull", "close", &url, these]);

    let shown = ok(&owner, &["pull", "show", &url, pull]);
    assert!(
        shown.contains(&format!(" on a.rs:4-5 ({these}, resolved): These")),
        "{shown}"
    );
    assert!(
        shown.contains(&format!(" on a.rs:5:old ({was}): Was quiet")),
        "{shown}"
    );
    assert!(
        shown.contains(&format!(" answering {these}: Thanks")),
        "{shown}"
    );

    let rkey = |uri: &str| uri.rsplit('/').next().unwrap().to_string();
    let thread = format!(
        "/{}/lined/pull/{}/{}",
        owner.account.handle,
        other.account.did,
        rkey(pull)
    );
    let comment = |d: &Value, uri: &str| -> Value {
        let all = d["comments"].as_array().unwrap();
        all.iter()
            .find(|c| c["uri"] == uri)
            .cloned()
            .unwrap_or(Value::Null)
    };
    let all_said =
        |d: &Value| comment(d, again.trim()).is_object() && comment(d, these)["resolved"] == true;
    wait_until(|| all_said(&site.data(&thread)));
    let d = site.data(&thread);
    let line = |uri: &str| comment(&d, uri)["line"].clone();
    let text = |l: &Value| -> Vec<(u64, String)> {
        // None when there are none.
        let lines = l["lines"].as_array().into_iter().flatten();
        lines
            .map(|t| {
                (
                    t["number"].as_u64().unwrap(),
                    t["text"].as_str().unwrap().to_string(),
                )
            })
            .collect()
    };
    assert_eq!(text(&line(these)), [(4, "four".into()), (5, "FIVE".into())]);
    assert_eq!(
        (
            line(these)["startLine"].as_u64(),
            line(these)["outdated"].as_bool()
        ),
        (Some(4), Some(false))
    );
    let quiet: Vec<(u64, String)> = vec![
        (2, "two".into()),
        (3, "three".into()),
        (4, "four".into()),
        (5, "five".into()),
    ];
    assert_eq!(
        (text(&line(was)), line(was)["side"].as_str()),
        (quiet.clone(), Some("old"))
    );
    assert_eq!(
        (line(gone)["outdated"].as_bool(), text(&line(gone))),
        (Some(true), vec![])
    );
    assert_eq!(comment(&d, own.trim())["review"], Value::Null);
    assert_eq!(comment(&d, these)["review"], said);
    assert_eq!(comment(&d, again.trim())["reply"], these);
    assert!(comment(&d, was)["resolved"].is_null());

    // Once the pull changes those lines, what was said of them is outdated;
    // the side it leaves stands.
    other.commit_file(&clone, "a.rs", &numbered("Five"));
    other.git(&clone, &["push", "-q", "origin", "main:topic"]);
    let moved = |d: &Value| comment(d, these)["line"]["outdated"] == true;
    wait_until(|| moved(&site.data(&thread)));
    // Merged, it still shows them, from where the pull came.
    ok(&owner, &["pull", "merge", &url, pull]);
    wait_until(|| site.data(&thread)["merged"].is_string());
    let d = site.data(&thread);
    assert_eq!(comment(&d, these)["line"]["outdated"], true);
    assert_eq!(
        (
            text(&comment(&d, was)["line"]),
            comment(&d, was)["line"]["outdated"].as_bool()
        ),
        (quiet, Some(false))
    );
}

#[test]
fn pulls_and_issues() {
    let site = serve(true);
    let owner = person(account(), &site);
    let other = person(account(), &site);
    let url = owner.url("tracked");
    ok(
        &owner,
        &["repo", "create", &url, "--description", "Tracks <things>"],
    );
    let work = owner.work("w", 1);
    owner.git(&work, &["push", "-q", &url, "main"]);
    let repo = format!("/{}/tracked", owner.account.handle);

    let fork = ok(&other, &["repo", "fork", &url]);
    let clone = other.path("clone");
    other.git(other.dir.path(), &["clone", "-q", &fork, "clone"]);
    ok(&other, &["account", "email", "add", "other@example.com"]);
    write(&clone, "b", b"b\n");
    other.git(&clone, &["add", "b"]);
    let as_other = [
        "commit",
        "-q",
        "--author",
        "Other <other@example.com>",
        "-m",
        "b",
    ];
    other.git(&clone, &as_other);
    let brought = other.git(&clone, &["rev-parse", "HEAD"]);
    other.git(&clone, &["push", "-q", "origin", "main:topic"]);
    let pull = ok(
        &other,
        &[
            "pull",
            "create",
            &url,
            "main",
            &fork,
            "topic",
            "Add b",
            "-m",
            "Adds **b**.",
        ],
    );
    let issue = ok(&other, &["issue", "create", &url, "Broken <thing>"]);
    let quiet = ok(&other, &["issue", "create", &url, "Quiet"]);
    ok(
        &owner,
        &["comment", "add", &url, &pull, "Not yet", "--changes"],
    );
    ok(
        &owner,
        &["comment", "add", &url, &pull, "Fixed", "--approve"],
    );
    ok(&owner, &["comment", "add", &url, &pull, "Thanks"]);
    ok(&owner, &["pull", "close", &url, &quiet]);

    let rkey = |uri: &str| uri.rsplit('/').next().unwrap().to_string();
    let other_did = &other.account.did;
    let thread = format!("{repo}/pull/{other_did}/{}", rkey(&pull));
    // Open, it shows what its branch in the fork brings in.
    let changed = |d: &Value| -> Vec<(String, u64)> {
        d["changes"]["files"]
            .as_array()
            .unwrap()
            .iter()
            .map(|f| {
                (
                    f["new"].as_str().unwrap().into(),
                    f["added"].as_u64().unwrap(),
                )
            })
            .collect()
    };
    wait_until(|| site.data(&thread)["changes"].is_object());
    let open = site.data(&thread);
    assert_eq!(changed(&open), [("b".to_string(), 1)]);
    // Asked for its files only, they come without their rows, which come
    // a few files at a time.
    let tracked = format!("at://{}/dev.knotless.repo/tracked", owner.account.did);
    let (tracked, uri) = (tracked.as_str(), pull.trim());
    let files_only = [("repo", tracked), ("uri", uri), ("filesOnly", "true")];
    let listed = site.json(&xrpc("getThread", &files_only));
    let b = &listed["changes"]["files"][0];
    assert_eq!(
        (b["new"].as_str(), b["added"].as_u64()),
        (Some("b"), Some(1))
    );
    assert_eq!(b["rows"], serde_json::json!([]));
    // With how many rows are to come, and hunks among them, to keep room for.
    assert!(b["rowCount"].as_u64().unwrap() >= 2, "{b}");
    assert_eq!(b["hunkCount"], 1);
    let diff = |params: &[(&str, &str)]| site.status(&xrpc("getPullDiff", params));
    let both = [
        ("repo", tracked),
        ("uri", uri),
        ("paths", "b"),
        ("paths", "c"),
    ];
    let (code, body) = diff(&both);
    assert_eq!(code, 200, "{body}");
    let rows: Value = serde_json::from_str(&body).unwrap();
    let files = rows["files"].as_array().unwrap();
    assert_eq!(files.len(), 1);
    assert!(
        files[0]["rows"]
            .as_array()
            .unwrap()
            .iter()
            .any(|r| r["kind"] == "a")
    );
    // Asked for the first rows too, the first files come with them, as
    // getPullDiff gives them.
    let first = [
        ("repo", tracked),
        ("uri", uri),
        ("filesOnly", "true"),
        ("firstRows", "true"),
    ];
    let first = site.json(&xrpc("getThread", &first));
    assert_eq!(first["changes"]["files"][0]["rows"], files[0]["rows"]);
    // It needs a pull, and a file of it to show.
    assert_eq!(diff(&[("repo", tracked), ("paths", "b")]).0, 400);
    assert_eq!(diff(&[("repo", tracked), ("uri", uri)]).0, 400);
    let not_a_pull = [("repo", tracked), ("uri", issue.trim()), ("paths", "b")];
    assert_eq!(diff(&not_a_pull).0, 400);
    // Its commits are the fork's, shown on the fork's pages.
    let c = &open["changes"];
    assert_eq!(c["commits"].as_array().unwrap().len(), 1);
    assert_eq!(c["commits"][0]["sha"], brought.as_str());
    assert_eq!(
        (c["repo"]["handle"].as_str(), c["repo"]["name"].as_str()),
        (Some(other.account.handle.as_str()), Some("tracked"))
    );

    // Open, only its checks of the head it is at show: one of an older
    // head is not, though it ran later.
    ok(&owner, &["runner", "add", &url, &owner.account.handle]);
    let tracked = serde_json::json!({
        "uri": format!("at://{}/dev.knotless.repo/tracked", owner.account.did),
        "key": owner.record("dev.knotless.repo", "tracked")["key"].clone(),
    });
    // Keyed before the checks below, which are to be the newest once it
    // is merged, and the older head's after the head's, to be its newest.
    for (rkey, sha, status, at) in [
        (
            "a0",
            brought.as_str(),
            "running",
            "2026-10-19T00:00:01.000Z",
        ),
        (
            "a1",
            &"0".repeat(40)[..],
            "failure",
            "2026-10-19T00:00:02.000Z",
        ),
    ] {
        owner.put_record(
            "dev.knotless.check",
            rkey,
            serde_json::json!({
                "$type": "dev.knotless.check", "repo": tracked.clone(),
                "ref": "refs/heads/topic", "commit": sha, "pull": pull.trim(),
                "workflow": "pulled", "status": status, "startedAt": at,
            }),
        );
    }
    let older = at(&format!("{repo}/check/{}/a1", owner.account.did));
    wait_until(|| site.status(&older).0 == 200);
    wait_until(|| site.data(&thread)["checks"].as_array().unwrap().len() == 1);
    let head = site.data(&thread)["checks"][0].clone();
    assert_eq!(
        (head["status"].as_str(), head["commit"].as_str()),
        (Some("running"), Some(brought.as_str()))
    );

    // Queued, open, it says so.
    let key = owner.record("dev.knotless.repo", "tracked")["key"].clone();
    let queued = serde_json::json!({
        "$type": "dev.knotless.queue", "subject": pull, "state": "queued",
        "repo": {"uri": format!("at://{}/dev.knotless.repo/tracked", owner.account.did), "key": key},
        "createdAt": "2026-10-21T00:00:00.000Z",
    });
    owner.put_record("dev.knotless.queue", &knotless::keys::tid(), queued);
    wait_until(|| site.data(&thread)["queue"]["state"] == "queued");
    assert_eq!(
        site.data(&thread)["queue"]["createdAt"],
        "2026-10-21T00:00:00.000Z"
    );

    let merged = ok(&owner, &["pull", "merge", &url, &pull]);
    wait_until(|| site.data(&thread)["merged"].is_string());
    assert!(site.data(&thread)["queue"].is_null());
    // Merged, what the merge brought, shown on the base's pages.
    let done = site.data(&thread);
    assert_eq!(changed(&done), [("b".to_string(), 1)]);
    let shas: Vec<&str> = done["changes"]["commits"]
        .as_array()
        .unwrap()
        .iter()
        .map(|c| c["sha"].as_str().unwrap())
        .collect();
    assert!(shas.contains(&brought.as_str()), "{shas:?}");
    assert_eq!(done["changes"]["repo"]["did"], owner.account.did.as_str());

    // What the pull brought in shows as its author's: the merge vouched for
    // it. Their address anywhere else is no one's.
    let theirs = site.data(&format!("{repo}/commit/{brought}"));
    assert_eq!(
        theirs["commit"]["author"]["actor"]["handle"],
        other.account.handle.as_str()
    );
    let args = [
        "repo",
        "commit",
        &url,
        "-b",
        "aside",
        "-m",
        "Not theirs",
        "--put",
        "c",
        "-",
    ];
    let author = ["--author", "Other", "other@example.com"];
    let (code, sha) = owner.cli(&[&args[..], &author].concat(), b"c\n");
    assert_eq!(code, 0, "{sha}");
    let aside = at(&format!("{repo}/commit/{}", sha.trim()));
    wait_until(|| site.status(&aside).0 == 200);
    assert_eq!(site.json(&aside)["commit"]["author"]["actor"], Value::Null);
    let d = site.data(&thread);
    assert_eq!(
        (d["item"]["kind"].as_str(), d["item"]["title"].as_str()),
        (Some("pull"), Some("Add b"))
    );
    assert_eq!(d["item"]["state"], "merged");
    assert_eq!(d["item"]["author"]["handle"], other.account.handle.as_str());
    assert_eq!(d["item"]["comments"], 3);
    assert!(d["html"].as_str().unwrap().contains("<strong>b</strong>"));
    let verdicts: Vec<&Value> = d["comments"]
        .as_array()
        .unwrap()
        .iter()
        .map(|c| &c["verdict"])
        .collect();
    assert_eq!(
        verdicts,
        [
            &Value::from("changes"),
            &Value::from("approve"),
            &Value::Null
        ]
    );
    assert_eq!(d["comments"][2]["html"], "<p>Thanks</p>\n");
    // The owner asked for changes, then approved: the latest stands.
    let review = &d["reviews"].as_array().unwrap()[..];
    assert_eq!(review.len(), 1, "{review:?}");
    assert_eq!(review[0]["reviewer"]["did"], owner.account.did.as_str());
    assert_eq!(review[0]["verdict"], "approve");
    assert_eq!(review[0]["commit"], d["comments"][1]["commit"]);
    assert_eq!(
        (&review[0]["outdated"], &review[0]["maintainer"]),
        (&serde_json::json!(false), &serde_json::json!(true))
    );
    // No rule names the owner a gate of main.
    assert_eq!(review[0].get("gate"), None);

    // Checks: those of the runners each repository names, the newest run
    // of each workflow, of the commit a listing shows and of a pull's branch.
    let check = |s: &Sandbox,
                 rkey: &str,
                 repo: Value,
                 r: &str,
                 sha: &str,
                 workflow: &str,
                 status: &str,
                 at: &str| {
        s.put_record(
            "dev.knotless.check",
            rkey,
            serde_json::json!({
                "$type": "dev.knotless.check", "repo": repo, "ref": r, "commit": sha,
                "workflow": workflow, "status": status, "startedAt": at,
            }),
        )
    };
    let reference = |s: &Sandbox, name: &str| {
        let key = s.record("dev.knotless.repo", name)["key"].clone();
        let uri = format!("at://{}/dev.knotless.repo/{name}", s.account.did);
        serde_json::json!({"uri": uri, "key": key})
    };
    let fork_name = fork.rsplit('/').next().unwrap();
    ok(&other, &["runner", "add", &fork, &other.account.handle]);
    let tip = site.data(&repo)["lastCommit"]["sha"]
        .as_str()
        .unwrap()
        .to_string();
    let ours = reference(&owner, "tracked");
    let theirs = reference(&other, fork_name);
    let main = "refs/heads/main";
    let topic = "refs/heads/topic";
    check(
        &owner,
        "c1",
        ours.clone(),
        main,
        &tip,
        "ci",
        "failure",
        "2026-10-20T00:00:01.000Z",
    );
    check(
        &owner,
        "c2",
        ours.clone(),
        main,
        &tip,
        "ci",
        "success",
        "2026-10-20T00:00:02.000Z",
    );
    check(
        &owner,
        "c3",
        ours.clone(),
        main,
        &"0".repeat(40),
        "ci",
        "failure",
        "2026-10-20T00:00:03.000Z",
    );
    // Not a runner the repository names.
    check(
        &other,
        "c4",
        ours.clone(),
        main,
        &tip,
        "lint",
        "failure",
        "2026-10-20T00:00:04.000Z",
    );
    check(
        &other,
        "c5",
        theirs.clone(),
        topic,
        &"1".repeat(40),
        "ci",
        "running",
        "2026-10-20T00:00:05.000Z",
    );
    check(
        &other,
        "c6",
        theirs,
        "refs/heads/other",
        &"1".repeat(40),
        "lint",
        "error",
        "2026-10-20T00:00:06.000Z",
    );
    // A run of the pull itself, by the target's runner: on the pull's page,
    // in its workflow's place by name, and not on the code page, though it
    // is of the commit shown.
    owner.put_record(
        "dev.knotless.check",
        "c7",
        serde_json::json!({
            "$type": "dev.knotless.check", "repo": ours.clone(), "ref": topic, "commit": tip,
            "pull": pull.trim(), "workflow": "pulled", "status": "success",
            "startedAt": "2026-10-20T00:00:07.000Z",
        }),
    );
    // Its newest run of `pulled` is c7, not the runs of its heads above.
    wait_until(|| {
        let shown = site.data(&thread);
        let uri = shown["checks"][1]["uri"].as_str().unwrap_or_default();
        shown["checks"].as_array().unwrap().len() == 2 && uri.ends_with("/c7")
    });
    let shown = site.data(&thread);
    assert_eq!(shown["checks"][0]["workflow"], "ci");
    assert_eq!(shown["checks"][0]["status"], "running");
    assert_eq!(shown["checks"][1]["workflow"], "pulled");
    // What its runner counts for the owner: on the pull's page, with its
    // runs and in its settings, in the order the record names runners.
    assert_eq!(shown["minutes"], Value::Null);
    owner.put_record(
        "dev.knotless.minutes",
        &owner.account.did,
        serde_json::json!({
            "$type": "dev.knotless.minutes", "owner": owner.account.did, "minutes": 1640,
            "allowance": 2000, "freeing": [{"at": "2026-11-02T00:00:00Z", "minutes": 310}],
            "updatedAt": "2026-10-20T00:00:09.000Z",
        }),
    );
    wait_until(|| site.data(&thread)["minutes"].is_array());
    let counted = &site.data(&thread)["minutes"][0];
    assert_eq!(counted["runner"]["handle"], owner.account.handle.as_str());
    assert_eq!(
        (&counted["minutes"], &counted["allowance"]),
        (&1640.into(), &2000.into())
    );
    assert_eq!(counted["freeing"][0]["minutes"], 310);
    let tab = site.data(&format!("{repo}/checks"));
    assert_eq!(tab["minutes"][0]["minutes"], 1640, "{tab}");
    let settings = site.data(&format!("{repo}/settings/actions"));
    assert_eq!(settings["minutes"][0]["minutes"], 1640, "{settings}");
    wait_until(|| site.data(&repo)["checks"].as_array().unwrap().len() == 1);
    let code = site.data(&repo);
    assert_eq!(code["checks"][0]["status"], "success");
    assert_eq!(code["checks"][0]["commit"], tip.as_str());
    assert_eq!(code["checks"][0]["error"], Value::Null);
    assert_eq!(
        check_page(&code["checks"][0]),
        format!("{repo}/check/{}/c2", owner.account.did)
    );
    // History shows the same beside the commit, its newest run of each
    // workflow alone: c2, not c1 before it, nor the pull's c7.
    let history = site.data(&format!("{repo}/commits/main"));
    let listed = history["checks"].as_array().unwrap();
    assert_eq!(listed.len(), 1, "{history}");
    assert_eq!(check_page(&listed[0]), check_page(&code["checks"][0]));
    assert_eq!(listed[0]["commit"], tip.as_str());
    // And beside the branch whose tip it is, on the branches page.
    let branches = site.data(&format!("{repo}/branches"));
    let main = &branches["branches"][0];
    assert_eq!(main["commit"]["sha"], tip.as_str(), "{branches}");
    assert_eq!(check_page(&main["checks"][0]), check_page(&listed[0]));
    assert_eq!(
        check_page(&shown["checks"][1]),
        format!("{repo}/check/{}/c7", owner.account.did)
    );

    // A check's own page: its steps, the pull it ran for, its log, whole
    // or its tail, and the pictures it made, passing over outputs of a
    // kind it does not know.
    let log = owner.upload_blob(b"==> fmt\nok\n==> clippy\nwarning: unused\n", "text/plain");
    let picture = owner.upload_blob(b"GIF89a", "image/gif");
    let page_like = owner.upload_blob(b"<script>1</script>", "text/html");
    owner.put_record(
        "dev.knotless.check",
        "c8",
        serde_json::json!({
            "$type": "dev.knotless.check", "repo": ours.clone(), "ref": topic, "commit": tip,
            "pull": pull.trim(), "workflow": "steps", "status": "failure",
            "steps": [
                {"name": "fmt", "status": "success", "exitCode": 0, "durationMs": 1200},
                {"name": "clippy", "status": "failure", "exitCode": 1, "durationMs": 64000},
            ],
            "log": log,
            "outputs": [
                {"$type": "dev.knotless.check#image", "image": picture.clone(), "alt": "home 2 after"},
                {"$type": "dev.knotless.check#image", "image": page_like.clone(), "alt": "not one"},
                {"$type": "dev.knotless.check#trace", "name": "trace.zip"},
            ],
            "startedAt": "2026-10-20T00:00:08.000Z",
            "finishedAt": "2026-10-20T00:01:13.000Z",
        }),
    );
    let page = format!("{repo}/check/{}/c8", owner.account.did);
    wait_until(|| site.status(&at(&page)).0 == 200);
    let run = site.data(&page);
    assert_eq!(run["check"]["workflow"], "steps");
    assert_eq!(run["ref"], topic);
    assert_eq!(run["check"]["commit"], tip.as_str());
    assert_eq!(run["pull"], pull.trim());
    assert_eq!(run["runner"]["handle"], owner.account.handle.as_str());
    assert_eq!(run["check"]["finishedAt"], "2026-10-20T00:01:13.000Z");
    assert_eq!(run["steps"][1]["exitCode"], 1);
    assert_eq!(run["steps"][1]["durationMs"], 64000);
    assert_eq!(run["log"], "==> fmt\nok\n==> clippy\nwarning: unused\n");
    let images = run["check"]["images"].as_array().unwrap();
    assert_eq!(images.len(), 2);
    assert_eq!(images[0]["alt"], "home 2 after");
    let cid = |blob: &Value| blob["ref"]["$link"].as_str().unwrap().to_string();
    let shown = images[0]["url"].as_str().unwrap();
    assert!(
        shown.starts_with("/xrpc/dev.knotless.getCheckImage?"),
        "{shown}"
    );
    assert!(
        shown.ends_with(&format!("&cid={}", cid(&picture))),
        "{shown}"
    );
    // Each picture, served as one, and only those the check names.
    let served = |url: &Value| {
        let res = site.get(url.as_str().unwrap());
        let kind = res.headers()["content-type"].to_str().unwrap().to_string();
        (res.status().as_u16(), kind, res.bytes().unwrap().to_vec())
    };
    assert_eq!(
        served(&images[0]["url"]),
        (200, "image/gif".to_string(), b"GIF89a".to_vec())
    );
    assert_eq!(served(&images[1]["url"]).1, "application/octet-stream");
    // The pull's page shows the same pictures beside the check.
    wait_until(|| site.data(&thread)["checks"].as_array().unwrap().len() == 3);
    // By name: ci, pulled, steps.
    let listed = site.data(&thread)["checks"][2].clone();
    assert_eq!(listed["workflow"], "steps");
    assert_eq!(&listed["images"], &run["check"]["images"]);
    let unnamed = shown.replace(&cid(&picture), &cid(&log));
    assert_eq!(site.status(&unnamed).0, 404);
    let plain = site.data(&format!("{repo}/check/{}/c2", owner.account.did));
    assert_eq!(
        (plain["pull"].clone(), plain["log"].clone()),
        (Value::Null, Value::Null)
    );
    // Not a runner the repository names, not a check, and a check of another
    // repository: none of them shown.
    for gone in [
        format!("{repo}/check/{other_did}/c4"),
        format!("{repo}/check/{}/none", owner.account.did),
        format!("/{}/{fork_name}/check/{other_did}/c4", other.account.handle),
    ] {
        assert_eq!(site.status(&at(&gone)).0, 404, "{gone}");
    }
    // Landed by the merge queue, its checks are its batch's: those on its
    // base at the batch's tip, not the runs of its own heads.
    let landed = serde_json::json!({
        "$type": "dev.knotless.state", "repo": ours.clone(), "subject": pull.trim(),
        "state": "merged", "commit": d["merged"].clone(), "batch": tip,
        "createdAt": "2026-10-20T00:00:09.000Z",
    });
    owner.put_record("dev.knotless.state", &knotless::keys::tid(), landed);
    wait_until(|| site.data(&thread)["checks"].as_array().unwrap().len() == 1);
    assert_eq!(
        check_page(&site.data(&thread)["checks"][0]),
        format!("{repo}/check/{}/c2", owner.account.did)
    );
    assert_eq!(
        d["comments"][2]["author"]["handle"],
        owner.account.handle.as_str()
    );
    assert!(
        merged
            .trim()
            .starts_with(&d["merged"].as_str().unwrap()[..7]),
        "{merged}"
    );
    assert_eq!(d["base"], "main");
    let head = serde_json::json!({
        "repo": {"did": other_did, "handle": other.account.handle, "name": "tracked"},
        "branch": "topic",
    });
    assert_eq!(d["head"], head);
    // A pull squashed into a linear branch names itself on its commit's
    // last line, which shows as a link to it.
    ok(&owner, &["branch", "create", &url, "release"]);
    ok(&owner, &["branch", "protect", &url, "release"]);
    write(&clone, "d", b"d\n");
    other.git(&clone, &["add", "d"]);
    other.git(&clone, &["commit", "-q", "-m", "d"]);
    other.git(&clone, &["push", "-q", "origin", "main:more"]);
    let squashed = ok(
        &other,
        &[
            "pull", "create", &url, "release", &fork, "more", "Add d", "-m", "Adds d.",
        ],
    );
    let sha = ok(&owner, &["pull", "merge", &url, &squashed]);
    let squashed_thread = format!("{repo}/pull/{other_did}/{}", rkey(&squashed));
    wait_until(|| site.data(&squashed_thread)["merged"].is_string());
    wait_until(|| site.status(&at(&format!("{repo}/commit/{sha}"))).0 == 200);
    let shown = site.data(&format!("{repo}/commit/{sha}"))["commit"].clone();
    assert_eq!(shown["message"], "Add d\n\nAdds d.");
    assert_eq!(
        shown["pull"],
        serde_json::json!({"uri": squashed.trim(), "title": "Add d"})
    );
    let log = site.data(&format!("{repo}/commits/release"));
    assert_eq!(log["commits"][0]["pull"]["title"], "Add d");
    // Under it, the merge commit of the pull before, naming it too.
    assert_eq!(log["commits"][1]["pull"]["uri"], pull.trim());

    let items = |tail: &str| site.data(&format!("{repo}{tail}"));
    let titles = |v: &Value| -> Vec<String> {
        v["items"]
            .as_array()
            .unwrap()
            .iter()
            .map(|i| i["title"].as_str().unwrap().to_string())
            .collect()
    };
    wait_until(|| titles(&items("/issues?state=closed")).contains(&"Quiet".to_string()));
    assert_eq!(items("")["repo"]["description"], "Tracks <things>");
    let open = items("/issues");
    assert_eq!(titles(&open), ["Broken <thing>"]);
    assert_eq!(
        (open["openCount"].as_u64(), open["closedCount"].as_u64()),
        (Some(1), Some(1))
    );
    assert_eq!(open["items"][0]["kind"], "issue");
    assert_eq!(
        (
            open["repo"]["openIssues"].as_u64(),
            open["repo"]["openPulls"].as_u64()
        ),
        (Some(1), Some(0))
    );
    assert_eq!(open["items"][0]["uri"], issue.trim());
    assert_eq!(items("/pulls")["items"], serde_json::json!([]));
    let closed = items("/pulls?state=closed");
    // A page at a time, from where the one before ended.
    let first = items("/pulls?state=closed&limit=1");
    assert_eq!(first["items"][0]["uri"], closed["items"][0]["uri"]);
    let next = first["cursor"].as_str().unwrap();
    let second = items(&format!("/pulls?state=closed&limit=1&cursor={next}"));
    assert_eq!(second["items"][0]["uri"], closed["items"][1]["uri"]);
    assert_eq!(
        (
            closed["items"][1]["state"].as_str(),
            closed["items"][1]["comments"].as_u64()
        ),
        (Some("merged"), Some(3))
    );
    let page = site.data(&format!("{repo}/issue/{other_did}/{}", rkey(&issue)));
    assert_eq!(page["item"]["title"], "Broken <thing>");
    assert_eq!(
        (&page["body"], &page["html"], &page["base"], &page["merged"]),
        (&Value::Null, &Value::Null, &Value::Null, &Value::Null)
    );
    assert_eq!(page["comments"], serde_json::json!([]));
    // A pull's card and an issue's, and what the pull says, as written.
    // Said more about, the issue names a new card.
    let issue_page = format!("{repo}/issue/{other_did}/{}", rkey(&issue));
    let before = page["card"].as_str().unwrap().to_string();
    assert!(before.starts_with(&format!("{issue_page}/card.png?v=")));
    let (png, _) = card(&site, &before);
    let pulled = site.data(&thread);
    assert_eq!(pulled["body"], "Adds **b**.");
    assert_ne!(card(&site, pulled["card"].as_str().unwrap()).0, png);
    ok(&owner, &["comment", "add", &url, &issue, "Seen"]);
    wait_until(|| site.data(&issue_page)["card"] != before.as_str());
    let after = site.data(&issue_page)["card"].as_str().unwrap().to_string();
    assert_ne!(card(&site, &after).0, png);
    let (_, cache) = card(&site, &before);
    assert_eq!(cache, "no-cache");
    for missing in [
        format!("{repo}/issue/{other_did}/nothing/card.png"),
        format!("{repo}/wiki/{other_did}/{}/card.png", rkey(&issue)),
        format!("/{}/nothing/card.png", owner.account.handle),
    ] {
        assert_eq!(site.status(&missing).0, 404, "{missing}");
    }
    assert_eq!(
        site.status(&at(&format!("{repo}/issue/{other_did}/nothing")))
            .0,
        404
    );

    // The fork says where it came from.
    let source = serde_json::json!({
        "did": owner.account.did,
        "handle": owner.account.handle,
        "name": "tracked",
    });
    let forked = site.data(&format!("/{}/tracked", other.account.handle));
    assert_eq!(forked["repo"]["forkOf"], source);

    // Each account has a page: its public repositories, newest first, and
    // who it is, as its Bluesky profile says.
    let theirs = site.profile(&other.account.handle);
    assert_eq!(theirs["did"], other_did.as_str());
    assert_eq!(
        theirs["repos"][0]["forkOf"],
        serde_json::json!({
            "did": owner.account.did,
            "handle": owner.account.handle,
            "name": "tracked",
        })
    );
    assert_eq!(
        (
            &theirs["displayName"],
            &theirs["description"],
            &theirs["avatar"]
        ),
        (&Value::Null, &Value::Null, &Value::Null)
    );
    let session: Value = Client::new()
        .post(format!(
            "{}/xrpc/com.atproto.server.createSession",
            net().pds
        ))
        .json(&serde_json::json!({
            "identifier": owner.account.handle,
            "password": owner.account.password,
        }))
        .send()
        .unwrap()
        .json()
        .unwrap();
    let picture: Value = Client::new()
        .post(format!("{}/xrpc/com.atproto.repo.uploadBlob", net().pds))
        .bearer_auth(session["accessJwt"].as_str().unwrap())
        .header("content-type", "image/png")
        .body(b"\x89PNG\r\n\x1a\n".to_vec())
        .send()
        .unwrap()
        .json()
        .unwrap();
    let put = Client::new()
        .post(format!("{}/xrpc/com.atproto.repo.putRecord", net().pds))
        .bearer_auth(session["accessJwt"].as_str().unwrap())
        .json(&serde_json::json!({
            "repo": owner.account.did,
            "collection": "app.bsky.actor.profile",
            "rkey": "self",
            "record": {
                "$type": "app.bsky.actor.profile",
                "displayName": "The <Owner>",
                "description": "Keeps things tracked.",
                "website": "https://site.example",
                "pronouns": "she/her",
                "avatar": picture["blob"],
            },
        }))
        .send()
        .unwrap();
    assert!(put.status().is_success());
    ok(&owner, &["repo", "create", &owner.url("later")]);
    // While the firehose is watched, both are read from the index, which
    // has them once the firehose says so.
    wait_until(|| {
        let mine = site.profile(&owner.account.did);
        names(&mine["repos"]) == ["later", "tracked"]
            && mine["description"] == "Keeps things tracked."
    });
    let mine = site.profile(&owner.account.did);
    assert_eq!(names(&mine["repos"]), ["later", "tracked"]);
    assert_eq!(mine["displayName"], "The <Owner>");
    assert_eq!(mine["handle"], owner.account.handle.as_str());
    assert_eq!(mine["description"], "Keeps things tracked.");
    // Its picture, as Bluesky's CDN serves it, wherever the account is
    // shown: over its repositories, beside them, and beside what it said.
    let cid = picture["blob"]["ref"]["$link"].as_str().unwrap();
    let pictured = format!(
        "https://cdn.bsky.app/img/avatar/plain/{}/{cid}@jpeg",
        owner.account.did
    );
    assert_eq!(mine["avatar"], pictured.as_str());
    assert_eq!(mine["repos"][0]["owner"]["avatar"], pictured.as_str());
    let discussed = site.data(&issue_page);
    assert_eq!(discussed["repo"]["owner"]["avatar"], pictured.as_str());
    assert_eq!(discussed["item"]["author"]["avatar"], Value::Null);
    let seen = discussed["comments"].as_array().unwrap().iter();
    let seen: Vec<_> = seen
        .filter(|c| c["author"]["handle"] == owner.account.handle.as_str())
        .collect();
    assert_eq!(seen[0]["author"]["avatar"], pictured.as_str());
    assert_eq!(mine["pronouns"], "she/her");
    // With no links, its website is one.
    let site_link = serde_json::json!([{"url": "https://site.example"}]);
    assert_eq!(mine["links"], site_link);
    // Its links, as the link records its profile names in its repo say,
    // in order: only http and https addresses, and only its own records.
    let elsewhere = owner.put_record(
        "app.bsky.actor.link",
        "3mlinkaaaaaa2",
        serde_json::json!({"url": "https://elsewhere.example", "title": "Elsewhere", "createdAt": "2026-10-23T00:00:00Z"}),
    );
    let script = owner.put_record(
        "app.bsky.actor.link",
        "3mlinkaaaaaa3",
        serde_json::json!({"url": "javascript:alert(1)", "createdAt": "2026-10-23T00:00:00Z"}),
    );
    let foreign = serde_json::json!({
        "uri": format!("at://{other_did}/app.bsky.actor.link/3mlinkaaaaaa2"),
        "cid": elsewhere["cid"],
    });
    let mut bsky = owner.record("app.bsky.actor.profile", "self");
    bsky["links"] = serde_json::json!([foreign, script, elsewhere]);
    owner.put_record("app.bsky.actor.profile", "self", bsky);
    let kept = serde_json::json!([{"url": "https://elsewhere.example", "title": "Elsewhere"}]);
    wait_until(|| site.profile(&owner.account.did)["links"] == kept);

    // A knotless profile stands in for the Bluesky one, field by field,
    // and its picture is served from here, as the account's PDS has it.
    let own = owner.path("own.png");
    std::fs::write(&own, b"\x89PNG\r\n\x1a\nmine").unwrap();
    let own = own.to_str().unwrap();
    ok(
        &owner,
        &[
            "profile",
            "--bio",
            "Code, mostly.",
            "--link",
            "https://owner.example",
            "--avatar",
            own,
        ],
    );
    wait_until(|| site.profile(&owner.account.did)["description"] == "Code, mostly.");
    let mine = site.profile(&owner.account.did);
    assert_eq!(mine["displayName"], "The <Owner>");
    assert_eq!(mine["description"], "Code, mostly.");
    assert_eq!(mine["pronouns"], "she/her");
    assert_eq!(
        mine["links"],
        serde_json::json!([{"url": "https://owner.example"}])
    );
    // Its address in full, as it was asked for.
    let full = mine["avatar"].as_str().unwrap();
    let shown = full.strip_prefix(&site.base).unwrap();
    let mine_cid = shown.rsplit("&cid=").next().unwrap();
    let avatar = |did: &str, cid: &str| xrpc("getAvatar", &[("actor", did), ("cid", cid)]);
    assert_eq!(shown, avatar(&owner.account.did, mine_cid));
    assert_eq!(mine["repos"][0]["owner"]["avatar"], full);
    let res = site.get(shown);
    assert_eq!(res.headers()["content-type"], "image/png");
    assert_eq!(res.bytes().unwrap().as_ref(), b"\x89PNG\r\n\x1a\nmine");
    // Only the picture the profile names, of an account there is.
    assert_eq!(site.status(&avatar(&owner.account.did, cid)).0, 404);
    assert_eq!(site.status(&avatar(other_did, cid)).0, 404);
    assert_eq!(site.status(&avatar("nobody.invalid", "x")).0, 404);
    // In full, by handle as by DID; and, asked through the pages, on the
    // site they serve.
    let view = site.json(&format!(
        "/xrpc/dev.knotless.getProfile?actor={}",
        owner.account.handle
    ));
    assert_eq!(
        view,
        serde_json::json!({
            "did": owner.account.did,
            "handle": owner.account.handle,
            "displayName": "The <Owner>",
            "description": "Code, mostly.",
            "pronouns": "she/her",
            "links": [{"url": "https://owner.example"}],
            "avatar": full,
            "followersCount": 0,
            "followsCount": 0,
            "badges": [
                {"kind": "forked-and-fixed", "tier": 1, "count": 2},
                {"kind": "first-light", "tier": 1, "count": 1},
            ],
        })
    );
    let through: Value = Client::new()
        .get(format!(
            "{}/xrpc/dev.knotless.getActorRepos?actor={}",
            site.base, owner.account.did
        ))
        .header("x-forwarded-host", "knotless.example")
        .header("x-forwarded-proto", "https")
        .send()
        .unwrap()
        .json()
        .unwrap();
    assert_eq!(
        through["repos"][0]["owner"]["avatar"],
        format!("https://knotless.example{shown}")
    );
    for method in ["getProfile", "getActorRepos"] {
        let asked = format!("/xrpc/dev.knotless.{method}?actor=nobody.invalid");
        let (status, said) = site.status(&asked);
        assert_eq!(status, 400, "{method}");
        let said: Value = serde_json::from_str(&said).unwrap();
        assert_eq!(said["error"], "ProfileNotFound", "{method}");
    }
    // Its cards show its picture where its initial was, cut to a circle:
    // a picture no renderer reads leaves the initial, but is what the
    // profile names, so the card is kept for good all the same. A new
    // picture names a new card.
    let tracked = format!("/{}/tracked", owner.account.handle);
    let unread = site.data(&tracked)["repo"]["card"]
        .as_str()
        .unwrap()
        .to_string();
    let (unread_png, cache) = card(&site, &unread);
    assert_eq!(cache, "public, max-age=31536000, immutable");
    let face = owner.path("face.png");
    std::fs::write(&face, one_color([200, 10, 20])).unwrap();
    ok(
        &owner,
        &["account", "profile", "--avatar", face.to_str().unwrap()],
    );
    wait_until(|| site.profile(&owner.account.did)["avatar"] != full);
    let pictured_card = site.data(&tracked)["repo"]["card"]
        .as_str()
        .unwrap()
        .to_string();
    assert_ne!(pictured_card, unread);
    let (png, cache) = card(&site, &pictured_card);
    assert_eq!(cache, "public, max-age=31536000, immutable");
    assert_eq!(color_at(&png, 112, 90), [200, 10, 20]);
    assert_ne!(color_at(&unread_png, 112, 90), [200, 10, 20]);
    let item_card = site.data(&issue_page)["card"].as_str().unwrap().to_string();
    assert_eq!(color_at(&card(&site, &item_card).0, 112, 90), [200, 10, 20]);
    // Taken out again, the Bluesky profile's shows.
    ok(
        &owner,
        &[
            "account", "profile", "--bio", "", "--link", "", "--avatar", "",
        ],
    );
    wait_until(|| site.profile(&owner.account.did)["avatar"] == pictured.as_str());
    let mine = site.profile(&owner.account.did);
    assert_eq!(mine["description"], "Keeps things tracked.");
    assert_eq!(mine["avatar"], pictured.as_str());
    let view = site.json(&format!(
        "/xrpc/dev.knotless.getProfile?actor={}",
        owner.account.did
    ));
    assert_eq!(view["avatar"], pictured.as_str());
    assert_eq!(view["description"], "Keeps things tracked.");
    assert_eq!(
        named(&mine["repos"], "tracked")["description"],
        "Tracks <things>"
    );
    assert_eq!(
        named(&mine["repos"], "tracked")["owner"]["handle"],
        owner.account.handle.as_str()
    );
    // The profile the index keeps changes as the firehose says, its links
    // with it.
    ok(&owner, &["account", "profile", "--bio", "Kept here."]);
    let shown = format!("/xrpc/dev.knotless.getProfile?actor={}", owner.account.did);
    wait_until(|| site.json(&shown)["description"] == "Kept here.");
    assert_eq!(site.json(&shown)["links"], kept);
    // A server with no index reads the same from the PDS.
    let read = serve(false).profile(&owner.account.did);
    assert_eq!(read["description"], "Kept here.");
    assert_eq!(read["links"], kept);
    assert!(names(&read["repos"]).contains(&"tracked"));
    ok(&owner, &["account", "profile", "--bio", ""]);
    wait_until(|| site.json(&shown)["description"] == "Keeps things tracked.");
    let nobody = person(account(), &site);
    let view = site.json(&format!(
        "/xrpc/dev.knotless.getProfile?actor={}",
        nobody.account.did
    ));
    assert_eq!(
        view,
        serde_json::json!({
            "did": nobody.account.did,
            "handle": nobody.account.handle,
            "followersCount": 0,
            "followsCount": 0,
        })
    );
    assert_eq!(
        site.profile(&nobody.account.handle)["repos"],
        serde_json::json!([])
    );

    // Renamed, it keeps its pulls and issues, even through the old name.
    // An answer asked for by the old name redirects to the same one under
    // the new, as does a raw file; and the fork names its source as it is
    // called now.
    ok(&owner, &["repo", "rename", &url, "tracked2"]);
    let moved = format!("/{}/tracked2", owner.account.handle);
    wait_until(|| {
        titles(&site.data(&format!("{moved}/issues"))).contains(&"Broken <thing>".to_string())
    });
    assert!(ok(&owner, &["pull", "list", &url]).contains(&format!("pull\tmerged\t{pull}")));
    let direct = Client::builder()
        .redirect(reqwest::redirect::Policy::none())
        .build()
        .unwrap();
    let old = |path: &str| direct.get(format!("{}{path}", site.base)).send().unwrap();
    // Once the firehose has told the server of the rename, its pulls by
    // the old name are the new one's.
    let closed = format!("{repo}/pulls?state=closed");
    wait_until(|| site.data(&closed)["repo"]["name"] == "tracked2");
    assert_eq!(site.data(&closed)["items"][0]["state"], "merged");
    let file = site.data(&moved)["entries"][0]["name"]
        .as_str()
        .unwrap()
        .to_string();
    assert_eq!(
        old(&format!("{repo}/raw/main/{file}")).headers()["location"],
        format!("{moved}/raw/main/{file}")
    );
    // A query by the old name answers under the new, which the pages
    // send the old address on to.
    let renamed = site.data(&repo);
    assert_eq!(renamed["repo"]["name"], "tracked2");
    assert_eq!(names(&renamed["entries"])[0], file.as_str());
    assert!(site.data(&thread)["merged"].is_string());
    wait_until(|| {
        site.data(&format!("/{}/tracked", other.account.handle))["repo"]["forkOf"]["name"]
            == "tracked2"
    });

    // A pull whose branch is gone shows no changes.
    other.git(&clone, &["push", "-q", "origin", "main:gone"]);
    let gone = ok(
        &other,
        &[
            "pull",
            "create",
            &owner.url("tracked2"),
            "main",
            &fork,
            "gone",
            "Gone",
        ],
    );
    ok(&other, &["branch", "delete", &fork, "gone"]);
    let gone = format!("{moved}/pull/{other_did}/{}", rkey(&gone));
    wait_until(|| site.status(&at(&gone)).0 == 200);
    wait_until(|| site.data(&gone)["changes"].is_null());

    // Without an index, there is no knowing.
    let bare = serve(false);
    for path in [
        "/xrpc/dev.knotless.getFeed".to_string(),
        "/xrpc/dev.knotless.listRepos?q=x".to_string(),
        "/xrpc/dev.knotless.getActorActivity?actor=did:plc:x".to_string(),
        "/xrpc/dev.knotless.listActorItems?actor=did:plc:x&kind=pull".to_string(),
        at(&format!("{moved}/pulls")),
    ] {
        let (status, body) = bare.status(&path);
        assert_eq!(status, 404, "{path}");
        assert!(body.contains("keeps no index"), "{path}: {body}");
    }
}

/// The events about `handle`'s repository `name`, newest first.
fn about<'a>(feed: &'a Value, handle: &str, name: &str) -> Vec<&'a Value> {
    feed.as_array()
        .unwrap()
        .iter()
        .filter(|e| e["repo"]["handle"] == handle && e["repo"]["name"] == name)
        .collect()
}

/// The events about `handle`'s repository `fed` that moved `refname`.
fn on_ref<'a>(feed: &'a Value, handle: &str, refname: &str) -> Vec<&'a Value> {
    let events = about(feed, handle, "fed").into_iter();
    events.filter(|e| e["action"]["ref"] == refname).collect()
}

/// Whether one of `events` did `kind`, with each field as `fields` has it.
fn did(events: &[&Value], kind: &str, fields: Value) -> bool {
    events.iter().any(|e| {
        let a = &e["action"];
        a["$type"] == format!("dev.knotless.getFeed#{kind}")
            && fields.as_object().unwrap().iter().all(|(k, v)| &a[k] == v)
    })
}

#[test]
fn a_forced_push() {
    use serde_json::json;
    let site = serve(true);
    let owner = person(account(), &site);
    let url = owner.url("forced");
    ok(&owner, &["repo", "create", &url]);
    let work = owner.work("w", 1);
    owner.commit_file(&work, "a", "a\n");
    owner.git(&work, &["push", "-q", &url, "main"]);
    // The last commit again, its tree changed as a rebase changes it but
    // by the same author at the same time saying the same, and one more:
    // only that one is new.
    std::fs::write(work.join("a"), "a, again\n").unwrap();
    owner.git(&work, &["commit", "-q", "-a", "--amend", "--no-edit"]);
    let b = owner.commit_file(&work, "b", "b\n");
    owner.git(&work, &["push", "-q", "-f", &url, "main"]);
    // A page of one is the forced push alone, without the push before it.
    let did = &owner.account.did;
    let newest = || {
        let page = site.json(&format!("/xrpc/dev.knotless.getFeed?limit=1&actor={did}"));
        page["events"][0]["action"].clone()
    };
    wait_until(|| newest()["forced"] == true);
    let pushed = newest();
    assert_eq!(
        (&pushed["created"], &pushed["count"], &pushed["new"]),
        (&json!(false), &json!(1), &json!(1)),
        "{pushed}"
    );
    assert_eq!(pushed["commits"][0]["sha"], b.as_str());
    // Where it left the ref, for a link that outlives the ref.
    assert_eq!(pushed["to"], b.as_str());
    // One that only moved forward was not forced.
    owner.commit_file(&work, "c", "c\n");
    owner.git(&work, &["push", "-q", &url, "main"]);
    wait_until(|| newest()["count"] == 1 && newest().get("forced").is_none());
}

#[test]
fn the_feed() {
    use serde_json::json;
    let site = serve(true);
    let owner = person(account(), &site);
    let other = person(account(), &site);
    let (o, t) = (&owner.account.handle, &other.account.handle);
    let us = [owner.account.did.as_str(), other.account.did.as_str()];
    // Other tests' pushes and pulls share the feed, so it is read back to
    // where it was as this began: once it shows `last`, it must show each
    // of `wants`.
    let newest = site.json("/xrpc/dev.knotless.getFeed?limit=1");
    let since = newest["events"][0]["createdAt"].as_str().unwrap_or("");
    let shows = |handle: &str, last: (&str, Value), wants: &[(&str, Value)]| {
        wait_until(|| {
            did(
                &about(&site.feed(since, &us), handle, "fed"),
                last.0,
                last.1.clone(),
            )
        });
        let feed = site.feed(since, &us);
        let events = about(&feed, handle, "fed");
        for (kind, fields) in wants {
            assert!(
                did(&events, kind, fields.clone()),
                "no {kind} {fields} in {events:#?}"
            );
        }
        feed
    };

    let url = owner.url("fed");
    ok(
        &owner,
        &["repo", "create", &url, "--description", "Fed <to> the feed"],
    );
    let work = owner.work("w", 1);
    owner.git(&work, &["push", "-q", &url, "main"]);
    // Pushes to a ref close together are one, from where the first found
    // it to where the last left it, so one back to where it was counts
    // only what stayed.
    for i in 0..6 {
        owner.commit_file(&work, "f", &format!("{i}\n"));
    }
    owner.git(&work, &["push", "-q", &url, "main"]);
    owner.git(&work, &["push", "-q", "-f", &url, "HEAD~2:main"]);
    owner.git(&work, &["checkout", "-q", "-b", "side"]);
    owner.commit_file(&work, "s", "s\n");
    owner.git(&work, &["push", "-q", &url, "side"]);
    // A commit another ref had first is counted, not listed again; nor is
    // one listed by an earlier event, though main was pushed first.
    owner.git(&work, &["tag", "v1"]);
    owner.git(&work, &["push", "-q", &url, "v1"]);
    owner.git(&work, &["push", "-q", &url, "side:main"]);
    owner.git(&work, &["push", "-q", &url, ":side"]);
    let feed = shows(
        o,
        ("deleted", json!({"ref": "refs/heads/side"})),
        &[
            ("created", json!({"description": "Fed <to> the feed"})),
            (
                "pushed",
                json!({"ref": "refs/heads/main", "created": true, "count": 8, "new": 5}),
            ),
            (
                "pushed",
                json!({"ref": "refs/heads/side", "created": true, "count": 3, "new": 3}),
            ),
            (
                "pushed",
                json!({"ref": "refs/tags/v1", "created": true, "count": 3, "new": 0, "commits": []}),
            ),
        ],
    );
    let on = |r: &str| on_ref(&feed, o, r);
    let (main, side) = (on("refs/heads/main"), on("refs/heads/side"));
    assert_eq!((main.len(), side.len()), (1, 2));
    // None of what side listed.
    let shas = |e: &Value| {
        e["action"]["commits"]
            .as_array()
            .unwrap()
            .iter()
            .map(|c| c["sha"].as_str().unwrap().to_string())
            .collect::<Vec<_>>()
    };
    let (main_shas, side_shas) = (shas(main[0]), shas(side[1]));
    assert_eq!((main_shas.len(), side_shas.len()), (5, 3));
    assert!(main_shas.iter().all(|sha| !side_shas.contains(sha)));
    assert_eq!(main[0]["actor"]["handle"], o.as_str());
    assert_eq!(main[0]["actor"]["did"], owner.account.did.as_str());

    // A merge says it is one; a commit with one parent says nothing.
    owner.git(&work, &["checkout", "-q", "-b", "joined"]);
    owner.commit_file(&work, "j", "j\n");
    owner.git(&work, &["checkout", "-q", "-b", "other", "HEAD~1"]);
    owner.commit_file(&work, "o", "o\n");
    owner.git(&work, &["merge", "-q", "--no-ff", "-m", "Join", "joined"]);
    owner.git(&work, &["push", "-q", &url, "other"]);
    let feed = shows(o, ("pushed", json!({"ref": "refs/heads/other"})), &[]);
    let joined = on_ref(&feed, o, "refs/heads/other");
    let merges: Vec<_> = joined[0]["action"]["commits"]
        .as_array()
        .unwrap()
        .iter()
        .map(|c| (c["message"].as_str().unwrap().trim(), c.get("merge")))
        .collect();
    assert_eq!(merges[0], ("Join", Some(&json!(true))), "{merges:?}");
    assert!(
        merges[1..].iter().all(|(_, merge)| merge.is_none()),
        "{merges:?}"
    );

    let fork = ok(&other, &["repo", "fork", &url]);
    let clone = other.path("clone");
    other.git(other.dir.path(), &["clone", "-q", &fork, "clone"]);
    other.commit_file(&clone, "b", "b\n");
    other.git(&clone, &["push", "-q", "origin", "main:topic"]);
    let feed = shows(
        t,
        (
            "pushed",
            json!({"ref": "refs/heads/topic", "created": true, "count": 1}),
        ),
        &[(
            "created",
            json!({"forkOf": {"did": owner.account.did, "handle": o, "name": "fed"}}),
        )],
    );
    // A fork's copy of its source's branches is the fork itself.
    assert!(!did(
        &about(&feed, t, "fed"),
        "pushed",
        json!({"ref": "refs/heads/main"})
    ));

    let pull = ok(
        &other,
        &["pull", "create", &url, "main", &fork, "topic", "Add b"],
    );
    let issue = ok(&other, &["issue", "create", &url, "Broken <thing>"]);
    ok(
        &owner,
        &["comment", "add", &url, &pull, "Not *yet*", "--changes"],
    );
    ok(
        &owner,
        &["comment", "add", &url, &pull, "Fixed", "--approve"],
    );
    ok(&owner, &["comment", "add", &url, &issue, "Seen it"]);
    ok(&owner, &["issue", "close", &url, &issue]);
    ok(&owner, &["issue", "reopen", &url, &issue]);
    let (pull, issue) = (pull.trim(), issue.trim());
    wait_until(|| {
        did(
            &about(&site.feed(since, &us), o, "fed"),
            "changed",
            json!({"subject": issue, "state": "open"}),
        )
    });
    ok(&owner, &["pull", "merge", &url, pull]);
    let feed = shows(
        o,
        ("changed", json!({"subject": pull, "state": "merged"})),
        &[
            // Each says where its subject stands now, not then: the pull
            // merged, and the issue, closed and reopened above, open.
            (
                "opened",
                json!({"subject": pull, "title": "Add b", "subjectState": "merged"}),
            ),
            (
                "commented",
                json!({"subject": pull, "verdict": "changes", "text": "Not *yet*", "subjectState": "merged"}),
            ),
            (
                "commented",
                json!({"subject": pull, "verdict": "approve", "title": "Add b"}),
            ),
            (
                "opened",
                json!({"subject": issue, "title": "Broken <thing>", "subjectState": "open"}),
            ),
            (
                "commented",
                json!({"subject": issue, "text": "Seen it", "title": "Broken <thing>"}),
            ),
            (
                "changed",
                json!({"subject": issue, "state": "closed", "subjectState": "open"}),
            ),
        ],
    );
    // Opened, said and decided about, by title.
    let titled = |title: &str| {
        about(&feed, o, "fed")
            .iter()
            .filter(|e| e["action"]["title"] == title)
            .count()
    };
    assert_eq!((titled("Add b"), titled("Broken <thing>")), (4, 4));
    // A comment without a verdict has none to say.
    assert!(
        about(&feed, o, "fed")
            .iter()
            .any(|e| e["action"]["text"] == "Seen it" && e["action"].get("verdict").is_none())
    );
    // At most forty, unless asked for more or fewer; at least one. A page
    // is the newest records less those not shown, such as another test's
    // fork's copy of its source's refs, so it may hold fewer, but it
    // always ends where the next begins.
    let page = |q: &str| site.json(&format!("/xrpc/dev.knotless.getFeed{q}"));
    assert!(page("")["events"].as_array().unwrap().len() <= 40);
    for q in ["?limit=1", "?limit=0"] {
        let one = page(q);
        assert!(one["events"].as_array().unwrap().len() <= 1, "{q}: {one}");
        assert!(one["cursor"].is_string(), "{q}: {one}");
    }
    // Page by page, each starting strictly after the last.
    let first = page("?limit=2");
    let cursor = first["cursor"].as_str().unwrap().replace(' ', "%20");
    let next = page(&format!("?limit=1&cursor={cursor}"));
    for after in next["events"].as_array().unwrap() {
        for shown in first["events"].as_array().unwrap() {
            assert!(after["createdAt"].as_str() <= shown["createdAt"].as_str());
            assert_ne!(after, shown);
        }
    }
    assert!(names(&site.search("FED")["repos"]).contains(&"fed"));

    // Of one kind only: a page of nothing else, and of each but stars,
    // which only one account's or whom one follows' hold, some.
    let did = &other.account.did;
    let of = |e: &Value| {
        let a = &e["action"];
        let on = |n: &str| a["subject"].as_str().unwrap_or("").contains(n);
        match a["$type"].as_str().unwrap().split('#').nth(1).unwrap() {
            "pushed" | "deleted" => "pushes",
            "created" => "repos",
            "commented" => "comments",
            "starred" => "stars",
            _ if on("/dev.knotless.pull/") => "pulls",
            _ => "issues",
        }
    };
    // The pull's author, who reviews nothing of their own, says something.
    ok(&other, &["comment", "add", &url, issue, "Still broken"]);
    let comments = || page(&format!("?actor={did}&kind=comments"))["events"].clone();
    wait_until(|| comments().as_array().is_some_and(|e| !e.is_empty()));
    for kind in ["pushes", "pulls", "issues", "comments", "repos"] {
        let only = page(&format!("?actor={did}&kind={kind}"));
        let events = only["events"].as_array().unwrap();
        assert!(!events.is_empty(), "{kind}: {only}");
        assert!(events.iter().all(|e| of(e) == kind), "{kind}: {only}");
    }
    assert_eq!(page("?kind=stars")["events"], json!([]));
    assert_eq!(page("?kind=gists")["events"], json!([]));

    // How much each did, a day at a time, and each repository's pushes.
    let activity = |did: &str, q: &str| {
        site.json(&format!(
            "/xrpc/dev.knotless.getActorActivity?actor={did}{q}"
        ))
    };
    let total = |a: &Value, kind: &str| -> u64 {
        let days = a["days"].as_array().unwrap().iter();
        days.map(|d| d[kind].as_u64().unwrap()).sum()
    };
    let kinds = ["repos", "pulls", "issues", "comments"];
    let mine = activity(&owner.account.did, "");
    let counted = kinds.map(|k| total(&mine, k));
    assert_eq!(counted, [1, 0, 0, 3], "{mine}");
    let theirs = activity(&other.account.did, "");
    assert_eq!(kinds.map(|k| total(&theirs, k)), [1, 1, 1, 1], "{theirs}");
    assert!(total(&theirs, "pushes") > 0);
    // Pushes, by the day and by the repository, agree.
    let fed = &mine["repos"][0];
    assert_eq!(
        (mine["repos"].as_array().unwrap().len(), &fed["name"]),
        (1, &json!("fed"))
    );
    let by_day = fed["days"].as_array().unwrap().iter();
    let pushed: u64 = by_day.map(|d| d["pushes"].as_u64().unwrap()).sum();
    assert!(pushed > 0, "{mine}");
    assert_eq!(pushed, total(&mine, "pushes"));
    // Since a day to come, nothing done yet, but the last push still known.
    let later = activity(&owner.account.did, "&since=2999-01-01T00:00:00Z");
    assert_eq!(later["days"], json!([]));
    assert_eq!(later["repos"][0]["days"], json!([]));
    assert_eq!(later["repos"][0]["pushedAt"], fed["pushedAt"]);
    assert_eq!(
        activity(&owner.account.did, "&since=2000-01-01T00:00:00Z"),
        mine
    );
    // Days in another zone fall elsewhere, but count the same.
    let zoned = activity(&owner.account.did, "&tz=Pacific/Kiritimati");
    let all = ["repos", "pushes", "pulls", "issues", "comments"];
    assert_eq!(all.map(|k| total(&zoned, k)), all.map(|k| total(&mine, k)));
}

/// The pages: anything knotless-serve does not answer itself is handed to
/// the app at `--web`, as asked, with the host it was asked of; and a
/// failure to reach it is asked over if a read, and said.
#[test]
fn pages_drawn_elsewhere() {
    use axum::http::HeaderMap;
    use axum::response::IntoResponse;
    let rt = tokio::runtime::Runtime::new().unwrap();
    let listen = || {
        let listener = rt
            .block_on(tokio::net::TcpListener::bind("127.0.0.1:0"))
            .unwrap();
        let at = format!("http://{}", listener.local_addr().unwrap());
        (listener, at)
    };
    // An app that says what it was asked.
    let (app, at) = listen();
    let echo = axum::Router::new().fallback(
        |method: axum::http::Method, uri: axum::http::Uri, headers: HeaderMap, body: String| async move {
            let header = |name: &str| headers.get(name).map(|v| v.to_str().unwrap().to_string());
            let said = serde_json::json!({
                "method": method.as_str(),
                "at": uri.to_string(),
                "host": header("x-forwarded-host"),
                "proto": header("x-forwarded-proto"),
                "body": body,
            });
            let policy = if uri.path() == "/own" { "no-referrer" } else { "" };
            let mut res = axum::Json(said).into_response();
            if !policy.is_empty() {
                res.headers_mut()
                    .insert("referrer-policy", policy.parse().unwrap());
            }
            if uri.path() == "/packed" {
                res.headers_mut()
                    .insert("content-encoding", "zstd".parse().unwrap());
            }
            res
        },
    );
    // An app that drops the first connection it takes, then answers.
    let dropping = |echo: axum::Router| {
        let (app, at) = listen();
        rt.spawn(async move {
            drop(app.accept().await.unwrap());
            axum::serve(app, echo).await.unwrap()
        });
        at
    };
    let (reread, rewritten) = (dropping(echo.clone()), dropping(echo.clone()));
    rt.spawn(async move { axum::serve(app, echo).await.unwrap() });

    let s = Sandbox::new();
    let front = |web: String| {
        let (listener, base) = listen();
        let app: Arc<App> = App::build(ctx(&s), Duration::ZERO, None, None);
        let routes = router(app).merge(knotless_serve::web::pages(web));
        rt.spawn(async move { axum::serve(listener, routes).await.unwrap() });
        base
    };
    let base = front(format!("{at}/"));
    let client = Client::new();
    let res = client
        .post(format!("{base}/alice.test/tally?x=1"))
        .header("host", "pages.example")
        .header("x-forwarded-proto", "https")
        .body("hi")
        .send()
        .unwrap();
    assert_eq!(res.status().as_u16(), 200);
    assert_eq!(res.headers()["x-content-type-options"], "nosniff");
    assert_eq!(res.headers()["referrer-policy"], "same-origin");
    let said: Value = res.json().unwrap();
    assert_eq!(
        said,
        serde_json::json!({
            "method": "POST",
            "at": "/alice.test/tally?x=1",
            "host": "pages.example",
            "proto": "https",
            "body": "hi",
        })
    );
    // What the app says of itself stands.
    let own = client.get(format!("{base}/own")).send().unwrap();
    assert_eq!(own.headers()["referrer-policy"], "no-referrer");
    assert_eq!(own.json::<Value>().unwrap()["proto"], "http");
    // A page goes compressed to a browser that takes brotli; what the app
    // compressed itself goes as it is.
    let squeezed = |at: &str| {
        let res = client
            .get(format!("{base}{at}"))
            .header("accept-encoding", "br")
            .send()
            .unwrap();
        res.headers()["content-encoding"]
            .to_str()
            .unwrap()
            .to_string()
    };
    assert_eq!(squeezed("/alice.test/tally"), "br");
    assert_eq!(squeezed("/packed"), "zstd");
    // A page is sent on as the app sends it, compressed or not: its first
    // part reaches the browser while the app still holds the rest.
    let holding = || {
        let (app, at) = listen();
        let gate = Arc::new(tokio::sync::Notify::new());
        let held = gate.clone();
        let page = axum::Router::new().fallback(move || {
            let held = held.clone();
            async move {
                let parts = futures::stream::unfold(0, move |n| {
                    let held = held.clone();
                    async move {
                        if n == 1 {
                            held.notified().await;
                        }
                        let part = ["first ", "rest"].get(n)?.to_string();
                        Some((Ok::<_, std::io::Error>(part), n + 1))
                    }
                });
                let mut res = axum::body::Body::from_stream(parts).into_response();
                let html = "text/html".parse().unwrap();
                res.headers_mut().insert("content-type", html);
                res
            }
        });
        rt.spawn(async move { axum::serve(app, page).await.unwrap() });
        (gate, at)
    };
    let patient = Client::builder()
        .timeout(Duration::from_secs(30))
        .build()
        .unwrap();
    for encoding in ["identity", "br"] {
        let (gate, at) = holding();
        let mut res = patient
            .get(format!("{}/page", front(at)))
            .header("accept-encoding", encoding)
            .send()
            .unwrap();
        let mut first = [0; 64];
        let n = std::io::Read::read(&mut res, &mut first).unwrap();
        assert!(n > 0, "{encoding}: nothing before the rest");
        gate.notify_one();
        let mut all = first[..n].to_vec();
        std::io::Read::read_to_end(&mut res, &mut all).unwrap();
        let sent = res.headers().get("content-encoding").cloned();
        if encoding == "identity" {
            assert!(b"first ".starts_with(&first[..n]), "{encoding}");
            assert_eq!((all.as_slice(), sent), (&b"first rest"[..], None));
        } else {
            assert_eq!(sent.unwrap(), "br");
        }
    }
    // What knotless-serve answers itself is not handed on.
    let ours = client
        .get(format!("{base}/xrpc/dev.knotless.getFeed"))
        .send()
        .unwrap();
    assert!(ours.text().unwrap().contains("keeps no index"));
    // Nor is `/favicon.ico`, which would be drawn as a handle's profile: it
    // goes to the pages' icon.
    let staying = Client::builder()
        .redirect(reqwest::redirect::Policy::none())
        .build()
        .unwrap();
    let icon = staying.get(format!("{base}/favicon.ico")).send().unwrap();
    assert_eq!(icon.status().as_u16(), 308);
    assert_eq!(icon.headers()["location"], "/favicon.svg");
    // A crawler is kept off what robots.txt keeps it off, whether it read
    // it or not: by the longest rule, with its escapes or without. A
    // browser, or a preview of a link shared, is not.
    const GPT: &str = "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; GPTBot/1.4)";
    const SLACK: &str = "Slackbot-LinkExpanding 1.0 (+https://api.slack.com/robots)";
    const BROWSER: &str = "Mozilla/5.0 (Macintosh) AppleWebKit/605.1.15 Safari/605.1.15";
    for (agent, at, status) in [
        (
            GPT,
            "/alice.test/tally/issues?q=label:area/serve%20author:bob",
            403,
        ),
        (
            GPT,
            "/alice.test/tally/pulls/__data.json?x-sveltekit-invalidated=01",
            403,
        ),
        (GPT, "/alice.test/tally/tree/dev/src", 403),
        (GPT, "/search?q=x", 403),
        (GPT, "/alice.test/tally/issues?q=state:closed", 200),
        (
            GPT,
            "/alice.test/tally/issues?q=state%3Aclosed&cursor=2",
            200,
        ),
        (
            GPT,
            "/alice.test/tally/issues?q=state%3Aclosed%20label:x",
            403,
        ),
        (GPT, "/alice.test/tally/issues", 200),
        (GPT, "/alice.test/tally/tree/main/src", 200),
        (GPT, "/alice.test", 200),
        (SLACK, "/alice.test/tally/commit/abc", 200),
        (BROWSER, "/alice.test/tally/issues?q=label:x", 200),
    ] {
        let res = client
            .get(format!("{base}{at}"))
            .header("user-agent", agent)
            .send()
            .unwrap();
        assert_eq!(res.status().as_u16(), status, "{agent} at {at}");
        if status == 403 {
            assert!(res.text().unwrap().contains("robots.txt"));
        }
    }
    // Too much to hand on, or no app to hand it to.
    // The answer can come before all of it is sent, which closes the
    // connection under the rest.
    let big = client
        .post(format!("{base}/x"))
        .body(vec![b'x'; 2 << 20])
        .send();
    match big {
        Ok(res) => assert_eq!(res.status().as_u16(), 502),
        Err(e) => assert!(e.is_request(), "{e}"),
    }
    // A connection lost is asked over on a read, and not on a write.
    let res = client.get(format!("{}/x", front(reread))).send().unwrap();
    assert_eq!(res.status().as_u16(), 200);
    let written = front(rewritten);
    let res = client.post(format!("{written}/x")).send().unwrap();
    assert_eq!(res.status().as_u16(), 502);
    let res = client.post(format!("{written}/x")).send().unwrap();
    assert_eq!(res.status().as_u16(), 200);
    let (gone, nowhere) = listen();
    drop(gone);
    let res = client.get(front(nowhere)).send().unwrap();
    assert_eq!(res.status().as_u16(), 502);
    assert!(
        res.text()
            .unwrap()
            .starts_with("The pages are not being drawn")
    );
}

#[test]
fn the_merge_queue() {
    let site = serve(true);
    let owner = person(account(), &site);
    let url = owner.url("lined");
    ok(&owner, &["repo", "create", &url]);
    ok(&owner, &["runner", "add", &url, &owner.account.handle]);
    let work = owner.work("w", 1);
    owner.git(&work, &["push", "-q", &url, "main"]);
    let repo = format!("/{}/lined", owner.account.handle);
    let mut pulls = Vec::new();
    for b in ["a", "b", "c", "d", "e", "f"] {
        owner.git(&work, &["checkout", "-q", "-B", b, "main"]);
        owner.commit_file(&work, b, &format!("{b}\n"));
        owner.git(&work, &["push", "-q", &url, b]);
        pulls.push(ok(
            &owner,
            &[
                "pull",
                "create",
                &url,
                "main",
                &url,
                b,
                &format!("Pull {b}"),
            ],
        ));
    }
    // g, into another branch.
    owner.git(&work, &["push", "-q", &url, "main:feature/x"]);
    owner.git(&work, &["checkout", "-q", "-B", "g", "main"]);
    owner.commit_file(&work, "g", "g\n");
    owner.git(&work, &["push", "-q", &url, "g"]);
    pulls.push(ok(
        &owner,
        &["pull", "create", &url, "feature/x", &url, "g", "Pull g"],
    ));
    let issue = ok(&owner, &["issue", "create", &url, "Not a pull"]);
    let page = |uri: &str| {
        let (did, _, rkey) = knotless::forge::parse_uri(uri).unwrap();
        let kind = if uri.contains(".pull/") {
            "pull"
        } else {
            "issue"
        };
        format!("{repo}/{kind}/{did}/{rkey}")
    };
    let key = owner.record("dev.knotless.repo", "lined")["key"].clone();
    let reference = serde_json::json!({
        "uri": format!("at://{}/dev.knotless.repo/lined", owner.account.did), "key": key,
    });
    let did = owner.account.did.clone();
    let check_uri = |rkey: &str| format!("at://{did}/dev.knotless.check/{rkey}");
    // a, b and c queued, in that order, and e; g into feature/x between b
    // and c; d ejected by the checks of a batch it was in, one of which the
    // index knows, and f before it.
    for (at, p) in [
        ("01.000", 0),
        ("02.000", 1),
        ("02.500", 6),
        ("03.000", 2),
        ("05.000", 4),
    ] {
        let queued = serde_json::json!({
            "$type": "dev.knotless.queue", "repo": reference, "subject": pulls[p],
            "state": "queued", "createdAt": format!("2026-10-21T00:00:{at}Z"),
        });
        owner.put_record("dev.knotless.queue", &knotless::keys::tid(), queued);
    }
    let ejected = serde_json::json!({
        "$type": "dev.knotless.queue", "repo": reference, "subject": pulls[3],
        "state": "ejected", "reason": "its batch failed its checks",
        "checks": [
            {"workflow": "test", "status": "failure", "check": check_uri("q1")},
            {"workflow": "gone", "status": "error", "check": check_uri("q0")},
        ],
        "createdAt": "2026-10-21T00:00:04.000Z",
    });
    owner.put_record("dev.knotless.queue", &knotless::keys::tid(), ejected);
    let earlier = serde_json::json!({
        "$type": "dev.knotless.queue", "repo": reference, "subject": pulls[5],
        "state": "ejected", "reason": "it conflicts with main",
        "createdAt": "2026-10-21T00:00:00.000Z",
    });
    owner.put_record("dev.knotless.queue", &knotless::keys::tid(), earlier);
    // e lands, as the batch of its own that passed.
    let merged = ok(&owner, &["pull", "merge", &url, &pulls[4]]);
    let merged = merged.lines().next().unwrap().to_string();
    let batched = |p: usize, commit: &str| serde_json::json!({"pull": pulls[p], "commit": commit});
    let run = |rkey: &str, commit: &str, batch: Value, status: &str, at: &str| {
        let finished = (status != "running").then(|| format!("2026-10-22T0{at}:30:00.000Z"));
        owner.put_record(
            "dev.knotless.check",
            rkey,
            serde_json::json!({
                "$type": "dev.knotless.check", "repo": reference, "ref": "refs/heads/main",
                "commit": commit, "baseCommit": "b".repeat(40), "batch": batch,
                "workflow": "test", "status": status,
                "startedAt": format!("2026-10-22T0{at}:00:00.000Z"), "finishedAt": finished,
            }),
        );
    };
    let (t1, tx) = ("1".repeat(40), "2".repeat(40));
    run(
        "q1",
        &tx,
        serde_json::json!([batched(3, &tx)]),
        "failure",
        "1",
    );
    run(
        "q2",
        &merged,
        serde_json::json!([batched(4, &merged)]),
        "success",
        "2",
    );
    let ab = serde_json::json!([batched(0, &"3".repeat(40)), batched(1, &t1)]);
    run("q3", &t1, ab, "running", "3");
    // c's pre-flight, running at its head.
    owner.put_record(
        "dev.knotless.check",
        "q4",
        serde_json::json!({
            "$type": "dev.knotless.check", "repo": reference, "ref": "refs/heads/c",
            "commit": "4".repeat(40), "pull": pulls[2].trim(), "queue": "first",
            "workflow": "test", "status": "running", "startedAt": "2026-10-22T04:00:00.000Z",
        }),
    );

    let queue = format!("{repo}/queue");
    wait_until(|| {
        let q = site.data(&queue);
        q["landed"].as_array().is_some_and(|l| !l.is_empty())
            && q["branches"][1]["batches"]
                .as_array()
                .is_some_and(|b| !b.is_empty())
            && q["ejected"][0]["checks"]
                .as_array()
                .is_some_and(|c| !c.is_empty())
            && q["branches"][1]["pulls"][2]["stage"] == "preflight"
    });
    let q = site.data(&queue);
    // One line a branch, by name, though their pulls were queued between
    // each other's.
    let names: Vec<&Value> = q["branches"]
        .as_array()
        .unwrap()
        .iter()
        .map(|b| &b["branch"])
        .collect();
    assert_eq!(names, ["feature/x", "main"]);
    assert_eq!(q["branches"][0]["pulls"][0]["said"], "Queued, 1st of 1");
    let line = &q["branches"][1];
    let said: Vec<(&str, &str, &Value)> = (line["pulls"].as_array().unwrap().iter())
        .map(|p| {
            let title = p["pull"]["title"].as_str().unwrap();
            (title, p["said"].as_str().unwrap(), &p["batch"])
        })
        .collect();
    assert_eq!(
        said,
        [
            ("Pull a", "In a batch of 2 being checked", &Value::from(0)),
            ("Pull b", "In a batch of 2 being checked", &Value::from(0)),
            (
                "Pull c",
                "Queued, 3rd of 3, running its checks",
                &Value::Null
            ),
        ]
    );
    // Each says where it stands, with its pre-flights' runs until it is in
    // a batch; and the queue how long it takes, as the batch that landed
    // did, its pre-flights too with none of them passed.
    let stages: Vec<&Value> = (line["pulls"].as_array().unwrap().iter())
        .map(|p| &p["stage"])
        .collect();
    assert_eq!(stages, ["checking", "checking", "preflight"]);
    assert_eq!(line["pulls"][2]["checks"][0]["workflow"], "test");
    assert_eq!(line["pulls"][0]["checks"].as_array().map(Vec::len), Some(0));
    let typical = serde_json::json!({"batchSeconds": 1800, "preflightSeconds": 1800});
    assert_eq!(q["typical"], typical);
    assert_eq!(q["branches"][0]["pulls"][0]["stage"], "queued");
    assert_eq!(line["pulls"][0]["queuedAt"], "2026-10-21T00:00:01.000Z");
    assert_eq!(line["pulls"][0]["pull"]["uri"], pulls[0].trim());
    let batch = &line["batches"][0];
    assert_eq!(
        (&batch["commit"], &batch["state"]),
        (&Value::from(t1.as_str()), &Value::from("checking"))
    );
    assert_eq!(batch["size"], 2);
    assert_eq!(batch["startedAt"], "2026-10-22T03:00:00.000Z");
    assert_eq!(batch["checks"][0]["status"], "running");
    let e = &q["ejected"][0];
    assert_eq!(e["pull"]["title"], "Pull d");
    assert_eq!(e["reason"], "its batch failed its checks");
    assert_eq!(e["checks"].as_array().unwrap().len(), 1);
    assert_eq!(
        check_page(&e["checks"][0]),
        format!("{repo}/check/{did}/q1")
    );
    assert_eq!(q["ejected"][1]["pull"]["title"], "Pull f");
    let landed = &q["landed"][0];
    assert_eq!(landed["commit"], merged.as_str());
    assert_eq!(landed["branch"], "main");
    assert_eq!(landed["landedAt"], "2026-10-22T02:30:00.000Z");
    assert_eq!(landed["pulls"][0]["title"], "Pull e");

    // The pull page says where it stands, and links here: with its batch's
    // checks while that is checked, those that failed once ejected.
    let a = site.data(&page(&pulls[0]))["queue"].clone();
    assert_eq!(a["said"], "In a batch of 2 being checked");
    assert_eq!(
        check_page(&a["checks"][0]),
        format!("{repo}/check/{did}/q3")
    );
    let c = site.data(&page(&pulls[2]))["queue"].clone();
    assert_eq!(
        (c["said"].as_str(), c["checks"].as_array().map(Vec::len)),
        (Some("Queued, 3rd of 3, running its checks"), Some(1))
    );
    assert_eq!(c["stage"], "preflight");
    assert_eq!(c["typical"], typical);
    assert_eq!(c["aheadStartedAt"], "2026-10-22T03:00:00.000Z");
    assert_eq!(a["batchStartedAt"], "2026-10-22T03:00:00.000Z");
    let d = site.data(&page(&pulls[3]))["queue"].clone();
    assert_eq!(d["state"], "ejected");
    assert_eq!(
        d["said"],
        "Ejected: its batch failed its checks. Failed: test (failure), gone (error)"
    );
    assert_eq!(d["checks"][0]["workflow"], "test");
    assert!(site.data(&page(&issue))["queue"].is_null());
    assert!(site.data(&page(&pulls[4]))["queue"].is_null());
}

#[test]
fn the_runs() {
    let site = serve(true);
    let owner = person(account(), &site);
    let url = owner.url("ran");
    ok(&owner, &["repo", "create", &url]);
    ok(&owner, &["runner", "add", &url, &owner.account.handle]);
    let work = owner.work("w", 1);
    // A workflow run on pushes, one run only when asked, one that does not
    // read as one, and a file that is not one.
    let flows = [
        (
            "ci.yml",
            "# Builds and tests. On every push.\nsteps: [{run: make}]\n",
        ),
        (
            "audit.yaml",
            "# Checks the docs\n# against the code.\non: {asked: {}}\nsteps: [{run: x}]\n",
        ),
        ("broken.yml", "on: {nope: 1}\nsteps: []\n"),
        ("notes.md", "# Not a workflow.\n"),
    ];
    for (name, text) in flows {
        write(
            &work,
            &format!(".knotless/workflows/{name}"),
            text.as_bytes(),
        );
    }
    owner.git(&work, &["add", "-A"]);
    owner.git(&work, &["commit", "-q", "-m", "Workflows\n\nAnd more."]);
    let tip = owner.git(&work, &["rev-parse", "HEAD"]);
    owner.git(&work, &["push", "-q", &url, "main"]);
    owner.git(&work, &["checkout", "-q", "-b", "topic"]);
    owner.commit_file(&work, "t", "t\n");
    owner.git(&work, &["push", "-q", &url, "topic"]);
    let pull = ok(
        &owner,
        &["pull", "create", &url, "main", &url, "topic", "A topic"],
    );
    let pull = pull.trim().to_string();
    let key = owner.record("dev.knotless.repo", "ran")["key"].clone();
    let reference = serde_json::json!({
        "uri": format!("at://{}/dev.knotless.repo/ran", owner.account.did), "key": key,
    });
    let run = |rkey: &str, workflow: &str, commit: &str, at: u8, more: Value| {
        let mut check = serde_json::json!({
            "$type": "dev.knotless.check", "repo": reference, "ref": "refs/heads/main",
            "commit": commit, "workflow": workflow, "status": "success",
            "startedAt": format!("2026-10-22T0{at}:00:00.000Z"),
            "finishedAt": format!("2026-10-22T0{at}:01:30.000Z"),
        });
        check
            .as_object_mut()
            .unwrap()
            .extend(more.as_object().unwrap().clone());
        owner.put_record("dev.knotless.check", rkey, check);
    };
    let none = serde_json::json!({});
    run("r1", "ci", &tip, 1, none.clone());
    let of_pull = serde_json::json!({"ref": "refs/heads/topic", "pull": pull, "status": "failure"});
    run("r2", "ci", &"1".repeat(40), 2, of_pull);
    let batch = serde_json::json!({"batch": [{"pull": pull, "commit": "2".repeat(40)}]});
    run("r3", "ci", &"2".repeat(40), 3, batch);
    run("r4", "audit", &"3".repeat(40), 4, none);

    let page = format!("/{}/ran/checks", owner.account.handle);
    wait_until(|| {
        site.data(&page)["runs"]
            .as_array()
            .is_some_and(|r| r.len() == 4)
    });
    let shown = site.data(&page);
    let runs = shown["runs"].as_array().unwrap();
    let rkeys: Vec<&str> = runs
        .iter()
        .map(|r| {
            r["check"]["uri"]
                .as_str()
                .unwrap()
                .rsplit('/')
                .next()
                .unwrap()
        })
        .collect();
    assert_eq!(rkeys, ["r4", "r3", "r2", "r1"]);
    // Asked for at a commit this server does not have: no subject.
    assert_eq!(runs[0]["check"]["workflow"], "audit");
    assert!(runs[0]["subject"].is_null());
    assert_eq!(
        (&runs[1]["batch"], &runs[1]["ref"]),
        (&Value::from(1), &Value::from("refs/heads/main"))
    );
    assert!(runs[1]["subject"].is_null());
    assert_eq!(runs[2]["pull"]["title"], "A topic");
    assert_eq!(runs[2]["ref"], "refs/heads/topic");
    assert_eq!(runs[2]["check"]["status"], "failure");
    assert_eq!(runs[3]["subject"], "Workflows");
    assert!(runs[3]["pull"].is_null() && runs[3]["batch"].is_null());
    assert!(shown["cursor"].is_null());
    let workflows: Vec<(&str, bool, Option<&str>)> =
        (shown["workflows"].as_array().unwrap().iter())
            .map(|w| {
                (
                    w["name"].as_str().unwrap(),
                    w["asked"].as_bool().unwrap(),
                    w["about"].as_str(),
                )
            })
            .collect();
    assert_eq!(
        workflows,
        [
            ("audit", true, Some("Checks the docs against the code.")),
            ("ci", false, Some("Builds and tests.")),
        ]
    );

    // One workflow's, and a page at a time.
    let audit = site.data(&format!("{page}?workflow=audit"));
    assert_eq!(audit["runs"].as_array().unwrap().len(), 1);
    let first = site.data(&format!("{page}?limit=2"));
    assert_eq!(first["runs"].as_array().unwrap().len(), 2);
    let cursor = first["cursor"].as_str().unwrap();
    let next = site.data(&format!("{page}?limit=2&cursor={cursor}"));
    assert_eq!(next["runs"][0]["check"]["uri"], runs[2]["check"]["uri"]);
}

/// A composer's preview: Markdown as a comment in the repository shows it
/// once posted, and nothing longer than a comment may be.
#[test]
fn previewing_markdown() {
    let site = serve(true);
    let owner = person(account(), &site);
    let url = owner.url("noted");
    ok(&owner, &["repo", "create", &url]);
    let work = owner.work("w", 1);
    owner.git(&work, &["push", "-q", &url, "main"]);
    let issue = ok(&owner, &["issue", "create", &url, "Notes"]);
    let issue = issue.trim();
    let said = "See [the guide](docs/guide.md), https://example.com and <b>this</b>.";
    ok(&owner, &["comment", "add", &url, issue, said]);
    let handle = &owner.account.handle;
    let (did, _, rkey) = knotless::forge::parse_uri(issue).unwrap();
    let thread = at(&format!("/{handle}/noted/issue/{did}/{rkey}"));
    let commented = || {
        let (code, body) = site.status(&thread);
        code == 200 && serde_json::from_str::<Value>(&body).unwrap()["comments"][0].is_object()
    };
    wait_until(commented);
    let shown = site.json(&thread)["comments"][0]["html"].clone();
    let html = shown.as_str().unwrap();
    assert!(html.contains(&format!("href=\"/{handle}/noted/docs/guide.md\"")));
    assert!(html.contains("&lt;b&gt;this&lt;/b&gt;"));

    let repo = format!("at://{handle}/dev.knotless.repo/noted");
    let input = serde_json::json!({ "text": said, "repo": repo });
    let (status, preview) = site.post("renderMarkdown", input);
    assert_eq!((status, &preview["html"]), (200, &shown));
    // Without a repository, relative links resolve from the site's root.
    let bare = serde_json::json!({ "text": "[a](b.md)" });
    let (_, preview) = site.post("renderMarkdown", bare);
    assert_eq!(preview["html"], "<p><a href=\"/b.md\">a</a></p>\n");

    // Longer than a comment may be, or in a repository no record's URI
    // names, it is refused.
    let long = serde_json::json!({ "text": "a".repeat(20_001) });
    let (status, why) = site.post("renderMarkdown", long);
    assert_eq!(
        (status, why["error"].as_str()),
        (400, Some("InvalidRequest"))
    );
    assert!(
        why["message"]
            .as_str()
            .unwrap()
            .contains("longer than 20000")
    );
    let elsewhere = serde_json::json!({ "text": "a", "repo": "noted" });
    let (status, why) = site.post("renderMarkdown", elsewhere);
    assert_eq!(status, 400);
    assert!(why["message"].as_str().unwrap().contains("is not at://"));
}

/// Deletes `owner`'s record at `collection`/`rkey`, as any client could.
fn delete_record(owner: &Sandbox, collection: &str, rkey: &str) {
    let client = Client::new();
    let session: Value = client
        .post(format!(
            "{}/xrpc/com.atproto.server.createSession",
            net().pds
        ))
        .json(&serde_json::json!({
            "identifier": owner.account.did, "password": owner.account.password,
        }))
        .send()
        .and_then(|r| r.json())
        .unwrap();
    let token = session["accessJwt"].as_str().unwrap();
    let deleted = client
        .post(format!("{}/xrpc/com.atproto.repo.deleteRecord", net().pds))
        .bearer_auth(token)
        .json(&serde_json::json!({
            "repo": owner.account.did, "collection": collection, "rkey": rkey,
        }))
        .send()
        .unwrap();
    assert!(deleted.status().is_success());
}

/// Real Cargo, with a home of its own, knowing nothing but `registries`.
struct Cargo {
    home: std::path::PathBuf,
    token: String,
}

impl Cargo {
    fn run(&self, dir: &std::path::Path, args: &[&str]) -> (bool, String) {
        let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into());
        let mut command = std::process::Command::new(cargo);
        for (var, _) in std::env::vars() {
            // What building this test set, as a coverage run does.
            let building = ["CARGO", "RUSTC", "RUSTFLAGS", "LLVM_", "RUSTDOC"];
            if building.iter().any(|b| var.starts_with(b)) {
                command.env_remove(var);
            }
        }
        let out = command
            .args(args)
            .current_dir(dir)
            .env("CARGO_HOME", &self.home)
            .env("CARGO_TARGET_DIR", dir.join("target"))
            .env("CARGO_TERM_COLOR", "never")
            .env("CARGO_REGISTRIES_HERE_TOKEN", &self.token)
            .output()
            .unwrap();
        let said = String::from_utf8_lossy(&out.stderr).into_owned();
        (
            out.status.success(),
            said + &String::from_utf8_lossy(&out.stdout),
        )
    }

    fn ok(&self, dir: &std::path::Path, args: &[&str]) -> String {
        let (ok, said) = self.run(dir, args);
        assert!(ok, "cargo {args:?}: {said}");
        said
    }
}

/// A crate's directory, of `toml` after its `[package]`, and `lib` or `main`.
fn cargo_crate(
    root: &std::path::Path,
    name: &str,
    version: &str,
    toml: &str,
    file: &str,
    code: &str,
) -> std::path::PathBuf {
    let dir = root.join(format!("{name}-{version}"));
    std::fs::create_dir_all(dir.join("src")).unwrap();
    let manifest = format!(
        "[package]\nname = \"{name}\"\nversion = \"{version}\"\nedition = \"2021\"\ndescription = \"says {name}\"\nlicense = \"MIT\"\n{toml}\n"
    );
    std::fs::write(dir.join("Cargo.toml"), manifest).unwrap();
    std::fs::write(dir.join("src").join(file), code).unwrap();
    dir
}

#[test]
fn a_cargo_registry() {
    use serde_json::json;

    const PUBLISH: [&str; 4] = ["publish", "--registry", "here", "--allow-dirty"];
    let site = serve(true);
    let owner = person(account(), &site);
    let handle = owner.account.handle.clone();
    let url = owner.url("crates");
    ok(&owner, &["repo", "create", &url]);
    let work = owner.work("w", 1);
    owner.git(&work, &["push", "-q", &url, "main"]);
    let did = owner.account.did.clone();
    let key = owner.record("dev.knotless.repo", "crates")["key"]
        .as_str()
        .unwrap()
        .to_string();
    let here = format!("/cargo/{did}/{key}");
    wait_until(|| site.status(&format!("{here}/config.json")).0 == 200);

    let config = site.json(&format!("{here}/config.json"));
    assert_eq!(
        config["api"].as_str().unwrap(),
        format!("{}{here}", site.base)
    );
    assert_eq!(
        config["dl"].as_str().unwrap(),
        format!("{}{here}/dl/{{crate}}/{{version}}/download", site.base)
    );

    // Where cargo login sends its user, what the token is.
    let (status, page) = site.status(&format!("{here}/me"));
    assert_eq!(status, 200);
    assert!(page.contains("<handle>:<app password>"), "{page}");
    assert!(page.contains(&did), "{page}");

    let root = owner.path("cargo");
    let home = root.join("home");
    std::fs::create_dir_all(&home).unwrap();
    std::fs::write(
        home.join("config.toml"),
        format!(
            "[registries.here]\nindex = \"sparse+{}{here}/\"\n",
            site.base
        ),
    )
    .unwrap();
    let cargo = Cargo {
        home,
        token: format!("{handle}:{}", owner.account.password),
    };

    // A crate, and one depending on it, renamed and with a namespaced feature.
    let leaf = cargo_crate(
        &root,
        "leaf",
        "1.0.0",
        "[features]\nnothing = []\n",
        "lib.rs",
        "pub fn says() -> u8 { 1 }\n",
    );
    cargo.ok(&leaf, &PUBLISH);
    let extra = cargo_crate(
        &root,
        "extra",
        "1.0.0",
        "",
        "lib.rs",
        "pub fn says() -> u8 { 2 }\n",
    );
    cargo.ok(&extra, &PUBLISH);
    let mid = cargo_crate(
        &root,
        "mid",
        "0.2.0",
        "[dependencies]\nnamed = { package = \"leaf\", version = \"1\", registry = \"here\" }\nextra = { version = \"1\", registry = \"here\", optional = true }\n[features]\nwith-extra = [\"dep:extra\"]\n",
        "lib.rs",
        "pub fn says() -> u8 { named::says() + 40 }\n",
    );
    let said = cargo.ok(&mid, &PUBLISH);
    // Found at once, with no wait for the firehose.
    assert!(!said.contains("timed out"), "{said}");
    assert!(said.contains("Published mid v0.2.0"), "{said}");

    // The index's line and a read of it, by its ETag.
    let line = site.status(&format!("{here}/3/m/mid")).1;
    let line: Value = serde_json::from_str(line.trim()).unwrap();
    assert_eq!(
        (line["vers"].as_str(), line["v"].as_i64()),
        (Some("0.2.0"), Some(2))
    );
    assert_eq!(line["yanked"], false);
    let index = format!("{}{here}/3/m/mid", site.base);
    let first = reqwest::blocking::get(&index).unwrap();
    let etag = first.headers()["etag"].to_str().unwrap().to_string();
    let again = reqwest::blocking::Client::new()
        .get(&index)
        .header("if-none-match", &etag)
        .send()
        .unwrap();
    assert_eq!(again.status(), 304);
    assert_eq!(site.status(&format!("{here}/m/id/mid")).0, 404);
    assert_eq!(site.status(&format!("{here}/no/ne/none")).0, 404);
    assert_eq!(
        site.status(&format!("{here}/dl/none/1.0.0/download")).0,
        404
    );

    // Used by something that names only the registry, from nothing kept.
    let app_toml = "[dependencies]\nmid = { version = \"0.2\", registry = \"here\", features = [\"with-extra\"] }\n";
    let app = cargo_crate(
        &root,
        "app",
        "0.0.1",
        app_toml,
        "main.rs",
        "fn main() { println!(\"{}\", mid::says()); }\n",
    );
    let said = cargo.ok(&app, &["run", "-q"]);
    assert!(said.contains("41"), "{said}");

    // Refused: what is there, a token that is no good, another's, or none.
    let (done, said) = cargo.run(&leaf, &PUBLISH);
    assert!(!done && said.contains("already exists"), "{said}");
    let other = cargo_crate(&root, "other", "1.0.0", "", "lib.rs", "");
    let denied = |token: &str| {
        let cargo = Cargo {
            home: cargo.home.clone(),
            token: token.into(),
        };
        let (done, said) = cargo.run(&other, &PUBLISH);
        assert!(!done && said.contains("403"), "{said}");
        said
    };
    assert!(denied(&format!("{handle}:wrong")).contains("could not log in"));
    assert!(denied("nocolon").contains("log in: cargo login"));
    let stranger = account();
    let theirs = format!("{}:{}", stranger.handle, stranger.password);
    assert!(denied(&theirs).contains("only "));

    // What Cargo would never send is refused too.
    let put = |crate_: &[u8], said: Value, token: &str| {
        let said = said.to_string();
        let mut body = (said.len() as u32).to_le_bytes().to_vec();
        body.extend(said.as_bytes());
        body.extend((crate_.len() as u32).to_le_bytes());
        body.extend(crate_);
        reqwest::blocking::Client::new()
            .put(format!("{}{here}/api/v1/crates/new", site.base))
            .header("authorization", token)
            .body(body)
            .send()
            .unwrap()
    };
    let packed = |dir: &std::path::Path, name: &str, version: &str| {
        cargo.ok(
            dir,
            &[
                "package",
                "--allow-dirty",
                "--no-verify",
                "--registry",
                "here",
            ],
        );
        std::fs::read(
            dir.join("target/package")
                .join(format!("{name}-{version}.crate")),
        )
        .unwrap()
    };
    let token = cargo.token.as_str();
    let crate_ = packed(&other, "other", "1.0.0");
    let says = |res: reqwest::blocking::Response| (res.status().as_u16(), res.text().unwrap());
    let meta = |name: &str, version: &str| json!({"name": name, "vers": version});
    let (status, said) = says(put(&crate_, meta("claims", "1.0.0"), token));
    assert!(
        status == 400 && said.contains("its Cargo.toml is not"),
        "{said}"
    );
    let (status, said) = says(put(&crate_, meta("Other", "1.0.0"), token));
    assert!(status == 400 && said.contains("package's name"), "{said}");
    let (status, said) = says(put(b"not a crate", meta("other", "1.0.0"), token));
    assert!(status == 400 && said.contains("no Cargo.toml"), "{said}");
    let (status, said) = says(put(&crate_, json!("no"), token));
    assert_eq!(status, 400, "{said}");
    let raw = reqwest::blocking::Client::new()
        .put(format!("{}{here}/api/v1/crates/new", site.base))
        .header("authorization", token)
        .body("xx")
        .send()
        .unwrap();
    assert_eq!(raw.status(), 400);
    let long = "x".repeat(100_001);
    let (status, said) = says(put(
        &crate_,
        json!({"name": "other", "vers": "1.0.0", "links": long}),
        token,
    ));
    assert!(status == 400 && said.contains("index line"), "{said}");

    // Build metadata makes a record of another key, which then goes.
    let twin = cargo_crate(&root, "leaf", "1.0.0+b", "", "lib.rs", "");
    let crate_ = packed(&twin, "leaf", "1.0.0+b");
    let (status, said) = says(put(&crate_, meta("leaf", "1.0.0+b"), token));
    assert!(
        status == 400 && said.contains("leaf 1.0.0 is published already"),
        "{said}"
    );
    let versions = |name: &str| -> Vec<String> {
        owner
            .records("dev.knotless.release")
            .into_iter()
            .filter(|(_, r)| r["package"] == name)
            .map(|(_, r)| r["version"].as_str().unwrap().to_string())
            .collect()
    };
    assert_eq!(versions("leaf"), ["1.0.0"]);

    // A yank: a built lockfile still builds from nothing kept, and nothing
    // new takes it, until it is undone. Told at once, as a publish is.
    let yank = ["yank", "--registry", "here", "--version", "1.0.0", "extra"];
    cargo.ok(&extra, &yank);
    let line = site.status(&format!("{here}/ex/tr/extra")).1;
    assert!(line.contains("\"yanked\":true"), "{line}");
    std::fs::remove_dir_all(cargo.home.join("registry")).unwrap();
    std::fs::remove_dir_all(app.join("target")).unwrap();
    assert!(cargo.ok(&app, &["run", "-q", "--locked"]).contains("41"));
    let new_toml = "[dependencies]\nextra = { version = \"1\", registry = \"here\" }\n";
    let new = cargo_crate(&root, "new", "0.0.1", new_toml, "main.rs", "fn main() {}\n");
    let (done, said) = cargo.run(&new, &["build"]);
    assert!(!done && said.contains("yanked"), "{said}");
    cargo.ok(
        &extra,
        &[
            "yank",
            "--registry",
            "here",
            "--undo",
            "--version",
            "1.0.0",
            "extra",
        ],
    );
    cargo.ok(&new, &["build"]);
    let (done, said) = cargo.run(
        &extra,
        &["yank", "--registry", "here", "--version", "9.0.0", "extra"],
    );
    assert!(
        !done && said.contains("has published no extra 9.0.0"),
        "{said}"
    );

    // Searched for by name, each at its latest, as many as asked.
    let found = site.json(&format!("{here}/api/v1/crates?q=e&per_page=1"));
    assert_eq!(found["meta"]["total"], 2);
    assert_eq!(
        found["crates"][0],
        json!({"name": "extra", "max_version": "1.0.0", "description": "says extra"})
    );
    assert_eq!(found["crates"].as_array().unwrap().len(), 1);
    let said = cargo.ok(&root, &["search", "--registry", "here", "mid"]);
    assert!(
        said.contains("mid = \"0.2.0\"") && said.contains("says mid"),
        "{said}"
    );
    let everything = site.json(&format!("{here}/api/v1/crates"));
    assert_eq!(everything["meta"]["total"], 3);
    // The releases page knows each for a crate.
    let repo = format!("at://{did}/dev.knotless.repo/crates");
    let listed = site.json(&xrpc("listReleases", &[("repo", &repo)]));
    let listed = listed["releases"].as_array().unwrap();
    assert!(listed.iter().all(|r| r["crate"] == true), "{listed:?}");

    // A file whose bytes are not what its record says is not sent, and a
    // body too large is refused as it is read.
    let blob = owner.upload_blob(b"x", "application/octet-stream");
    let bad = serde_json::json!({
        "$type": "dev.knotless.release", "repo": key, "package": "bad", "version": "1.0.0",
        "cargo": {"index": json!({"name": "bad", "vers": "1.0.0"}).to_string()},
        "files": [{"name": "bad-1.0.0.crate", "size": 1, "sha256": "a".repeat(64),
            "parts": [{"blob": blob}]}],
        "createdAt": "2026-10-27T00:00:00.000Z",
    });
    owner.put_record("dev.knotless.release", "bad", bad);
    wait_until(|| site.status(&format!("{here}/3/b/bad")).0 == 200);
    assert_eq!(site.status(&format!("{here}/dl/bad/1.0.0/download")).0, 502);
    let (status, _) = says(
        reqwest::blocking::Client::new()
            .put(format!("{}{here}/api/v1/crates/new", site.base))
            .header("authorization", token)
            .body(vec![0u8; (64 << 20) + 1])
            .send()
            .unwrap(),
    );
    assert_eq!(status, 413);

    // A repository that is not there is none of it.
    assert_eq!(
        site.status(&format!("/cargo/{did}/nokey/config.json")).0,
        404
    );
    let (status, _) = says(
        reqwest::blocking::Client::new()
            .put(format!("{}/cargo/{did}/nokey/api/v1/crates/new", site.base))
            .header("authorization", token)
            .body("xx")
            .send()
            .unwrap(),
    );
    assert_eq!(status, 404);
}

#[test]
fn the_releases() {
    let site = serve(true);
    let owner = person(account(), &site);
    let handle = owner.account.handle.clone();
    let url = owner.url("shipped");
    ok(&owner, &["repo", "create", &url]);
    let work = owner.work("w", 1);
    owner.git(&work, &["push", "-q", &url, "main"]);
    let none = owner.url("unshipped");
    ok(&owner, &["repo", "create", &none]);

    // The client twice, a pre-release of it after, and an image: one
    // whose files are an OCI layout's, its index beside its blobs.
    let file = |name: &str, bytes: &[u8]| {
        let path = owner.path(name);
        std::fs::write(&path, bytes).unwrap();
        path.to_string_lossy().into_owned()
    };
    let tarball = file("tool-x86_64.tar.gz", b"tarball");
    let index = file("index.json", b"{}");
    let blob = file(&format!("sha256-{}", "a".repeat(64)), b"layer");
    let publish = |package: &str, version: &str, files: &[&str], notes: &str| {
        let mut args = vec!["release", "publish", &url, package, version];
        args.extend(files);
        args.extend(["--notes", notes]);
        ok(&owner, &args).trim().to_string()
    };
    let first = publish("tool", "0.1.0", &[&tarball], "The **first** one.");
    publish(
        "tool",
        "0.1.1",
        &[&tarball],
        "See [the guide](docs/guide.md).",
    );
    publish("image", "1.0.0", &[&index, &blob], "An image.");
    publish("tool", "0.2.0-rc.1", &[&tarball], "Try it.");
    // A record that is no release, by its version, is passed by.
    let key = owner.record("dev.knotless.repo", "shipped")["key"].clone();
    let bogus = serde_json::json!({
        "$type": "dev.knotless.release", "repo": key, "package": "tool",
        "version": "not one", "files": [], "createdAt": "2026-10-27T00:00:00.000Z",
    });
    owner.put_record("dev.knotless.release", "bogus", bogus);

    let repo = format!("at://{handle}/dev.knotless.repo/shipped");
    let list = |more: &str| {
        site.json(&format!(
            "{}{more}",
            xrpc("listReleases", &[("repo", &repo)])
        ))
    };
    wait_until(|| list("")["releases"].as_array().unwrap().len() == 4);
    let all = list("");
    let shown: Vec<(&str, &str, bool, bool)> = all["releases"]
        .as_array()
        .unwrap()
        .iter()
        .map(|r| {
            (
                r["package"].as_str().unwrap(),
                r["version"].as_str().unwrap(),
                r["latest"] == true,
                r["image"] == true,
            )
        })
        .collect();
    // Newest first; the latest of each is its highest without a
    // pre-release part, so not the pre-release, newest though it is.
    assert_eq!(
        shown,
        [
            ("tool", "0.2.0-rc.1", false, false),
            ("image", "1.0.0", true, true),
            ("tool", "0.1.1", true, false),
            ("tool", "0.1.0", false, false),
        ]
    );
    let r = &all["releases"][2];
    let html = r["html"].as_str().unwrap();
    assert!(html.contains(&format!("href=\"/{handle}/shipped/docs/guide.md\"")));
    assert_eq!(r["notes"], "See [the guide](docs/guide.md).");
    assert_eq!(r["files"][0]["name"], "tool-x86_64.tar.gz");
    assert_eq!(r["files"][0]["size"], 7);
    assert_eq!(r["files"][0]["sha256"].as_str().unwrap().len(), 64);
    assert_eq!(
        all["packages"],
        serde_json::json!([
            {"name": "image", "latest": "1.0.0", "image": true, "releaseCount": 1},
            {"name": "tool", "latest": "0.1.1", "releaseCount": 3},
        ])
    );
    assert!(all["cursor"].is_null());

    // One package's, a page at a time.
    let versions = |page: &Value| -> Vec<String> {
        let releases = page["releases"].as_array().unwrap().iter();
        releases
            .map(|r| r["version"].as_str().unwrap().to_string())
            .collect()
    };
    let tool = list("&package=tool&limit=2");
    assert_eq!(versions(&tool), ["0.2.0-rc.1", "0.1.1"]);
    assert_eq!(tool["packages"].as_array().unwrap().len(), 2);
    let cursor = tool["cursor"].as_str().unwrap();
    let rest = list(&format!("&package=tool&limit=2&cursor={cursor}"));
    assert_eq!(versions(&rest), ["0.1.0"]);
    assert!(rest["cursor"].is_null());
    let bad = format!("{}&cursor=x", xrpc("listReleases", &[("repo", &repo)]));
    assert_eq!(site.status(&bad).0, 400);

    // As an Atom feed: each version, named by its record, with its notes
    // as HTML, linked to its package's releases.
    let feed = site
        .get(&format!("/{handle}/shipped/releases.atom"))
        .text()
        .unwrap();
    assert_eq!(feed.matches("<entry>").count(), 4);
    assert!(feed.contains("<title>tool 0.1.1</title>"));
    assert!(feed.contains(&format!("<id>{}</id>", r["uri"].as_str().unwrap())));
    assert!(feed.contains(&format!(
        "{}/{handle}/shipped/releases?package=tool\"",
        site.base
    )));
    assert!(feed.contains("<content type=\"html\">&lt;p&gt;See &lt;a href=&quot;"));
    assert!(feed.contains(&format!(
        "<author><name>Releases of {handle}/shipped</name></author>"
    )));

    // The repository's pages say, under its name, the newest that is its
    // package's latest, and its packages; the one with none says nothing.
    let head = site.json(&xrpc("getRepo", &[("repo", &repo)]));
    let summary = &head["releases"];
    assert_eq!(summary["count"], 4);
    let release = &summary["release"];
    assert_eq!(
        (&release["package"], &release["version"]),
        (&"image".into(), &"1.0.0".into())
    );
    assert_eq!(
        (&release["latest"], &release["image"]),
        (&true.into(), &true.into())
    );
    assert_eq!(summary["packages"], all["packages"]);
    let unshipped = format!("at://{handle}/dev.knotless.repo/unshipped");
    let bare = site.json(&xrpc("getRepo", &[("repo", &unshipped)]));
    assert!(bare["releases"].is_null());
    let listed = site.json(&xrpc("listReleases", &[("repo", &unshipped)]));
    assert_eq!(listed["releases"], serde_json::json!([]));
    assert!(listed["packages"].is_null());

    // Each file downloads from the pages, as it was published, to be
    // saved rather than shown, and asked for again by its hash.
    let download = |rest: &str, etag: Option<&str>| {
        let path = format!("{}/{handle}/shipped/releases/download/{rest}", site.base);
        let mut asked = Client::new().get(path);
        if let Some(etag) = etag {
            asked = asked.header("if-none-match", etag);
        }
        asked.send().unwrap()
    };
    let got = download("tool/0.1.1/tool-x86_64.tar.gz", None);
    assert_eq!(got.status().as_u16(), 200);
    let says = |name: &str| got.headers()[name].to_str().unwrap().to_string();
    let sha256 = r["files"][0]["sha256"].as_str().unwrap();
    assert_eq!(says("content-type"), "application/octet-stream");
    assert_eq!(
        says("content-disposition"),
        "attachment; filename=\"tool-x86_64.tar.gz\""
    );
    assert_eq!(says("content-length"), "7");
    assert_eq!(says("cache-control"), "no-cache");
    assert_eq!(says("x-content-type-options"), "nosniff");
    assert!(says("content-security-policy").starts_with("sandbox"));
    let etag = says("etag");
    assert_eq!(etag, format!("\"{sha256}\""));
    assert!(got.headers().get("content-encoding").is_none());
    assert_eq!(got.bytes().unwrap().as_ref(), b"tarball");
    let again = download("tool/0.1.1/tool-x86_64.tar.gz", Some(&etag));
    assert_eq!(again.status().as_u16(), 304);
    // Kept, the same file of another version is not read again.
    let kept = download("tool/0.1.0/tool-x86_64.tar.gz", None);
    assert_eq!(kept.bytes().unwrap().as_ref(), b"tarball");
    for missing in [
        "tool/0.1.1/nothing.tar.gz",
        "tool/9.9.9/tool-x86_64.tar.gz",
        "other/0.1.1/tool-x86_64.tar.gz",
        "tool-x86_64.tar.gz",
        "0.1.1/tool-x86_64.tar.gz",
    ] {
        assert_eq!(download(missing, None).status().as_u16(), 404, "{missing}");
    }

    // Unpublished, a version goes.
    let rkey = first.rsplit('/').next().unwrap();
    delete_record(&owner, "dev.knotless.release", rkey);
    wait_until(|| list("")["releases"].as_array().unwrap().len() == 3);
    let head = site.json(&xrpc("getRepo", &[("repo", &repo)]));
    assert_eq!(head["releases"]["count"], 3);

    // Taken down, a release leaves the list and its files are not handed
    // out.
    let uri = r["uri"].as_str().unwrap();
    let index = site.app.index.clone().unwrap();
    let subject = serde_json::json!({"$type": "com.atproto.repo.strongRef", "uri": uri});
    index
        .take_down("did:web:here", uri, &subject, true)
        .unwrap();
    assert_eq!(list("")["releases"].as_array().unwrap().len(), 2);
    let gone = download("tool/0.1.1/tool-x86_64.tar.gz", None);
    assert_eq!(gone.status().as_u16(), 404);
}

#[test]
fn badges_count_where_an_account_lives() {
    use knotless_serve::index::Home;
    let site = serve(true);
    // The reference PDS holds more accounts than one of an account's own.
    let others: Vec<Account> = (0..6).map(|_| account()).collect();
    let did = &others[0].did;
    let shown = format!("/xrpc/dev.knotless.getProfile?actor={did}");
    assert!(site.json(&shown)["badges"].is_null());
    // Looked up as the profile was first asked for: an account that never
    // moved, on a PDS of many.
    let index = site.app.index.as_ref().unwrap();
    let (home, _) = index.home(did).unwrap().unwrap();
    assert_eq!(home, Home::default());
    // Kept a day, what was found shows, and is looked up again.
    let kept = Home {
        moved: true,
        own: true,
    };
    index.set_home(did, kept, 0).unwrap();
    let kinds: Vec<Value> = site.json(&shown)["badges"]
        .as_array()
        .unwrap()
        .iter()
        .map(|b| b["kind"].clone())
        .collect();
    assert_eq!(kinds, ["carry-your-own", "moved-house"]);
    wait_until(|| index.home(did).unwrap().unwrap().0 == Home::default());
}

#[test]
fn the_steps_shared() {
    let site = serve(true);
    let owner = person(account(), &site);
    let handle = owner.account.handle.clone();
    let url = owner.url("steps");
    ok(&owner, &["repo", "create", &url]);
    let work = owner.work("w", 1);
    owner.git(&work, &["push", "-q", &url, "main"]);
    let commit = owner.git(&work, &["rev-parse", "HEAD"]);

    // Steps, each released as its package with the commit it is at; a
    // file that is no step, and a release naming no commit, are passed by.
    let file = |name: &str, text: &str| {
        let path = owner.path(name);
        std::fs::write(&path, text).unwrap();
        path.to_string_lossy().into_owned()
    };
    let wait = file(
        "wait.yml",
        "description: Waits for this commit's checks.\ninputs:\n  workflows: {description: Which must pass, required: true}\n  minutes: {default: \"25\"}\nneeds: [curl]\nsecrets: [TOKEN]\nsteps:\n  - run: echo\n",
    );
    let lint = file("lint.yml", "steps:\n  - run: cargo clippy\n");
    let broken = file("broken.yml", "not: a step\n");
    let publish = |package: &str, version: &str, path: &str, commit: Option<&str>| {
        let mut args = vec!["release", "publish", &url, package, version, path];
        args.extend(commit.map(|c| ["--commit", c]).into_iter().flatten());
        ok(&owner, &args);
    };
    publish("@step/wait", "1.0.0", &wait, Some(&commit));
    let repo = format!("at://{handle}/dev.knotless.repo/steps");
    let releases = || site.json(&xrpc("listReleases", &[("repo", &repo)]))["releases"].clone();
    wait_until(|| releases().as_array().unwrap().len() == 1);
    // What was indexed before they start is read first.
    let rt = &site._rt;
    rt.spawn(knotless_serve::shared::follow(site.app.clone()));
    rt.spawn(knotless_serve::languages::follow(site.app.clone()));
    publish("@step/wait", "1.1.0", &wait, Some(&commit));
    publish("@step/wait", "2.0.0-rc.1", &wait, Some(&commit));
    publish("@step/lint", "0.3.0", &lint, Some(&commit));
    publish("@step/broken", "1.0.0", &broken, Some(&commit));
    publish("@step/loose", "1.0.0", &lint, None);
    wait_until(|| releases().as_array().unwrap().len() == 6);

    let list = |params: &[(&str, &str)]| site.json(&xrpc("listSteps", params));
    let names = |listed: &Value| -> Vec<String> {
        let steps = listed["steps"].as_array().unwrap().iter();
        steps.map(|s| s["name"].as_str().unwrap().into()).collect()
    };
    wait_until(|| names(&list(&[])).len() == 2 && list(&[])["steps"][1]["version"] == "1.1.0");
    let all = list(&[]);
    assert_eq!(names(&all), ["lint", "wait"]);
    let shown = &all["steps"][1];
    assert_eq!(shown["repo"]["handle"], handle.as_str());
    assert_eq!(shown["repo"]["name"], "steps");
    assert_eq!(shown["description"], "Waits for this commit's checks.");
    assert_eq!(shown["secretCount"], 1);
    assert_eq!(shown["useCount"], 0);
    assert!(all["steps"][0]["description"].is_null());
    assert!(all["cursor"].is_null());
    // A page at a time, by name; by how a name starts; one repository's.
    let first = list(&[("limit", "1")]);
    assert_eq!(names(&first), ["lint"]);
    let cursor = first["cursor"].as_str().unwrap().to_string();
    let rest = list(&[("limit", "1"), ("cursor", &cursor)]);
    assert_eq!(names(&rest), ["wait"]);
    assert!(rest["cursor"].is_null());
    assert_eq!(names(&list(&[("q", "wa")])), ["wait"]);
    assert!(names(&list(&[("q", "x")])).is_empty());
    assert_eq!(names(&list(&[("repo", &repo)])).len(), 2);
    let other = owner.url("user");
    ok(&owner, &["repo", "create", &other]);
    let used_in = format!("at://{handle}/dev.knotless.repo/user");
    let status =
        |nsid: &str, params: &[(&str, &str)]| site.get(&xrpc(nsid, params)).status().as_u16();
    wait_until(|| status("listSteps", &[("repo", &used_in)]) == 200);
    assert!(names(&list(&[("repo", &used_in)])).is_empty());
    assert_eq!(status("listSteps", &[("repo", "at://nope")]), 400);
    let nowhere = format!("at://{handle}/dev.knotless.repo/nowhere");
    assert_eq!(status("listSteps", &[("repo", &nowhere)]), 404);
    let unknown = "at://nobody.invalid/dev.knotless.repo/x";
    assert_eq!(status("listSteps", &[("repo", unknown)]), 404);

    // One step: what it takes and asks for, and its versions, the highest
    // first.
    let get = |name: &str| site.json(&xrpc("getStep", &[("repo", &repo), ("name", name)]));
    let step = get("wait");
    assert_eq!(step["version"], "1.1.0");
    assert_eq!(step["commit"], commit.as_str());
    assert_eq!(step["path"], ".knotless/steps/wait.yml");
    assert_eq!(step["uses"], format!("{handle}/steps/wait@1"));
    assert_eq!(
        step["inputs"],
        serde_json::json!([
            {"name": "minutes", "required": false, "default": "25"},
            {"name": "workflows", "required": true, "description": "Which must pass"},
        ])
    );
    assert_eq!(step["needs"], serde_json::json!([{"name": "curl"}]));
    assert_eq!(step["secrets"], serde_json::json!([{"name": "TOKEN"}]));
    let versions: Vec<&str> = step["versions"]
        .as_array()
        .unwrap()
        .iter()
        .map(|v| v["version"].as_str().unwrap())
        .collect();
    assert_eq!(versions, ["2.0.0-rc.1", "1.1.0", "1.0.0"]);
    assert_eq!(get("lint")["uses"], format!("{handle}/steps/lint@0.3"));
    for missing in ["broken", "loose", "nothing"] {
        assert_eq!(
            status("getStep", &[("repo", &repo), ("name", missing)]),
            404
        );
    }

    // Used, as a repository's lock on its default branch pins it: read as
    // what the repository is written in is counted, once a list shows it.
    // Pinned twice, it is one repository using it.
    let user = owner.work("u", 1);
    std::fs::create_dir_all(user.join(".knotless")).unwrap();
    let pin = format!("{{version: 1.1.0, commit: {commit}}}");
    let lock = format!(
        "{handle}/steps/wait@1: {pin}\n{handle}/steps/wait@1.1: {pin}\n{handle}/nowhere/wait@1: {pin}\nnobody.invalid/x/y@1: {pin}\nnot-a-use: {pin}\n"
    );
    owner.commit_file(&user, ".knotless/uses.lock", &lock);
    owner.git(&user, &["push", "-q", &other, "main"]);
    let users = || {
        site.profile(&handle);
        get("wait")["useCount"].clone()
    };
    wait_until(|| users() == 1);
    let step = get("wait");
    assert_eq!(
        step["usedBy"],
        serde_json::json!([{"did": owner.account.did, "handle": handle, "name": "user"}])
    );
    assert_eq!(list(&[("q", "wait")])["steps"][0]["useCount"], 1);

    // Taken down, a step's releases leave it.
    let index = site.app.index.clone().unwrap();
    for v in step["versions"].as_array().unwrap() {
        let uri = v["uri"].as_str().unwrap();
        let subject = serde_json::json!({"$type": "com.atproto.repo.strongRef", "uri": uri});
        index
            .take_down("did:web:here", uri, &subject, true)
            .unwrap();
    }
    assert_eq!(names(&list(&[])), ["lint"]);

    // With no index, there are none.
    let plain = serve(false);
    assert_eq!(plain.get(&xrpc("listSteps", &[])).status().as_u16(), 404);
    plain
        ._rt
        .block_on(knotless_serve::shared::follow(plain.app.clone()));
    // Nor languages to count, nor, with no gateway, spaces to follow: each
    // returns at once. Run here, not as a server starts, where it is a
    // task the test may end before it is run, and its lines counted or
    // not by chance.
    plain
        ._rt
        .block_on(knotless_serve::languages::follow(plain.app.clone()));
    let every = Duration::from_secs(60);
    plain
        ._rt
        .block_on(knotless_serve::spaces::follow(plain.app.clone(), every));
}

/// Code search, over Zoekt: what the default branch of each public
/// repository holds, found in all of them or in one, as it is pushed; and
/// a repository deleted found no more.
#[test]
fn searching_code() {
    // A server that searches no code says so.
    let plain = serve(false);
    let asked = plain.status("/xrpc/dev.knotless.searchCode?q=x");
    assert_eq!(asked.0, 501, "{}", asked.1);

    let site = serve_with(Sandbox::new(), true, true);
    let s = person(account(), &site);
    let handle = s.account.handle.clone();
    // With nothing pushed, there is nothing to index.
    ok(&s, &["repo", "create", &s.url("unpushed")]);
    // Pushed before code is indexed: what has no shards yet is indexed as
    // indexing starts.
    for (name, n) in [("needle-one", 1), ("needle-two", 2)] {
        let work = s.work(name, 1);
        let lib = format!("// the haystack\nfn needle_{n}() {{}}\nfn other() {{}}\n");
        write(&work, "src/lib.rs", lib.as_bytes());
        s.git(&work, &["add", "-A"]);
        s.git(&work, &["commit", "-q", "-m", "Hide a needle"]);
        s.git(&work, &["push", "-q", &s.url(name), "main"]);
    }
    wait_until(|| names(&site.search("needle")["repos"]).len() == 2);
    let follow = knotless_serve::code::follow(site.app.clone());
    site._rt.spawn(follow.expect("a code index"));
    let search = |q: &str| site.json(&format!("/xrpc/dev.knotless.searchCode?q={q}"));
    let found = |q: &str| search(q)["files"].as_array().unwrap().len();
    wait_until(|| found("needle_") == 2);

    let one = search("needle_1");
    assert_eq!(
        (one["fileCount"].as_u64(), one["matchCount"].as_u64()),
        (Some(1), Some(1))
    );
    // Its languages, counted across all it finds.
    assert_eq!(
        one["languages"],
        serde_json::json!([{"name": "Rust", "color": "#dea584", "files": 1}])
    );
    // Picking another language finds nothing, but still lists them all.
    let other = search("needle_+lang:Markdown");
    assert_eq!(other["files"].as_array().unwrap().len(), 0, "{other}");
    assert_eq!(other["languages"][0]["files"], 2, "{other}");
    let file = &one["files"][0];
    assert_eq!(file["repo"]["name"], "needle-one");
    assert_eq!(file["repo"]["owner"]["handle"], handle.as_str());
    assert_eq!(file["path"], "src/lib.rs");
    assert_eq!(file["language"], "Rust");
    let commit = s.git(&s.path("needle-one"), &["rev-parse", "HEAD"]);
    assert_eq!(file["commit"], commit.trim());
    let chunk = &file["chunks"][0];
    assert_eq!(chunk["startLine"], 1, "{chunk}");
    assert_eq!(
        chunk["content"],
        "// the haystack\nfn needle_1() {}\nfn other() {}"
    );
    assert_eq!(
        chunk["ranges"],
        serde_json::json!([{"line": 2, "byteStart": 3, "byteEnd": 11}])
    );
    // Painted as its file's language reads: the comment first.
    let painted = chunk["highlights"].as_str().unwrap();
    assert!(painted.starts_with("comment:0,"), "{painted}");

    // A page at a time, the best first, and the next after the cursor.
    let first = search("needle_&limit=1");
    assert_eq!(first["files"].as_array().unwrap().len(), 1, "{first}");
    assert_eq!(first["cursor"], "1", "{first}");
    let next = search("needle_&limit=1&cursor=1");
    assert_eq!(next["files"].as_array().unwrap().len(), 1, "{next}");
    assert_ne!(next["files"][0]["repo"], first["files"][0]["repo"]);
    assert!(next.get("cursor").is_none(), "{next}");
    // Its languages were listed with the first.
    assert_eq!(next["languages"], serde_json::json!([]), "{next}");
    // No deeper than the pages go.
    let deep = search("needle_&cursor=1000");
    assert_eq!(deep["files"].as_array().unwrap().len(), 0, "{deep}");
    let bad = site.status("/xrpc/dev.knotless.searchCode?q=x&cursor=no");
    assert_eq!(bad.0, 400, "{}", bad.1);

    // In one repository, by its record's URI.
    let within = |name: &str| {
        let repo = format!("at://{handle}/dev.knotless.repo/{name}");
        site.json(&format!(
            "/xrpc/dev.knotless.searchCode?q=needle_&repo={repo}&limit=5"
        ))
    };
    let two = within("needle-two");
    assert_eq!(two["files"].as_array().unwrap().len(), 1, "{two}");
    assert_eq!(two["files"][0]["repo"]["name"], "needle-two");

    // A push is found once it is indexed again.
    let work = s.path("needle-two");
    write(&work, "src/more.rs", b"fn needle_3() {}\n");
    s.git(&work, &["add", "-A"]);
    s.git(&work, &["commit", "-q", "-m", "Hide another"]);
    s.git(&work, &["push", "-q", &s.url("needle-two"), "main"]);
    wait_until(|| found("needle_3") == 1);

    // A query Zoekt cannot read is the asker's to fix.
    let bad = site.status("/xrpc/dev.knotless.searchCode?q=(");
    assert_eq!(bad.0, 400, "{}", bad.1);

    // A repository deleted is found no more.
    // This account's, as others' repositories on the PDS are indexed too:
    // each named for its owner's DID, `:` written `_3a`.
    let theirs = s.account.did.replace(':', "_3a");
    let shards = || {
        let dir = std::fs::read_dir(site._sandbox.path("code")).unwrap();
        let names = dir.map(|e| e.unwrap().file_name().into_string().unwrap());
        let mine = |n: &String| n.starts_with(&theirs) && n.ends_with(".zoekt");
        names.filter(mine).count()
    };
    // A push's new shard is found before the one it replaces is deleted.
    wait_until(|| shards() == 2);
    // Each pushed repository has its meta file, and the unpushed one none.
    let metas = std::fs::read_dir(site._sandbox.path("code").join("meta")).unwrap();
    let metas = metas.map(|e| e.unwrap().file_name().into_string().unwrap());
    assert_eq!(metas.filter(|n| n.starts_with(&theirs)).count(), 2);
    // `/_status` tells of the index.
    let (_, status) = site.status("/_status");
    let status: Value = serde_json::from_str(&status).unwrap();
    assert_eq!(status["code"]["ok"], true, "{status}");
    assert!(status["code"]["shards"].as_u64().unwrap() >= 2, "{status}");
    ok(&s, &["repo", "delete", &s.url("needle-one"), "--yes"]);
    wait_until(|| found("needle_1") == 0);
    // And its shards are deleted.
    wait_until(|| shards() == 1);
    wait_until(|| found("needle_") == 2);
}