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

nordisk/monetize

crates/monetize-server/tests/estate.rs

//! **`EstateAdmin.Draw` on the wire**: the estate picture has a door, and the
//! door keeps the module's rules.
//!
//! `monetize_server::estate_svg` was finished, tested and unreachable — a
//! renderer with an example and no caller. These tests are about the CALL: that
//! the bearer is required, that the two halves arrive the right way round, that
//! a product this server does not serve is refused by name rather than drawn as
//! a blank board, and that the case the live box is in today — no `[machines]`
//! block at all — answers a board instead of an error or a hang.
//!
//! Measure, never assert a round trip: every claim below is read off the SVG
//! that came back over gRPC, not off an `Estate` built in this file.

use std::sync::Arc;

use monetize::testing::MockProduct;
use monetize_product::Growth;
use monetize_proto::estate_admin_client::EstateAdminClient;
use monetize_proto::{self as pb};
use monetize_server::config::{Config, MachineConfig};
use monetize_server::registry::Registry;
use monetize_server::state::AppState;
use monetize_server::testing::TestServer;
use tonic::metadata::MetadataValue;
use tonic::transport::Channel;
use tonic::Request;

const BEARER: &str = "test-bearer-token-of-sufficient-length";
const GIB: u64 = 1 << 30;

struct Rig {
    product: Arc<MockProduct>,
    #[allow(dead_code)]
    server: TestServer,
    channel: Channel,
}

impl Rig {
    /// A server whose config is exactly `cfg`, serving the gunnar double.
    async fn up(cfg: Config, product: Arc<MockProduct>) -> Rig {
        let product_dyn: Arc<monetize::testing::MockProduct> = product.clone();
        let mut state = AppState::in_memory(Registry::new().shared_product(product_dyn));
        *state.config.get_mut().unwrap() = cfg;
        let server = TestServer::spawn(Arc::new(state), BEARER).await;
        let channel = Channel::from_shared(server.endpoint())
            .unwrap()
            .connect()
            .await
            .unwrap();
        Rig {
            product,
            server,
            channel,
        }
    }

    fn authed<T>(&self, body: T) -> Request<T> {
        let mut r = Request::new(body);
        r.metadata_mut().insert(
            "authorization",
            MetadataValue::try_from(format!("Bearer {BEARER}")).unwrap(),
        );
        r
    }

    async fn draw(&self, product: &str) -> Result<String, tonic::Status> {
        EstateAdminClient::new(self.channel.clone())
            .draw(self.authed(pb::DrawEstateRequest {
                product: product.into(),
                ..Default::default()
            }))
            .await
            .map(|r| r.into_inner().svg)
    }
}

/// `[machines.front]`, `[machines.main]`, `[machines.twin]` — the gunnar.rs
/// shape, with a holger box on the same monetize to prove the filter.
fn three_machines() -> Config {
    let m = |product: &str,
             role: &str,
             title: &str,
             server: &str,
             plan: &str,
             vcpu: u32,
             ram_mb: u64| MachineConfig {
        product: product.into(),
        role: role.into(),
        title: title.into(),
        cloud: "upcloud".into(),
        server: server.into(),
        plan: plan.into(),
        vcpu,
        ram_mb,
        ..MachineConfig::default()
    };
    Config {
        machines: [
            (
                "front".to_string(),
                m(
                    "gunnar",
                    "front",
                    "caddy · the console",
                    "0000-front",
                    "STARTER-4xCPU-8GB",
                    4,
                    8192,
                ),
            ),
            (
                "main".to_string(),
                m(
                    "gunnar",
                    "appliance",
                    "the forge, no shell",
                    "0000-appliance",
                    "STARTER-4xCPU-16GB",
                    4,
                    16384,
                ),
            ),
            (
                "twin".to_string(),
                m(
                    "gunnar",
                    "twin",
                    "the log-holder",
                    "0000-twin",
                    "STARTER-1xCPU-1GB",
                    1,
                    1024,
                ),
            ),
            (
                "holger-box".to_string(),
                m(
                    "holger",
                    "appliance",
                    "somebody else's estate",
                    "0000-holger",
                    "STARTER-1xCPU-1GB",
                    1,
                    1024,
                ),
            ),
        ]
        .into(),
        ..Config::default()
    }
}

/// **The picture is served, and it is a picture.**
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn the_estate_is_drawn_as_one_self_contained_svg_document() {
    let rig = Rig::up(three_machines(), Arc::new(MockProduct::gunnar())).await;
    let svg = rig.draw("gunnar").await.expect("the board is drawn");

    assert!(
        svg.starts_with("<svg "),
        "an SVG document, not a fragment: {}",
        &svg[..svg.len().min(60)]
    );
    assert!(svg.trim_end().ends_with("</svg>"), "and a closed one");
    // Self-contained: nothing is fetched when this is opened. A picture that
    // pulled a font or a script would be a picture that leaks where it is read.
    for forbidden in ["<script", "<image", "xlink:href", "@import", "url(http"] {
        assert!(
            !svg.contains(forbidden),
            "the document must load nothing: {forbidden}"
        );
    }
    assert!(
        svg.contains("gunnar estate"),
        "the board says whose estate it is"
    );
}

/// **The board says how many machines it carries, in the same answer**
/// (owner 2026-10-02: the console says "this estate names no machines yet"
/// rather than showing a blank board) — counted off the roll call the picture
/// was drawn from, the other product's box not counted.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn the_board_counts_the_machines_it_names_and_an_undeclared_estate_is_zero() {
    let rig = Rig::up(three_machines(), Arc::new(MockProduct::gunnar())).await;
    let out = EstateAdminClient::new(rig.channel.clone())
        .draw(rig.authed(pb::DrawEstateRequest {
            product: "gunnar".into(),
            ..Default::default()
        }))
        .await
        .unwrap()
        .into_inner();
    assert_eq!(
        out.machines,
        Some(3),
        "front, main and twin — holger's box is not on gunnar's board"
    );
    let rig = Rig::up(Config::default(), Arc::new(MockProduct::gunnar())).await;
    let out = EstateAdminClient::new(rig.channel.clone())
        .draw(rig.authed(pb::DrawEstateRequest {
            product: "gunnar".into(),
            ..Default::default()
        }))
        .await
        .unwrap()
        .into_inner();
    assert_eq!(
        out.machines,
        Some(0),
        "no [machines] block: zero, said in the answer"
    );
    assert!(out.svg.starts_with("<svg "), "and still a board");
}

/// **The DECLARED half arrives from `[machines]`, and stays labelled declared.**
///
/// The plan's vCPU and RAM are the provider's claim about a box and nothing on
/// this estate reads them off it. The door must not be able to launder one into
/// a measurement on the way past.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_declared_plan_reaches_the_board_still_labelled_as_a_claim() {
    let rig = Rig::up(three_machines(), Arc::new(MockProduct::gunnar())).await;
    let svg = rig.draw("gunnar").await.expect("the board is drawn");

    assert!(
        svg.contains("0000-appliance"),
        "the provider id an operator pastes into a ticket"
    );
    assert!(
        svg.contains("STARTER-4xCPU-16GB"),
        "the plan name, verbatim"
    );
    assert!(
        svg.contains("plan says · STARTER-4xCPU-16GB"),
        "a declaration says so, beside the plan it came from"
    );
    assert!(
        svg.contains("4 vCPU"),
        "the plan's own count, not a reading"
    );
    assert!(
        !svg.contains("measured ·"),
        "nothing measured these boxes, so no caption may claim it did"
    );
    assert!(
        !svg.contains("cores"),
        "cores are what a MEASURED ceiling is drawn in; a plan is vCPU"
    );
}

/// **The MEASURED half arrives from the plugin, and a box it refused is a dash.**
///
/// One product, three machines: the appliance measured, and the twin and the front
/// asked nothing at all (a box's report carries no row for either).
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn what_the_plugin_read_is_drawn_and_what_it_refused_is_a_dash() {
    let product = Arc::new(MockProduct::gunnar());
    product.can_serve_growing(
        30 * GIB,
        40 * GIB,
        Growth::Sealed("this boot stack carries no engine".into()),
    );
    product.measure_ceiling("cpu_millicores", 4000);
    let rig = Rig::up(three_machines(), product).await;
    let svg = rig.draw("gunnar").await.expect("the board is drawn");

    // The appliance: read off the box, so the caption changes word.
    assert!(
        svg.contains("measured · plan STARTER-4xCPU-16GB"),
        "a reading beats a claim, and the caption says which it is"
    );
    assert!(svg.contains("4.0 cores"), "the measured ceiling, in cores");
    assert!(
        svg.contains("disk 10.0 GiB of 40.0 GiB"),
        "40 total, 30 servable — used is the difference"
    );
    // The twin and the front are asked nothing: both are dashes. A zero here
    // would draw an empty disk on a box nobody measured.
    assert!(
        svg.contains("disk — · not measured"),
        "an unmeasured box is a dash, never a zero"
    );
    assert!(
        svg.contains(">not asked<"),
        "nobody asked the front, which is not the same as down"
    );
}

/// **One board per product.** `[machines.<id>] product` filters, so another
/// product's iron is not on this estate.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn the_board_carries_only_the_product_that_was_asked_for() {
    let rig = Rig::up(three_machines(), Arc::new(MockProduct::gunnar())).await;
    let svg = rig.draw("gunnar").await.expect("the board is drawn");

    assert!(svg.contains("0000-appliance"), "gunnar's own box is on it");
    assert!(
        !svg.contains("0000-holger"),
        "another product's estate is another board"
    );
    assert!(
        !svg.contains("somebody else's estate"),
        "and none of its words are on this one"
    );
}

/// **A deployment with no `[machines]` draws a board, and does not 500.**
///
/// This is the case the live box is in today: the block is emitted by a tool
/// (`cargo xtask estate emit`) and no deployment has run it yet, so an empty
/// board is the FIRST thing this route ever serves. It is the honest drawing of
/// "this estate is not written down" — a picture with no chips on it — and it
/// must not be an error, because there is no error: nobody promised machines.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_deployment_that_declares_no_machines_is_answered_with_an_empty_board() {
    let rig = Rig::up(Config::default(), Arc::new(MockProduct::gunnar())).await;
    let svg = rig
        .draw("gunnar")
        .await
        .expect("an undeclared estate is still answered");

    assert!(svg.starts_with("<svg "), "a board, not a refusal");
    assert!(
        svg.contains("gunnar estate"),
        "titled, so the reader knows what is empty"
    );
    assert!(
        !svg.contains("not measured"),
        "no chips at all — an empty board carries no slots"
    );
    assert!(!svg.contains("plan says"));
}

/// **An undeclared estate does not disturb the product's box.**
///
/// With no machines on the board there is nothing for a reading to land on, so
/// the plugin is not called — which is what keeps a picture of an estate nobody
/// wrote down from hanging on a box it was never going to draw.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn an_empty_board_is_drawn_without_asking_the_product_anything() {
    let product = Arc::new(MockProduct::gunnar());
    let rig = Rig::up(Config::default(), product).await;

    let svg = rig
        .draw("gunnar")
        .await
        .expect("a down box cannot stop an empty board");
    assert!(svg.starts_with("<svg "));

    // The same server, once the estate IS declared: the twin nobody could ask
    // is drawn as such rather than as an error.
    let rig = Rig::up(three_machines(), rig.product.clone()).await;
    let svg = rig
        .draw("gunnar")
        .await
        .expect("a refusing plugin is still a board");
    assert!(
        svg.contains(">not asked<"),
        "the plugin refused, so nobody knows — not a zero and not a down"
    );
}

/// **A product this server does not serve is refused by name.**
///
/// `estate_svg::from_config` would draw an empty board for a misspelt product, and
/// an empty board is what an undeclared estate looks like — so a typo would be
/// indistinguishable from the truth. The refusal names what IS linked in.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn an_unknown_product_is_refused_rather_than_drawn_as_a_blank_board() {
    let rig = Rig::up(three_machines(), Arc::new(MockProduct::gunnar())).await;
    let err = rig.draw("idun").await.expect_err("idun is not served here");

    assert_eq!(err.code(), tonic::Code::NotFound);
    assert!(err.message().contains("idun"), "{}", err.message());
    assert!(
        err.message().contains("gunnar"),
        "and it names what IS linked in: {}",
        err.message()
    );
}

/// **The picture is behind the bearer, like every other operator read.**
///
/// It names provider ids, plan names and per-machine disk. A caller without the
/// token gets nothing at all — not a redacted board.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn a_caller_without_the_bearer_is_refused_the_estate() {
    let rig = Rig::up(three_machines(), Arc::new(MockProduct::gunnar())).await;
    let err = EstateAdminClient::new(rig.channel.clone())
        .draw(pb::DrawEstateRequest {
            product: "gunnar".into(),
            ..Default::default()
        })
        .await
        .expect_err("no bearer, no board");

    assert_eq!(err.code(), tonic::Code::Unauthenticated);
}

/// **The same estate draws the same bytes.** The renderer promises it; the door
/// must not undo it, because a caller hashing these bytes for an `ETag` is
/// relying on it.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn two_draws_of_an_unchanged_estate_are_byte_for_byte_equal() {
    let product = Arc::new(MockProduct::gunnar());
    product.can_serve(30 * GIB, 40 * GIB);
    let rig = Rig::up(three_machines(), product).await;

    let once = rig.draw("gunnar").await.expect("drawn");
    let twice = rig.draw("gunnar").await.expect("drawn again");
    assert_eq!(once, twice, "a diff would have to mean the estate changed");
}