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

nordisk/gunnar

crates/gunnar-grpc/tests/services.rs

//! The seven services, driven through their generated tonic traits.
//!
//! These call the service impls directly rather than over a socket: the point
//! is the behaviour, and a real listener would only add a port to the test and
//! a way for it to be flaky. The transport itself is tonic's to test.
//!
//! **These tests are about service behaviour, not about authentication.** The
//! harness turns the gate off with
//! `ServerConfig::insecure_allow_all_for_local_development` so that every RPC
//! below reads as what it is about. Authentication and authorisation have their
//! own file, `tests/auth.rs`, which drives all 37 RPCs with no credential and
//! asserts every one of them refuses, so nothing here is load-bearing for the
//! gate.
//!
//! Two things every test here is written to avoid:
//!
//! - **asserting a round-trip instead of an effect.** `create` then `get` only
//!   proves the store remembers; where it matters, the assertion is on what
//!   another observer sees: the event stream, `Admin.Stats`, `ListGates`.
//! - **treating an unwired seam as a success.** Refs and mirror-sync must
//!   answer `UNIMPLEMENTED`, never an empty list, because an empty ref list is
//!   indistinguishable from a working server with no refs.

use std::sync::Arc;
use std::time::Duration;

use gunnar_grpc::{
    AccessService, AdminService, EventBus, EventsService, MirrorService, PolicyService,
    RefsService, ReposService, ServerConfig, ServiceContext,
};
use gunnar_grpc::{Caller, ControlPlane, MemoryControlPlane};
use gunnar_proto::access_server::Access as _;
use gunnar_proto::admin_server::Admin as _;
use gunnar_proto::events_server::Events as _;
use gunnar_proto::mirror_server::Mirror as _;
use gunnar_proto::policy_server::Policy as _;
use gunnar_proto::refs_server::Refs as _;
use gunnar_proto::repos_server::Repos as _;
use gunnar_proto::*;
use tokio_stream::StreamExt;
use tonic::{Code, Request};

const QUIET: Duration = Duration::from_millis(200);

/// A real ed25519 public key. `ssh-keygen -lf` prints
/// `SHA256:trpJv6vJY1KqksoqEmKrfP9nkaubTa6pWNCT0+h1PoQ` for it.
const REAL_ED25519_KEY: &str =
    "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAQASTVsBFoMErQCd7+a2RGl/jUEjR/Dgynpyy6/vVZm \
     alice@workstation";

struct Harness {
    ctx: ServiceContext,
}

impl Harness {
    fn new() -> Self {
        let control: Arc<dyn ControlPlane> = Arc::new(MemoryControlPlane::new());
        let bus = Arc::new(EventBus::new(64));
        Self {
            ctx: ServiceContext::new(
                control,
                bus,
                ServerConfig::default().insecure_allow_all_for_local_development(),
            ),
        }
    }

    fn repos(&self) -> ReposService {
        ReposService::new(self.ctx.clone())
    }
    fn refs(&self) -> RefsService {
        RefsService::new(self.ctx.clone())
    }
    fn access(&self) -> AccessService {
        AccessService::new(self.ctx.clone())
    }
    fn policy(&self) -> PolicyService {
        PolicyService::new(self.ctx.clone())
    }
    fn events(&self) -> EventsService {
        EventsService::new(self.ctx.clone())
    }
    fn admin(&self) -> AdminService {
        AdminService::new(self.ctx.clone())
    }
    fn mirror(&self) -> MirrorService {
        MirrorService::new(self.ctx.clone())
    }

    /// The same harness with the gate ON, plus a caller that may use it. Used
    /// by exactly one test, the one that has to prove `Health`'s flag is not a
    /// constant. Everything else about authentication lives in `tests/auth.rs`.
    fn authenticated() -> (Self, Caller) {
        let plane = MemoryControlPlane::new();
        let principal = plane
            .add_principal("root", "root", PrincipalKind::User)
            .expect("principal");
        plane.grant_server(&principal.id, GrantLevel::Admin);
        let control: Arc<dyn ControlPlane> = Arc::new(plane);
        let bus = Arc::new(EventBus::new(64));
        let caller = Caller {
            principal_id: principal.id,
            fingerprint: "SHA256:test".into(),
            machine: false,
            authenticated: true,
        };
        (
            Self {
                ctx: ServiceContext::new(control, bus, ServerConfig::default()),
            },
            caller,
        )
    }

    async fn namespace(&self, name: &str) {
        self.repos()
            .create_namespace(Request::new(CreateNamespaceRequest {
                name: name.into(),
                description: String::new(),
            }))
            .await
            .expect("namespace created");
    }

    async fn repo(&self, namespace: &str, repo: &str) -> Repo {
        self.repos()
            .create(Request::new(CreateRepoRequest {
                path: Some(RepoPath::new(namespace, repo)),
                ..Default::default()
            }))
            .await
            .expect("repo created")
            .into_inner()
            .repo
            .expect("a created repo comes back")
    }
}

fn path(namespace: &str, repo: &str) -> Option<RepoPath> {
    Some(RepoPath::new(namespace, repo))
}

// ── Repos ──────────────────────────────────────────────────────────────────

#[tokio::test]
async fn creating_a_repo_applies_the_server_defaults() {
    let harness = Harness::new();
    harness.namespace("nordisk").await;
    let repo = harness.repo("nordisk", "gunnar").await;

    assert_eq!(repo.path.as_ref().unwrap().to_string(), "nordisk/gunnar");
    assert_eq!(
        repo.hash_kind,
        HashKind::Sha256 as i32,
        "sha256 is the default hash kind for new repos"
    );
    assert_eq!(
        repo.default_branch, "refs/heads/main",
        "the default branch is a FULL ref name, not a short one"
    );
    assert_eq!(
        repo.visibility,
        Visibility::Private as i32,
        "a repo must not default to public"
    );
    assert!(repo.created_at_unix_ms > 0);
}

#[tokio::test]
async fn a_repo_cannot_be_created_outside_a_namespace() {
    // Multi-tenancy from day 1. There is no ungrouped repo and no
    // default namespace to fall back on.
    let harness = Harness::new();
    let status = harness
        .repos()
        .create(Request::new(CreateRepoRequest {
            path: path("does-not-exist", "gunnar"),
            ..Default::default()
        }))
        .await
        .expect_err("an unknown namespace must be refused");
    assert_eq!(status.code(), Code::NotFound);
    assert!(status.message().contains("namespace"), "{status:?}");
}

#[tokio::test]
async fn a_malformed_repo_path_is_rejected_at_the_edge() {
    let harness = Harness::new();
    harness.namespace("nordisk").await;

    for (bad, why) in [
        (
            Some(RepoPath::new("", "gunnar")),
            "an empty namespace is not a default namespace",
        ),
        (Some(RepoPath::new("..", "gunnar")), "`..` must not resolve"),
        (
            Some(RepoPath::new("nordisk", "sub/repo")),
            "a smuggled slash must not create a nested path",
        ),
        (None, "a missing path is not an empty path"),
    ] {
        let status = harness
            .repos()
            .create(Request::new(CreateRepoRequest {
                path: bad.clone(),
                ..Default::default()
            }))
            .await
            .expect_err(why);
        assert_eq!(status.code(), Code::InvalidArgument, "{why}: {status:?}");
    }
}

#[tokio::test]
async fn creating_the_same_repo_twice_is_an_error_not_a_silent_reuse() {
    let harness = Harness::new();
    harness.namespace("nordisk").await;
    harness.repo("nordisk", "gunnar").await;

    let status = harness
        .repos()
        .create(Request::new(CreateRepoRequest {
            path: path("nordisk", "gunnar"),
            ..Default::default()
        }))
        .await
        .expect_err("a second create must fail");
    assert_eq!(status.code(), Code::AlreadyExists);
}

#[tokio::test]
async fn a_namespace_holding_repos_is_not_deleted_by_accident() {
    let harness = Harness::new();
    harness.namespace("nordisk").await;
    harness.repo("nordisk", "gunnar").await;

    let status = harness
        .repos()
        .delete_namespace(Request::new(DeleteNamespaceRequest {
            name: "nordisk".into(),
            delete_repos: false,
        }))
        .await
        .expect_err("deleting repos is never implicit");
    assert_eq!(status.code(), Code::FailedPrecondition);

    // …and does go, with the repos, when the caller says so.
    let deleted = harness
        .repos()
        .delete_namespace(Request::new(DeleteNamespaceRequest {
            name: "nordisk".into(),
            delete_repos: true,
        }))
        .await
        .expect("explicit delete succeeds")
        .into_inner();
    assert_eq!(deleted.repos_deleted, 1);
    assert!(harness
        .repos()
        .get(Request::new(GetRepoRequest {
            path: path("nordisk", "gunnar")
        }))
        .await
        .is_err());
}

#[tokio::test]
async fn listing_scopes_to_a_namespace() {
    let harness = Harness::new();
    harness.namespace("alpha").await;
    harness.namespace("beta").await;
    harness.repo("alpha", "one").await;
    harness.repo("alpha", "two").await;
    harness.repo("beta", "three").await;

    let all = harness
        .repos()
        .list(Request::new(ListReposRequest::default()))
        .await
        .expect("list")
        .into_inner();
    assert_eq!(all.repos.len(), 3);

    let alpha = harness
        .repos()
        .list(Request::new(ListReposRequest {
            namespace: "alpha".into(),
            ..Default::default()
        }))
        .await
        .expect("list")
        .into_inner();
    assert_eq!(alpha.repos.len(), 2, "the namespace filter must exclude");
    assert!(alpha
        .repos
        .iter()
        .all(|r| r.path.as_ref().unwrap().namespace == "alpha"));
}

// ── Events, over the service ───────────────────────────────────────────────

#[tokio::test]
async fn creating_a_repo_lights_a_watching_ui() {
    // the event stream's whole purpose: the UI stops polling. Assert the event a korp or
    // dwarves panel would actually receive, not that a bus method returned.
    let harness = Harness::new();
    harness.namespace("nordisk").await;

    let mut watch = harness
        .events()
        .watch(Request::new(WatchRequest::default()))
        .await
        .expect("watch")
        .into_inner();

    // Every Watch stream opens with the prologue, ahead of any event: it is
    // where the epoch lives, and a client that had to wait for an event to
    // learn it would wait for ever on a quiet server.
    let opened = watch.next().await.expect("open").expect("no status");
    assert_eq!(
        opened.kind,
        EventKind::StreamOpened as i32,
        "the first message of a Watch stream is the prologue"
    );
    match opened.payload {
        Some(event::Payload::StreamOpened(opened)) => {
            assert!(!opened.epoch.is_empty(), "the prologue must name the epoch");
            assert_eq!(opened.last_seq, 0, "nothing has been published yet");
            assert_eq!(opened.from_seq, 0, "this request sent no cursor");
        }
        other => panic!("expected StreamOpened, got {other:?}"),
    }

    let created = harness.repo("nordisk", "gunnar").await;

    let event = watch.next().await.expect("open").expect("no status");
    assert_eq!(event.kind, EventKind::RepoCreated as i32);
    assert_eq!(event.seq, 1);
    assert!(event.at_unix_ms > 0);
    match event.payload {
        Some(event::Payload::RepoCreated(payload)) => {
            assert_eq!(payload.repo.as_ref().unwrap(), &created);
            assert_eq!(
                payload.repo.unwrap().path.unwrap().to_string(),
                "nordisk/gunnar"
            );
        }
        other => panic!("expected RepoCreated, got {other:?}"),
    }

    // …and deleting it lights the same UI.
    harness
        .repos()
        .delete(Request::new(DeleteRepoRequest {
            path: path("nordisk", "gunnar"),
            ..Default::default()
        }))
        .await
        .expect("deleted");
    let event = watch.next().await.expect("open").expect("no status");
    assert_eq!(event.kind, EventKind::RepoDeleted as i32);
    assert_eq!(event.seq, 2);
}

#[tokio::test]
async fn a_watch_filtered_to_another_namespace_stays_quiet() {
    let harness = Harness::new();
    harness.namespace("alpha").await;
    harness.namespace("beta").await;

    let mut watch = harness
        .events()
        .watch(Request::new(WatchRequest {
            namespaces: vec!["beta".into()],
            ..Default::default()
        }))
        .await
        .expect("watch")
        .into_inner();

    // The prologue first, then silence. It is not filtered away — it says which
    // run the sequence numbers below belong to, which no filter is about.
    let opened = watch.next().await.expect("open").expect("no status");
    assert_eq!(opened.kind, EventKind::StreamOpened as i32);

    harness.repo("alpha", "not-yours").await;
    assert!(
        tokio::time::timeout(QUIET, watch.next()).await.is_err(),
        "a namespace-filtered watch must not see another tenant's repo"
    );

    harness.repo("beta", "yours").await;
    let event = tokio::time::timeout(QUIET, watch.next())
        .await
        .expect("an event for the watched namespace must arrive")
        .expect("open")
        .expect("no status");
    assert_eq!(event.repo_path().unwrap().namespace, "beta");
}

#[tokio::test]
async fn a_watch_with_a_cursor_replays_over_the_service_not_just_over_the_bus() {
    // The seam the bus tests cannot see. `Events.Watch` could honour every
    // filter, open every stream and still drop `from_seq` on the floor — a
    // `subscribe` where a `subscribe_from` belongs — and every test in
    // `tests/event_bus.rs` would stay green. So the cursor is exercised through
    // the RPC, with the repositories named.
    let harness = Harness::new();
    harness.namespace("nordisk").await;
    harness.repo("nordisk", "one").await;
    harness.repo("nordisk", "two").await;
    harness.repo("nordisk", "three").await;

    let mut watch = harness
        .events()
        .watch(Request::new(WatchRequest {
            from_seq: 1,
            ..Default::default()
        }))
        .await
        .expect("watch")
        .into_inner();

    let opened = watch.next().await.expect("open").expect("no status");
    match opened.payload {
        Some(opened) => match opened {
            event::Payload::StreamOpened(opened) => {
                assert_eq!(
                    opened.from_seq, 1,
                    "the service must pass the cursor through to the bus"
                );
                assert_eq!(opened.last_seq, 3, "three repositories, three events");
                assert_eq!(opened.replaying, 2, "seqs 2 and 3 are still buffered");
            }
            other => panic!("expected StreamOpened, got {other:?}"),
        },
        None => panic!("the prologue carried no payload"),
    }

    // The cursor sits at seq 1, so what must come back is seqs 2 and 3 — named
    // by the repositories they carry, so a stream that replayed the wrong end
    // of the ring cannot be mistaken for a working one.
    let mut replayed = Vec::new();
    while let Ok(Some(item)) = tokio::time::timeout(QUIET, watch.next()).await {
        let event = item.expect("no status");
        replayed.push((
            event.seq,
            event.repo_path().map(|p| p.to_string()).unwrap_or_default(),
        ));
    }
    assert_eq!(
        replayed,
        vec![
            (2, "nordisk/two".to_owned()),
            (3, "nordisk/three".to_owned())
        ],
        "a resume over the wire must deliver exactly what followed the cursor"
    );
}

#[tokio::test]
async fn a_watch_on_a_malformed_repo_filter_is_refused() {
    // A mistyped filter must not become an eternally silent stream that a UI
    // reads as "nothing is happening".
    let harness = Harness::new();

    // A `Response<WatchStream>` is not `Debug` (it holds a boxed stream), so
    // the rejection is matched rather than `expect_err`ed.
    let refused = |request: WatchRequest, why: &'static str| {
        let events = harness.events();
        async move {
            match events.watch(Request::new(request)).await {
                Ok(_) => panic!("{why}"),
                Err(status) => status,
            }
        }
    };

    let status = refused(
        WatchRequest {
            repos: vec![RepoPath::new("", "gunnar")],
            ..Default::default()
        },
        "an invalid filter path must be rejected",
    )
    .await;
    assert_eq!(status.code(), Code::InvalidArgument);

    let status = refused(
        WatchRequest {
            kinds: vec![9999],
            ..Default::default()
        },
        "an unknown event kind must be rejected",
    )
    .await;
    assert_eq!(status.code(), Code::InvalidArgument);
}

// ── Refs, the unwired seam ─────────────────────────────────────────────────

#[tokio::test]
async fn refs_report_unimplemented_rather_than_an_empty_success() {
    // Measure, never assert — the silent green: an empty ref list looks exactly like a healthy
    // server with no refs. Until gunnar-store is wired in, these must be loud.
    let harness = Harness::new();
    harness.namespace("nordisk").await;
    harness.repo("nordisk", "gunnar").await;

    let list = harness
        .refs()
        .list(Request::new(ListRefsRequest {
            repo: path("nordisk", "gunnar"),
            ..Default::default()
        }))
        .await
        .expect_err("the storage seam is not wired yet");
    assert_eq!(list.code(), Code::Unimplemented);
    assert!(list.message().contains("gunnar-store"), "{list:?}");

    for status in [
        harness
            .refs()
            .get(Request::new(GetRefRequest {
                repo: path("nordisk", "gunnar"),
                ref_name: "refs/heads/main".into(),
                peel: false,
            }))
            .await
            .expect_err("Refs.Get"),
        harness
            .refs()
            .log(Request::new(LogRequest {
                repo: path("nordisk", "gunnar"),
                ..Default::default()
            }))
            .await
            .expect_err("Refs.Log"),
        harness
            .refs()
            .read_object(Request::new(ReadObjectRequest {
                repo: path("nordisk", "gunnar"),
                oid: "a".repeat(64),
                max_bytes: 0,
            }))
            .await
            .expect_err("Refs.ReadObject"),
        harness
            .refs()
            .diff(Request::new(DiffRequest {
                repo: path("nordisk", "gunnar"),
                ..Default::default()
            }))
            .await
            .expect_err("Refs.Diff"),
    ] {
        assert_eq!(status.code(), Code::Unimplemented);
    }
}

#[tokio::test]
async fn refs_validate_the_path_before_reaching_the_seam() {
    // The path check must come FIRST; otherwise a malformed path would be
    // reported as "unimplemented" and its real problem hidden.
    let harness = Harness::new();
    let status = harness
        .refs()
        .list(Request::new(ListRefsRequest {
            repo: Some(RepoPath::new("..", "gunnar")),
            ..Default::default()
        }))
        .await
        .expect_err("a `..` namespace must be refused");
    assert_eq!(status.code(), Code::InvalidArgument);

    let status = harness
        .refs()
        .get(Request::new(GetRefRequest {
            repo: path("nordisk", "gunnar"),
            ref_name: String::new(),
            peel: false,
        }))
        .await
        .expect_err("an empty ref name must be refused");
    assert_eq!(status.code(), Code::InvalidArgument);
}

// ── Access ─────────────────────────────────────────────────────────────────

#[tokio::test]
async fn a_principal_gets_a_key_and_a_grant() {
    let harness = Harness::new();
    harness.namespace("nordisk").await;
    harness.repo("nordisk", "gunnar").await;

    let principal = harness
        .access()
        .add_principal(Request::new(AddPrincipalRequest {
            name: "alice".into(),
            kind: PrincipalKind::User as i32,
            id: String::new(),
        }))
        .await
        .expect("principal added")
        .into_inner()
        .principal
        .expect("returned");
    assert!(!principal.id.is_empty());

    let key = harness
        .access()
        .add_key(Request::new(AddKeyRequest {
            principal_id: principal.id.clone(),
            // A real ed25519 public key, because the fingerprint the key
            // table is keyed by is now computed from the key material, and
            // the SSH listener has to be able to match what is stored here.
            openssh_public_key: REAL_ED25519_KEY.into(),
            comment: "laptop".into(),
            expires_at_unix_ms: None,
            attestation: None,
        }))
        .await
        .expect("key added")
        .into_inner()
        .key
        .expect("returned");
    assert_eq!(key.algo, "ssh-ed25519");
    assert_eq!(key.principal_id, principal.id);

    let grant = harness
        .access()
        .grant(Request::new(GrantRequest {
            principal_id: principal.id.clone(),
            namespace: "nordisk".into(),
            repo: "gunnar".into(),
            level: GrantLevel::Write as i32,
        }))
        .await
        .expect("granted")
        .into_inner()
        .grant
        .expect("returned");
    assert_eq!(grant.level, GrantLevel::Write as i32);
    assert_eq!(grant.namespace, "nordisk");

    // A namespace-wide grant is a different grant, not an overwrite (multi-tenancy).
    harness
        .access()
        .grant(Request::new(GrantRequest {
            principal_id: principal.id.clone(),
            namespace: "nordisk".into(),
            repo: String::new(),
            level: GrantLevel::Read as i32,
        }))
        .await
        .expect("namespace-wide grant");
    let grants = harness
        .access()
        .list_grants(Request::new(ListGrantsRequest {
            principal_id: principal.id.clone(),
            ..Default::default()
        }))
        .await
        .expect("listed")
        .into_inner()
        .grants;
    assert_eq!(grants.len(), 2);

    // Removing the principal takes its key and both grants with it. A
    // revoked identity must not leave live access behind.
    let removed = harness
        .access()
        .remove_principal(Request::new(RemovePrincipalRequest {
            principal_id: principal.id.clone(),
        }))
        .await
        .expect("removed")
        .into_inner();
    assert_eq!(removed.keys_revoked, 1);
    assert_eq!(removed.grants_revoked, 2);
    assert!(harness
        .access()
        .list_keys(Request::new(ListKeysRequest::default()))
        .await
        .expect("listed")
        .into_inner()
        .keys
        .is_empty());
}

#[tokio::test]
async fn access_rejects_the_things_that_would_break_the_ssh_side() {
    let harness = Harness::new();
    harness.namespace("nordisk").await;

    let status = harness
        .access()
        .add_key(Request::new(AddKeyRequest {
            principal_id: "principal-does-not-exist".into(),
            openssh_public_key: "ssh-ed25519 AAAA example".into(),
            ..Default::default()
        }))
        .await
        .expect_err("a key for nobody is not a key");
    assert_eq!(status.code(), Code::NotFound);

    let principal = harness
        .access()
        .add_principal(Request::new(AddPrincipalRequest {
            name: "ci".into(),
            kind: PrincipalKind::Machine as i32,
            id: String::new(),
        }))
        .await
        .expect("added")
        .into_inner()
        .principal
        .unwrap();

    for bad in ["", "not-a-key", "rsa-but-wrong AAAA", "AAAAonefield"] {
        let status = harness
            .access()
            .add_key(Request::new(AddKeyRequest {
                principal_id: principal.id.clone(),
                openssh_public_key: bad.into(),
                ..Default::default()
            }))
            .await
            .expect_err("a malformed key line must be refused");
        assert_eq!(status.code(), Code::InvalidArgument, "for {bad:?}");
    }

    let status = harness
        .access()
        .add_principal(Request::new(AddPrincipalRequest {
            name: "unknown-kind".into(),
            kind: PrincipalKind::Unspecified as i32,
            id: String::new(),
        }))
        .await
        .expect_err("an unspecified kind must be refused");
    assert_eq!(status.code(), Code::InvalidArgument);

    let status = harness
        .access()
        .grant(Request::new(GrantRequest {
            principal_id: principal.id,
            namespace: "nordisk".into(),
            repo: "no-such-repo".into(),
            level: GrantLevel::Admin as i32,
        }))
        .await
        .expect_err("a grant on a repo that does not exist must be refused");
    assert_eq!(status.code(), Code::NotFound);
}

// ── Policy ─────────────────────────────────────────────────────────────────

#[tokio::test]
async fn list_gates_reflects_what_is_actually_configured() {
    // A gate list that always said "enabled" would be indistinguishable from a
    // configured server. Assert the OFF state first, then flip one thing at a
    // time and watch exactly that entry change.
    let harness = Harness::new();
    harness.namespace("nordisk").await;
    harness.repo("nordisk", "gunnar").await;

    let gates = |harness: &Harness| {
        let policy = harness.policy();
        async move {
            policy
                .list_gates(Request::new(ListGatesRequest {
                    repo: path("nordisk", "gunnar"),
                }))
                .await
                .expect("gates")
                .into_inner()
                .gates
        }
    };
    let enabled = |gates: &[GateInfo], name: &str| {
        gates
            .iter()
            .find(|g| g.name == name)
            .unwrap_or_else(|| panic!("no gate named {name}"))
            .enabled
    };

    let before = gates(&harness).await;
    assert_eq!(before.len(), 4, "the v1 gate set of the pre-receive gate");
    assert!(!enabled(&before, "branch-protection"));
    assert!(!enabled(&before, "ref-name-policy"));
    assert!(!enabled(&before, "signed-commits"));
    assert!(!enabled(&before, "size-limits"));

    harness
        .policy()
        .set_protection(Request::new(SetProtectionRequest {
            repo: path("nordisk", "gunnar"),
            protection: Some(BranchProtection {
                ref_pattern: "refs/heads/main".into(),
                require_signature: true,
                max_object_bytes: 100 * 1024 * 1024,
                ..Default::default()
            }),
        }))
        .await
        .expect("protection set");

    let after = gates(&harness).await;
    assert!(enabled(&after, "branch-protection"));
    assert!(enabled(&after, "signed-commits"));
    assert!(enabled(&after, "size-limits"));
    assert!(
        !enabled(&after, "ref-name-policy"),
        "branch protection must not switch on a gate nobody configured"
    );

    harness
        .policy()
        .set_ref_name_policy(Request::new(SetRefNamePolicyRequest {
            repo: path("nordisk", "gunnar"),
            policy: Some(RefNamePolicy {
                allow_patterns: vec!["refs/heads/feat/*".into()],
                deny_patterns: vec!["refs/heads/tmp-*".into()],
            }),
        }))
        .await
        .expect("ref-name policy set");
    assert!(enabled(&gates(&harness).await, "ref-name-policy"));
}

#[tokio::test]
async fn protection_defaults_deny_the_dangerous_operations() {
    // A `BranchProtection` whose fields all defaulted to "allow" would be a
    // gate that protects nothing. Assert the stored rule.
    let harness = Harness::new();
    harness.namespace("nordisk").await;
    harness.repo("nordisk", "gunnar").await;

    harness
        .policy()
        .set_protection(Request::new(SetProtectionRequest {
            repo: path("nordisk", "gunnar"),
            protection: Some(BranchProtection {
                ref_pattern: "refs/heads/main".into(),
                ..Default::default()
            }),
        }))
        .await
        .expect("set");

    let stored = harness
        .policy()
        .get_protection(Request::new(GetProtectionRequest {
            repo: path("nordisk", "gunnar"),
            ref_pattern: "refs/heads/main".into(),
        }))
        .await
        .expect("get")
        .into_inner()
        .protections;
    assert_eq!(stored.len(), 1);
    assert!(!stored[0].allow_force_push, "force-push denied by default");
    assert!(!stored[0].allow_delete, "delete denied by default");

    // A rule for a pattern that was never set must not come back.
    let none = harness
        .policy()
        .get_protection(Request::new(GetProtectionRequest {
            repo: path("nordisk", "gunnar"),
            ref_pattern: "refs/heads/release/*".into(),
        }))
        .await
        .expect("get")
        .into_inner()
        .protections;
    assert!(none.is_empty());
}

#[tokio::test]
async fn policy_on_a_repo_that_does_not_exist_is_not_answered() {
    let harness = Harness::new();
    harness.namespace("nordisk").await;
    let status = harness
        .policy()
        .list_gates(Request::new(ListGatesRequest {
            repo: path("nordisk", "never-created"),
        }))
        .await
        .expect_err("gates for a phantom repo must not be reported");
    assert_eq!(status.code(), Code::NotFound);
}

// ── Admin ──────────────────────────────────────────────────────────────────

/// The Gunnar RELEASE: the `gunnar` crate's version (1.6.0 on master; 1.5.2 on the 1.5.x
/// release line), which the release tag names.
fn gunnar_release() -> String {
    let m = std::fs::read_to_string(
        std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../gunnar/Cargo.toml"),
    )
    .expect("the gunnar crate's manifest");
    let pkg = &m[m.find("[package]").expect("[package]")..];
    pkg.lines()
        .find_map(|l| l.trim().strip_prefix("version = \""))
        .and_then(|v| v.split('"').next())
        .map(str::to_string)
        .expect("the gunnar crate names its version")
}

/// ★ ACCT-2 (2026-10-29): **`Health.version` is the Gunnar release that runs**, not this
/// crate's own semver. gunnar.rs's console header read "Gunnar 2.0.0" (gunnar-grpc's version
/// since the R5C crate versions) on a box that runs Gunnar 1.5.1.
#[tokio::test]
async fn health_names_the_gunnar_release_not_the_crates_version() {
    let health = Harness::new()
        .admin()
        .health(Request::new(HealthRequest {}))
        .await
        .expect("health")
        .into_inner();
    assert_eq!(
        health.version,
        gunnar_release(),
        "the release, as the gunnar crate and its tag name it"
    );
    assert_eq!(health.version, gunnar_common::RELEASE);
}

#[tokio::test]
async fn health_reports_whether_this_listener_authenticates() {
    // This harness deliberately runs the development escape hatch,
    // and `Health` must say so: the field is the RUNTIME answer, not the
    // build-time constant, and a UI rendering "authenticated" over an
    // allow-all listener would be a lie an operator believes.
    let harness = Harness::new();
    let health = harness
        .admin()
        .health(Request::new(HealthRequest {}))
        .await
        .expect("health")
        .into_inner();

    assert!(health.serving);
    assert!(
        !health.grpc_authentication_enabled,
        "this listener is running the allow-all escape hatch and must report it"
    );
    // …and the field is not a constant `false`: the same server with the
    // default config reports true. Without this the assertion above would pass
    // against a `Health` that had been hard-wired (measured, not asserted — the identity value).
    let (authenticated, caller) = Harness::authenticated();
    let mut probe = Request::new(HealthRequest {});
    probe.extensions_mut().insert(caller);
    let health_on = authenticated
        .admin()
        .health(probe)
        .await
        .expect("health")
        .into_inner();
    assert!(
        health_on.grpc_authentication_enabled,
        "a default (Required) listener must report authentication as ON"
    );
    let bind: std::net::SocketAddr = health.grpc_bind.parse().expect("a real address");
    assert!(
        bind.ip().is_loopback(),
        "an unauthenticated control plane reported a non-loopback bind: {bind}"
    );
    assert!(health.agent.starts_with("gunnar/"), "{}", health.agent);
}

#[tokio::test]
async fn stats_count_the_catalogue_and_the_bus_together() {
    let harness = Harness::new();
    harness.namespace("nordisk").await;
    harness.repo("nordisk", "gunnar").await;
    harness.repo("nordisk", "korp").await;
    harness
        .access()
        .add_principal(Request::new(AddPrincipalRequest {
            name: "alice".into(),
            kind: PrincipalKind::User as i32,
            id: String::new(),
        }))
        .await
        .expect("added");

    let stats = harness
        .admin()
        .stats(Request::new(StatsRequest {}))
        .await
        .expect("stats")
        .into_inner();

    assert_eq!(stats.namespaces, 1);
    assert_eq!(stats.repos, 2);
    assert_eq!(stats.principals, 1);
    assert_eq!(
        stats.events_published, 2,
        "two repo creations, two events; the counter is not decorative"
    );
    assert_eq!(stats.events_dropped, 0);
    assert_eq!(stats.event_buffer_capacity, 64);
}

#[tokio::test]
async fn storage_info_does_not_claim_a_backend_it_does_not_have() {
    // Say what was measured, not what was inferred. The test double has no
    // store; saying "tiered(redb,znippy)" here
    // would put a fiction on a dashboard.
    let harness = Harness::new();
    harness.namespace("nordisk").await;
    harness.repo("nordisk", "gunnar").await;

    let info = harness
        .admin()
        .storage_info(Request::new(StorageInfoRequest { repo: None }))
        .await
        .expect("storage info")
        .into_inner();
    assert!(info.object_backend.contains("none"), "{info:?}");
    assert!(info.ref_backend.contains("none"), "{info:?}");
    assert_eq!(info.repos.len(), 1);
    assert_eq!(
        info.repos[0].repo.as_ref().unwrap().to_string(),
        "nordisk/gunnar"
    );
}

// ── Repos.List narrows by name and by mirror, server-side (2026-10-04) ─────

async fn listed(harness: &Harness, request: ListReposRequest) -> Vec<String> {
    let mut names: Vec<String> = harness
        .repos()
        .list(Request::new(request))
        .await
        .expect("list")
        .into_inner()
        .repos
        .into_iter()
        .map(|r| r.path.expect("path").to_string())
        .collect();
    names.sort();
    names
}

/// ★ **A name query is gunnar's**: case-insensitive, over `<ns>/<name>` (so a
/// match may span the slash), applied to the whole listing — a console's
/// search no longer sees only the first page it happened to fetch.
#[tokio::test]
async fn a_listing_takes_a_name_query_over_the_whole_answer() {
    let harness = Harness::new();
    harness.namespace("nordisk").await;
    harness.namespace("other").await;
    for (ns, name) in [
        ("nordisk", "gunnar"),
        ("nordisk", "gunnar-ui"),
        ("nordisk", "holger"),
        ("other", "gunnar-docs"),
        ("other", "plain"),
    ] {
        harness.repo(ns, name).await;
    }
    let query = |namespace: &str, query: &str| ListReposRequest {
        namespace: namespace.into(),
        query: query.into(),
        ..Default::default()
    };
    assert_eq!(
        listed(&harness, query("", "GUNNAR")).await,
        vec!["nordisk/gunnar", "nordisk/gunnar-ui", "other/gunnar-docs"]
    );
    assert_eq!(
        listed(&harness, query("", "disk/hol")).await,
        vec!["nordisk/holger"]
    );
    assert_eq!(
        listed(&harness, query("other", "gunnar")).await,
        vec!["other/gunnar-docs"]
    );
    assert_eq!(
        listed(&harness, query("", "")).await.len(),
        5,
        "no query, no filter"
    );
}

/// ★ **Mirrors only** — the console's "Imported only" filter, as gunnar's
/// answer over every repository rather than over a first page.
#[tokio::test]
async fn a_listing_can_ask_for_mirrors_only() {
    let harness = Harness::new();
    harness.namespace("nordisk").await;
    harness.repo("nordisk", "plain").await;
    harness
        .mirror()
        .add(Request::new(AddMirrorRequest {
            repo: path("nordisk", "escaped"),
            remote_url: "ssh://git@codeberg.org/vetra/thing.git".into(),
            create_repo: true,
            ..Default::default()
        }))
        .await
        .expect("mirror added");
    let mirrors = ListReposRequest {
        mirrored_only: true,
        ..Default::default()
    };
    assert_eq!(listed(&harness, mirrors).await, vec!["nordisk/escaped"]);
    let both = ListReposRequest {
        mirrored_only: true,
        query: "plain".into(),
        ..Default::default()
    };
    assert!(
        listed(&harness, both).await.is_empty(),
        "both filters apply"
    );
}

// ── Mirror ─────────────────────────────────────────────────────────────────

#[tokio::test]
async fn a_mirror_is_registered_and_listed() {
    let harness = Harness::new();
    harness.namespace("nordisk").await;

    // Mirroring: `create_repo` lets one command be the whole escape hatch.
    let mirror = harness
        .mirror()
        .add(Request::new(AddMirrorRequest {
            repo: path("nordisk", "escaped"),
            remote_url: "ssh://git@codeberg.org/vetra/thing.git".into(),
            prune: true,
            create_repo: true,
            adopt: false,
        }))
        .await
        .expect("mirror added")
        .into_inner()
        .mirror
        .expect("returned");
    assert_eq!(mirror.state, MirrorState::Idle as i32);
    assert!(mirror.prune);
    assert!(mirror.last_sync_at_unix_ms.is_none());

    let listed = harness
        .mirror()
        .status(Request::new(MirrorStatusRequest {
            namespace: "nordisk".into(),
            repo: None,
        }))
        .await
        .expect("status")
        .into_inner()
        .mirrors;
    assert_eq!(listed.len(), 1);
    assert_eq!(
        listed[0].remote_url,
        "ssh://git@codeberg.org/vetra/thing.git"
    );

    // The repo really was created, in the namespace it was asked for.
    let repo = harness
        .repos()
        .get(Request::new(GetRepoRequest {
            path: path("nordisk", "escaped"),
        }))
        .await
        .expect("repo exists")
        .into_inner()
        .repo
        .unwrap();
    assert_eq!(repo.path.unwrap().namespace, "nordisk");
}

#[tokio::test]
async fn mirror_add_without_create_repo_does_not_invent_one() {
    let harness = Harness::new();
    harness.namespace("nordisk").await;
    let status = harness
        .mirror()
        .add(Request::new(AddMirrorRequest {
            repo: path("nordisk", "missing"),
            remote_url: "ssh://git@codeberg.org/vetra/thing.git".into(),
            prune: false,
            create_repo: false,
            adopt: false,
        }))
        .await
        .expect_err("no repo, no implicit creation");
    assert_eq!(status.code(), Code::NotFound);
}

#[tokio::test]
async fn mirror_sync_is_unimplemented_not_a_quiet_no_op() {
    // "0 refs changed" would look exactly like a mirror that is up to date.
    let harness = Harness::new();
    harness.namespace("nordisk").await;
    harness
        .mirror()
        .add(Request::new(AddMirrorRequest {
            repo: path("nordisk", "escaped"),
            remote_url: "ssh://git@codeberg.org/vetra/thing.git".into(),
            create_repo: true,
            ..Default::default()
        }))
        .await
        .expect("added");

    let status = harness
        .mirror()
        .sync(Request::new(SyncMirrorRequest {
            repo: path("nordisk", "escaped"),
        }))
        .await
        .expect_err("sync needs gunnar-client and a real store");
    assert_eq!(status.code(), Code::Unimplemented);
}

// ── The auth hole, asserted as behaviour ───────────────────────────────────

#[tokio::test]
async fn every_service_answers_without_a_credential() {
    // The v1 allow-all is a DECISION, not an accident, so it is pinned: v1 serves every
    // caller. If an auth interceptor lands and this starts failing, that is
    // the hole closing: update this test deliberately, with the crate docs.
    let harness = Harness::new();
    let no_metadata = Request::new(HealthRequest {});
    assert!(no_metadata.metadata().is_empty());
    assert!(harness.admin().health(no_metadata).await.is_ok());

    // …including the mutation that matters most: anyone can mint an identity.
    let unauthenticated = Request::new(AddPrincipalRequest {
        name: "anybody".into(),
        kind: PrincipalKind::User as i32,
        id: String::new(),
    });
    assert!(
        harness
            .access()
            .add_principal(unauthenticated)
            .await
            .is_ok(),
        "v1 is allow-all by decision; see the gunnar-grpc crate docs"
    );
}

/// **A principal's id is not its name** (2026-09-12). `AddPrincipal` takes an
/// id that may differ from the name, `ListPrincipals` answers a name filter
/// with one row, `RenamePrincipal` moves the label and leaves the id — and the
/// refusals (a taken name, a taken id, an unknown id) are driven red on the
/// same table before the acceptances.
#[tokio::test]
async fn a_principal_id_is_chosen_once_and_the_name_is_a_label() {
    let harness = Harness::new();

    // The default: no id given, so the id IS the name — every caller from
    // before the field existed.
    let alice = harness
        .access()
        .add_principal(Request::new(AddPrincipalRequest {
            name: "alice".into(),
            kind: PrincipalKind::User as i32,
            id: String::new(),
        }))
        .await
        .expect("added")
        .into_inner()
        .principal
        .unwrap();
    assert_eq!(alice.id, "alice");
    assert_eq!(alice.name, "alice");
    assert!(
        alice.created_at_unix_ms > 0,
        "the stamp must be on the wire"
    );

    // An explicit id: the account slug is `bob`, the label is not.
    let bob = harness
        .access()
        .add_principal(Request::new(AddPrincipalRequest {
            name: "Bob Builder".into(),
            kind: PrincipalKind::User as i32,
            id: "bob".into(),
        }))
        .await
        .expect("added")
        .into_inner()
        .principal
        .unwrap();
    assert_eq!(bob.id, "bob");
    assert_eq!(bob.name, "Bob Builder");

    // RED: a name another principal carries — as a name, and as an id.
    for (name, id) in [("Bob Builder", "bob2"), ("alice", "alice2")] {
        let status = harness
            .access()
            .add_principal(Request::new(AddPrincipalRequest {
                name: name.into(),
                kind: PrincipalKind::User as i32,
                id: id.into(),
            }))
            .await
            .expect_err("a taken name must be refused");
        assert_eq!(status.code(), Code::AlreadyExists, "for {name:?}: {status}");
    }
    // RED: a taken id under a fresh name.
    let status = harness
        .access()
        .add_principal(Request::new(AddPrincipalRequest {
            name: "Robert".into(),
            kind: PrincipalKind::User as i32,
            id: "bob".into(),
        }))
        .await
        .expect_err("a taken id must be refused");
    assert_eq!(status.code(), Code::AlreadyExists);

    // The name filter answers one row, by name, not by id.
    let by_name = harness
        .access()
        .list_principals(Request::new(ListPrincipalsRequest {
            name: "Bob Builder".into(),
            ..Default::default()
        }))
        .await
        .expect("listed")
        .into_inner()
        .principals;
    assert_eq!(
        by_name.iter().map(|p| p.id.as_str()).collect::<Vec<_>>(),
        ["bob"]
    );
    let by_id_as_name = harness
        .access()
        .list_principals(Request::new(ListPrincipalsRequest {
            name: "bob".into(),
            ..Default::default()
        }))
        .await
        .expect("listed")
        .into_inner()
        .principals;
    assert!(
        by_id_as_name.is_empty(),
        "an id is not a name: {by_id_as_name:?}"
    );

    // Rename: the label moves, the id and the stamp stay.
    let renamed = harness
        .access()
        .rename_principal(Request::new(RenamePrincipalRequest {
            principal_id: "bob".into(),
            name: "Robert".into(),
        }))
        .await
        .expect("renamed")
        .into_inner()
        .principal
        .unwrap();
    assert_eq!(renamed.id, "bob");
    assert_eq!(renamed.name, "Robert");
    assert_eq!(renamed.created_at_unix_ms, bob.created_at_unix_ms);
    // Applied, as another reader sees it.
    let listed = harness
        .access()
        .list_principals(Request::new(ListPrincipalsRequest::default()))
        .await
        .expect("listed")
        .into_inner()
        .principals;
    let row = listed
        .iter()
        .find(|p| p.id == "bob")
        .expect("still listed under its id");
    assert_eq!(row.name, "Robert");
    assert!(
        !listed.iter().any(|p| p.name == "Bob Builder"),
        "the old name must be gone from the listing"
    );

    // RED: rename onto a taken name, and rename of nobody.
    let status = harness
        .access()
        .rename_principal(Request::new(RenamePrincipalRequest {
            principal_id: "bob".into(),
            name: "alice".into(),
        }))
        .await
        .expect_err("a taken name must be refused on rename");
    assert_eq!(status.code(), Code::AlreadyExists);
    let status = harness
        .access()
        .rename_principal(Request::new(RenamePrincipalRequest {
            principal_id: "nobody".into(),
            name: "x".into(),
        }))
        .await
        .expect_err("nobody to rename");
    assert_eq!(status.code(), Code::NotFound);
    let status = harness
        .access()
        .rename_principal(Request::new(RenamePrincipalRequest {
            principal_id: "bob".into(),
            name: String::new(),
        }))
        .await
        .expect_err("an empty name is not a name");
    assert_eq!(status.code(), Code::InvalidArgument);
}

/// **A second key on a principal needs that principal's attestation**
/// (2026-09-12). On the double the rule is structural — the cryptographic
/// verification is the real plane's, driven in
/// `gunnar-cli/tests/add_key_attested.rs` — but the three arms and the
/// refusals are the same: a first key needs nobody's word; a second key
/// without the target's attestation is FAILED_PRECONDITION and writes nothing;
/// an attestation for another key, or signed by a key the target does not
/// hold, is INVALID_ARGUMENT; the honest one is accepted.
#[tokio::test]
async fn a_second_key_needs_the_principals_attestation() {
    let harness = Harness::new();
    let alice = harness
        .access()
        .add_principal(Request::new(AddPrincipalRequest {
            name: "alice".into(),
            kind: PrincipalKind::User as i32,
            id: String::new(),
        }))
        .await
        .expect("added")
        .into_inner()
        .principal
        .unwrap();

    // The first key: nobody to ask.
    let first = harness
        .access()
        .add_key(Request::new(AddKeyRequest {
            principal_id: alice.id.clone(),
            openssh_public_key: REAL_ED25519_KEY.into(),
            comment: "laptop".into(),
            expires_at_unix_ms: None,
            attestation: None,
        }))
        .await
        .expect("the first key needs no attestation")
        .into_inner()
        .key
        .unwrap();

    // A second, real key — a different one, so the fingerprint differs.
    const SECOND_KEY: &str =
        "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICoNhstm/zoeaAvHBeLWH192X+HjvMcX7NLMGq98FL0B other";
    const SECOND_FINGERPRINT: &str = "SHA256:BT8pCbOuAqBeX1M+Ep3hZMawlY3h74H9/91esEqk3fw";
    let second = |attestation: Option<SignedKeyAttestation>| AddKeyRequest {
        principal_id: alice.id.clone(),
        openssh_public_key: SECOND_KEY.into(),
        comment: "desktop".into(),
        expires_at_unix_ms: None,
        attestation,
    };

    // RED: no attestation. Nothing written.
    let status = harness
        .access()
        .add_key(Request::new(second(None)))
        .await
        .expect_err("a second key without the target's word must be refused");
    assert_eq!(status.code(), Code::FailedPrecondition, "{status}");
    let keys = harness
        .access()
        .list_keys(Request::new(ListKeysRequest {
            principal_id: alice.id.clone(),
        }))
        .await
        .expect("listed")
        .into_inner()
        .keys;
    assert_eq!(keys.len(), 1, "the refusal must have written nothing");

    // RED: an attestation about a different key.
    let status = harness
        .access()
        .add_key(Request::new(second(Some(SignedKeyAttestation {
            attestation: Some(KeyAttestation {
                principal_id: alice.id.clone(),
                fingerprint: "SHA256:somethingelse".into(),
                at_unix_ms: 1,
            }),
            key_fingerprint: first.fingerprint.clone(),
            signature: "-----BEGIN SSH SIGNATURE-----".into(),
        }))))
        .await
        .expect_err("an attestation for another key must be refused");
    assert_eq!(status.code(), Code::InvalidArgument, "{status}");

    // RED: signed by a key alice does not hold.
    let status = harness
        .access()
        .add_key(Request::new(second(Some(SignedKeyAttestation {
            attestation: Some(KeyAttestation {
                principal_id: alice.id.clone(),
                fingerprint: SECOND_FINGERPRINT.into(),
                at_unix_ms: 1,
            }),
            key_fingerprint: "SHA256:notalices".into(),
            signature: "-----BEGIN SSH SIGNATURE-----".into(),
        }))))
        .await
        .expect_err("an attestation by a stranger's key must be refused");
    assert_eq!(status.code(), Code::InvalidArgument, "{status}");

    // GREEN: about this key, by alice's key.
    let added = harness
        .access()
        .add_key(Request::new(second(Some(SignedKeyAttestation {
            attestation: Some(KeyAttestation {
                principal_id: alice.id.clone(),
                fingerprint: SECOND_FINGERPRINT.into(),
                at_unix_ms: 1,
            }),
            key_fingerprint: first.fingerprint.clone(),
            signature: "-----BEGIN SSH SIGNATURE-----".into(),
        }))))
        .await
        .expect("the attested second key is accepted")
        .into_inner()
        .key
        .unwrap();
    assert_eq!(added.fingerprint, SECOND_FINGERPRINT);
}