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

johnandersen777.bsky.social/knotless

git that lives in your Atmosphere account

Forked from chadtmiller.com/knotless

0
0
127 lines · 4.9 KB Raw History
//! `/_status` against a reference PDS: a mirror read as its code page
//! reads it, one that can no longer be, and `knotless status` showing it.

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

use knotless::commit::{Change, Spec};
use knotless::status::Status;
use knotless_serve::{App, router};
use std::time::Duration;

fn get(url: &str) -> (u16, Status) {
    let rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(async {
        let res = reqwest::get(url).await.unwrap();
        let code = res.status().as_u16();
        (code, res.json().await.unwrap())
    })
}

fn cli(args: &[&str]) -> (i32, String) {
    let mut argv = vec!["knotless".to_string()];
    argv.extend(args.iter().map(|a| a.to_string()));
    // On a thread of its own: the command's poll is more than a test
    // thread's stack holds, unoptimised.
    let run = std::thread::Builder::new()
        .stack_size(32 << 20)
        .spawn(move || {
            let mut out = Vec::new();
            // No appview, so a bare `status` never asks knotless.dev.
            let env =
                knotless::env::Env::from_vars(|k| (k == "KNOTLESS_APPVIEW").then(String::new));
            let code = knotless::cli::main_with(argv, env, &mut &b""[..], &mut out);
            (code, String::from_utf8(out).unwrap())
        });
    run.unwrap().join().unwrap()
}

#[test]
fn status_reads_a_mirror() {
    let s = Sandbox::new();
    let rt = tokio::runtime::Runtime::new().unwrap();
    let url = knotless::url::RepoUrl::parse(&s.url("probe")).unwrap();
    let spec = Spec {
        branch: "main".into(),
        message: "first".into(),
        changes: vec![Change::Put {
            path: "a.txt".into(),
            data: b"a\n".to_vec(),
            executable: false,
        }],
        retries: 3,
        ..Default::default()
    };
    let tip = rt
        .block_on(knotless::commit::commit(&ctx(&s), &url, &spec))
        .unwrap()
        .sha;
    // Trusted for a minute, so the mirror is not synced again under the test.
    let app = App::new(ctx(&s), Duration::from_secs(60));
    let listener = rt
        .block_on(tokio::net::TcpListener::bind("127.0.0.1:0"))
        .unwrap();
    let base = format!("http://{}", listener.local_addr().unwrap());
    let routes = router(app.clone());
    rt.spawn(async move { axum::serve(listener, routes).await.unwrap() });
    let handle = s.account.handle.clone();

    // Nothing followed, no index, no mirror asked after: well.
    let (code, status) = get(&format!("{base}/_status"));
    assert_eq!(code, 200);
    assert!(status.ok && status.problems.is_empty(), "{status:?}");

    // The mirror reads: its commit, and its packs counted.
    let probe = format!("{handle}/probe");
    let (code, status) = get(&format!("{base}/_status?repo={probe}.git"));
    assert_eq!(code, 200, "{status:?}");
    let mirror = status.mirror.unwrap();
    assert_eq!(mirror.commit.as_deref(), Some(tip.as_str()));
    assert!(mirror.packs.unwrap() >= 1, "{mirror:?}");
    let (code, text) = cli(&["status", &base, "--repo", &probe]);
    assert_eq!(code, 0, "{text}");
    assert!(text.starts_with(&format!("ok\t{base}")), "{text}");
    assert!(
        text.contains(&format!("mirror\t{probe}\tok at {}", &tip[..12])),
        "{text}"
    );
    let (code, json) = cli(&["status", &base, "--json"]);
    assert_eq!(code, 0);
    assert!(serde_json::from_str::<Status>(&json).unwrap().ok);

    // No such repository, or no repository named at all.
    let (code, status) = get(&format!("{base}/_status?repo={handle}/nope"));
    assert_eq!(code, 503);
    let error = status.mirror.unwrap().error.unwrap();
    assert!(error.contains("no such repository"), "{error}");
    assert!(status.problems[0].starts_with(&format!("mirror {handle}/nope: ")));
    let (code, status) = get(&format!("{base}/_status?repo=probe"));
    assert_eq!(code, 503);
    let error = status.mirror.unwrap().error.unwrap();
    assert_eq!(error, "?repo= takes <handle or did>/<name>");

    // Its packs gone: the objects can no longer be read, as when a mirror
    // outgrew the room its open handle had for packs.
    let git = rt.block_on(app.mirror(&handle, "probe")).unwrap();
    for pack in std::fs::read_dir(git.dir.join("objects/pack")).unwrap() {
        std::fs::remove_file(pack.unwrap().path()).unwrap();
    }
    let (code, status) = get(&format!("{base}/_status?repo={probe}"));
    assert_eq!(code, 503);
    let mirror = status.mirror.unwrap();
    assert_eq!(
        (mirror.ok, mirror.packs, mirror.commit),
        (false, None, None)
    );
    assert!(mirror.error.is_some());
    let (code, text) = cli(&["status", &base, "--repo", &probe]);
    assert_eq!(code, 1);
    assert!(text.starts_with("UNHEALTHY"), "{text}");

    // What the command is asked wrongly.
    assert_eq!(cli(&["status"]).0, 1);
    assert_eq!(cli(&["status", &base, "--bogus"]).0, 1);
    assert_eq!(cli(&["status", "http://127.0.0.1:1"]).0, 1);
    assert_eq!(cli(&["status", &format!("{base}/healthz")]).0, 1);
}