gunnar-ssh 3.0.0

gunnar's SSH server: a russh listener with publickey identity, a strict git exec dispatch, and the repository path sandbox.
Documentation
//! **`git-lfs-authenticate` is ANSWERED, with the words that say what to do.**
//!
//! # What this is a regression test for
//!
//! MEASURED 2026-10-25 against gunnar.rs with git-lfs 3.8.0: when a pure-SSH
//! `git-lfs-transfer` session fails or is refused, git-lfs falls back to
//! `ssh … git-lfs-authenticate <path> <operation>`. gunnar answered that exec
//! with a channel failure, so the push ended in
//!
//! ```text
//! batch request: exec request failed on channel 0: exit status 255
//! error: failed to fetch some objects from 'https://git.gunnar.rs/nordisk/gunnar.git/info/lfs'
//! ```
//!
//! — which CI lanes read as "the LFS server is unreachable" and pushed past
//! with `--no-verify`. Gunnar serves LFS over pure SSH only (git-lfs ≥ 3.0),
//! so the verb is answered on stderr with exit 1 and a sentence: pure SSH only,
//! retry. git-lfs prints that sentence after `batch request:`.
//!
//! The words must NOT contain `not found`, `no such file` or `command not
//! found`: git-lfs reads those as "this server has no git-lfs-authenticate" and
//! goes on to a guessed HTTPS endpoint, which Gunnar does not serve.

mod common;

use common::*;
use gunnar_ssh::Access;

#[tokio::test]
async fn git_lfs_authenticate_is_answered_with_the_pure_ssh_sentence() {
    let key = mint_key();
    let fixture = start_with_key(&key, Access::Admin).await;
    let session = authenticate(fixture.server.local_addr(), &key, "git")
        .await
        .expect("connect")
        .expect("authenticate");
    for line in [
        "git-lfs-authenticate 'nordisk/osm-katana.git' upload",
        "git-lfs-authenticate nordisk/osm-katana.git download",
    ] {
        match exec(&session, line).await.expect("exec") {
            ExecOutcome::Accepted {
                stdout,
                exit_status,
            } => {
                assert_eq!(
                    exit_status,
                    Some(1),
                    "{line}: a refusal git-lfs reports, not a crash: {stdout:?}"
                );
                assert!(
                    stdout.contains("pure SSH"),
                    "{line}: the sentence names the transport: {stdout:?}"
                );
                assert!(stdout.contains("git-lfs-transfer"), "{line}: {stdout:?}");
                assert!(
                    stdout.contains("again"),
                    "{line}: it says to retry: {stdout:?}"
                );
                let lower = stdout.to_ascii_lowercase();
                for fallback in ["not found", "no such file", "command not found"] {
                    assert!(
                        !lower.contains(fallback),
                        "{line}: `{fallback}` sends git-lfs to a guessed HTTPS endpoint Gunnar does not serve: {stdout:?}"
                    );
                }
            }
            ExecOutcome::Refused => panic!(
                "{line}: refused the channel — git-lfs prints `exec request failed on channel 0`, \
                 which says nothing about what to do"
            ),
        }
    }
    assert!(
        fixture.transport.served().is_empty(),
        "nothing is served by this verb"
    );
}