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/allowance_is_labelled.rs

//! **A cap gunnar reports must say what gunnar DOES at it.**
//!
//! `Usage.Read` reports seven caps to the tenant — `pack_bytes`, `cache_bytes`,
//! `cold_bytes`, `lfs_bytes`, `tombstoned_bytes`, `repos`, `objects` — and until
//! this file existed it reported all seven the same way: a number, in the
//! meter's unit, under a message called `Allowance`. **Three of them are a
//! wall.** The other four are a reading.
//!
//! > *"If a signed fact carries `caps["repos"]=5`, the console shows the tenant
//! > a limit of 5 and nothing on the box refuses the sixth."*
//!
//! monetize has always known the difference — `monetize_product::Enforcement`
//! is `Enforced` / `Measured` / `Provisioned` and `products/gunnar`'s `METERS`
//! marks `repos` as `Measured` with a test pinning it — but nothing carried that
//! word across the wire, so gunnar's own surface flattened it back into "a
//! limit". A number a tenant is shown as a limit that no code enforces is a
//! promise the product does not keep, whatever a table in another repository
//! says about it.
//!
//! # Red first — MEASURED 2026-09-07
//!
//! Run against `Allowance` with the field added to `usage.proto` and nothing
//! filling it, which is the honest shape of the defect: the message could
//! finally SAY what a cap does and `allowance_of` still said nothing.
//!
//! ```text
//! ---- a_measured_cap_is_not_reported_as_a_wall stdout ----
//! thread 'a_measured_cap_is_not_reported_as_a_wall' panicked at
//! crates/gunnar-grpc/tests/allowance_is_labelled.rs:96:5:
//! assertion `left == right` failed: `repos` is reported as a cap of 5 and the
//! tenant is told nothing about what happens at it
//!   left: None
//!  right: Some(Measured)
//! ```
//!
//! # Why the enforcement word is reported for a meter with NO cap too
//!
//! Because "nothing caps your repositories, and nothing would" and "you have no
//! repository cap yet" are different sentences, and a console rendering a usage
//! row needs the first one even where there is no cap to put beside it. The map
//! therefore has all seven entries on every read, on the metered appliance and
//! on the community one alike — it describes the CODE, not the tenant.

#![cfg(feature = "metered")]

use std::collections::BTreeMap;

use gunnar_grpc::service::usage::allowance_of;
use gunnar_proto::Enforcement;

/// The word this build attaches to `meter`, or `None` when it attached none.
fn word(allowance: &gunnar_proto::Allowance, meter: &str) -> Option<Enforcement> {
    allowance
        .enforcement
        .get(meter)
        .copied()
        .and_then(|code| Enforcement::try_from(code).ok())
}

fn caps(pairs: &[(&str, u64)]) -> BTreeMap<String, u64> {
    pairs
        .iter()
        .map(|(name, cap)| ((*name).to_owned(), *cap))
        .collect()
}

/// **The defect, exactly as the auditor wrote it.** A fact sells five
/// repositories; gunnar refuses the sixth nowhere; the tenant must be told
/// which of those two facts they are looking at.
#[test]
fn a_measured_cap_is_not_reported_as_a_wall() {
    let allowance = allowance_of(&caps(&[("repos", 5), ("pack_bytes", 10 << 30)]));

    // The cap is still REPORTED. Suppressing it was the other candidate fix and
    // it is the wrong one: the tenant bought the number, monetize prices the
    // order by it, and hiding it would answer "what did I buy" with silence.
    assert_eq!(allowance.repos, Some(5));
    assert_eq!(allowance.pack_bytes, Some(10 << 30));

    assert_eq!(
        word(&allowance, "repos"),
        Some(Enforcement::Measured),
        "`repos` is reported as a cap of 5 and the tenant is told nothing about \
         what happens at it"
    );
    assert_eq!(
        word(&allowance, "pack_bytes"),
        Some(Enforcement::Enforced),
        "`pack_bytes` is cut mid-stream by the receive loop; this one IS a wall"
    );
}

/// **All seven, always** — including the four with no cap in this fact. See the
/// module comment for why an uncapped meter still carries the word.
#[test]
fn every_meter_the_message_has_a_field_for_carries_a_word() {
    let allowance = allowance_of(&caps(&[]));

    for meter in [
        "pack_bytes",
        "cache_bytes",
        "cold_bytes",
        "lfs_bytes",
        "tombstoned_bytes",
        "repos",
        "objects",
    ] {
        assert!(
            matches!(
                word(&allowance, meter),
                Some(Enforcement::Measured) | Some(Enforcement::Enforced)
            ),
            "{meter} has an `Allowance` field and no word about what a cap on it \
             would do: {:?}",
            word(&allowance, meter)
        );
    }
    assert_eq!(
        allowance.enforcement.len(),
        7,
        "the map describes the seven meters this message has fields for and \
         nothing else: {:?}",
        allowance.enforcement
    );
}

/// **The three doors, named.** This is the list a future meter has to argue its
/// way onto: a `Meter` is `Enforced` here only when a REFUSAL exists on this
/// box, and the doc comment on `service::meters::METERS` names the file and the
/// line for each of them. A meter promoted here without a door would make this
/// test green and the product a liar, which is why the assertion is by name and
/// not a count.
#[test]
fn exactly_the_meters_with_a_door_are_enforced() {
    let allowance = allowance_of(&caps(&[]));
    let mut walls: Vec<&str> = allowance
        .enforcement
        .iter()
        .filter(|(_, code)| Enforcement::try_from(**code) == Ok(Enforcement::Enforced))
        .map(|(meter, _)| meter.as_str())
        .collect();
    walls.sort_unstable();

    assert_eq!(
        walls,
        ["cache_bytes", "lfs_bytes", "pack_bytes"],
        "the set of caps this box actually refuses on has changed; a meter joins \
         it when a door lands, not before"
    );
}