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

nordisk/holger

server/lib/tests/every_backend_enumerates_on_the_wire.rs

// stinky 1.4 (2026-09-21): the seventeen backends are optional (`fmt-*`); this
// census needs all of them, and `fmt-all` is in server-lib's `default` (the dev set).
//! **Every shipped backend says it enumerates — ON THE WIRE.**
//!
//! `a_zero_nobody_counted.rs` proves by source that each backend crate that
//! overrides `list` also overrides `supports_listing`. This file proves the
//! other half a consumer actually sees: each of the 17 shipped backends,
//! constructed for real and mapped through the ONE mapping the catalog RPC uses
//! (`holger_server_lib::capacity::repository_info`), carries
//! `RepositoryInfo.supports_listing = true`. Plus the two wrappers holger puts
//! in front of them: a proxy over a backend inherits its answer, and a group
//! over enumerating members answers `true` and names them.
//!
//! Before this field existed, monetize had to carry a `CANNOT_ENUMERATE` list
//! of formats by name. The list is now on the wire, per repository.

use std::sync::Arc;

use holger_server_lib::capacity::repository_info;
use holger_server_lib::group::GroupBackend;
use holger_server_lib::proxy::ProxyBackend;
use traits::RepositoryBackendTrait;

/// The 17 shipped backends, one of each, with no archive loaded — the answer
/// must be a property of the FORMAT, not of whether bytes are present.
fn estate() -> Vec<Arc<dyn RepositoryBackendTrait>> {
    let oci_dir = tempfile::tempdir().unwrap();
    let rust_dir = tempfile::tempdir().unwrap();
    let n = |s: &str| s.to_string();
    let all: Vec<Arc<dyn RepositoryBackendTrait>> = vec![
        Arc::new(
            repository_file_oci::OciRepoFile::new(n("file-oci"), oci_dir.path().to_path_buf())
                .unwrap(),
        ),
        Arc::new(repository_file_rust::RustRepoFile::new(
            n("file-rust"),
            rust_dir.path().to_path_buf(),
            "http://127.0.0.1:0".into(),
        )),
        Arc::new(repository_znippy_build_thing::BuildThingRepoZnippy::new(n(
            "build-thing",
        ))),
        Arc::new(repository_znippy_composer::ComposerRepoZnippy::new(n(
            "composer",
        ))),
        Arc::new(repository_znippy_conda::CondaRepoZnippy::new(n("conda"))),
        Arc::new(repository_znippy_deb::DebRepoZnippy::new(n("deb"))),
        Arc::new(repository_znippy_docker::DockerRepoZnippy::new(n("docker"))),
        Arc::new(repository_znippy_gem::GemRepoZnippy::new(n("gem"))),
        Arc::new(repository_znippy_go::GoRepoZnippy::new(n("go"))),
        Arc::new(repository_znippy_helm::HelmRepoZnippy::new(n("helm"))),
        Arc::new(repository_znippy_maven::MavenRepoZnippy::new(n("maven"))),
        Arc::new(repository_znippy_npm::NpmRepoZnippy::new(n("npm"))),
        Arc::new(repository_znippy_nuget::NugetRepoZnippy::new(n("nuget"))),
        Arc::new(repository_znippy_package::ZnippyRepoZnippy::new(n(
            "package",
        ))),
        Arc::new(repository_znippy_python::PipRepoZnippy::new(n("python"))),
        Arc::new(repository_znippy_rpm::RpmRepoZnippy::new(n("rpm"))),
        Arc::new(repository_znippy_rust::RustRepoZnippy::new(n("rust"))),
    ];
    // The tempdirs may go: the file backends only need the path to exist at
    // construction, and this test never writes.
    std::mem::forget(oci_dir);
    std::mem::forget(rust_dir);
    all
}

#[test]
fn all_seventeen_shipped_backends_carry_supports_listing_on_the_wire() {
    let all = estate();
    assert_eq!(all.len(), 17, "the shipped estate is 17 backends");
    let refusing: Vec<String> = all
        .iter()
        .map(|b| repository_info(b.name(), b.as_ref()))
        .filter(|info| !info.supports_listing)
        .map(|info| format!("{} ({})", info.name, info.repo_type))
        .collect();
    assert!(
        refusing.is_empty(),
        "backends still refusing to enumerate on the wire: {refusing:?}"
    );
    for b in &all {
        let info = repository_info(b.name(), b.as_ref());
        assert!(!info.is_group, "{} owns its storage", info.name);
        assert!(info.members.is_empty(), "{} has no members", info.name);
    }
}

#[test]
fn a_proxy_inherits_its_primary_answer_and_a_group_names_its_members() {
    let all = estate();
    let primary = all[10].clone(); // maven
    let proxy = ProxyBackend::new(primary, Vec::new());
    let info = repository_info("maven-proxied", &proxy);
    assert!(
        info.supports_listing,
        "a proxy over an enumerating primary enumerates"
    );
    assert!(!info.is_group);

    let group = GroupBackend::new("maven-all".into(), vec![all[10].clone(), all[11].clone()]);
    let info = repository_info("maven-all", &group);
    assert!(info.is_group);
    assert!(
        info.supports_listing,
        "a group over enumerating members enumerates"
    );
    assert_eq!(info.members, vec!["maven", "npm"]);

    let empty = GroupBackend::new("nothing".into(), Vec::new());
    let info = repository_info("nothing", &empty);
    assert!(info.is_group);
    assert!(
        !info.supports_listing,
        "an empty group has no member to enumerate"
    );
    assert!(info.members.is_empty());
}