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

nordisk/holger

estate-tests/tests/round_trip_against_monetizes_guardian.rs

//! ★ **A Holger box against monetize's OWN guardian code, in process**: the guardian's
//! store, its pin window (TOFU on the first call), its signed order queue under holger's
//! product key, its report record. The Link below is monetize-server's `Fleet` service
//! (`admit` + `report`/`orders`), minus tonic.

use std::cell::RefCell;
use std::collections::BTreeMap;
use std::sync::Arc;

use holger_fleet::exec::{Applied, HolgerBox};
use holger_fleet::{Addressee, BoxKey, Fault, Link, Puller, Signed};
use monetize::sign::{Keyring, Signer};
use monetize_embed::fleet::{FleetCall, SignedOrder};
use monetize_guardian::{FleetRefusal, Guardian, GuardianConfig, Store};
use monetize_product::pull::{GrowRoom, PurgeAnswer, PurgeTenant};
use monetize_product::{EntitlementFact, State, TenantId};

const NOW: u64 = 1_800_000_000_000;

struct InProcess {
    g: Arc<Guardian>,
}

fn refusal(r: FleetRefusal) -> Fault {
    Fault::new(
        if matches!(r, FleetRefusal::Unauthenticated) {
            "unauthenticated"
        } else {
            "refused"
        },
        r.to_string(),
    )
}

impl InProcess {
    fn admit(&self, s: &Signed) -> Result<FleetCall, Fault> {
        if !s.box_key.is_empty() {
            self.g
                .first_call(&s.call, &s.signature, &s.box_key, NOW)
                .map_err(refusal)?;
        }
        Ok(s.call.clone())
    }
}

impl Link for InProcess {
    fn report(&mut self, s: Signed) -> Result<u64, Fault> {
        let c = self.admit(&s)?;
        self.g.report(&c, &s.signature, NOW).map_err(refusal)?;
        Ok(30_000)
    }
    fn orders(&mut self, s: Signed) -> Result<Vec<SignedOrder>, Fault> {
        let c = self.admit(&s)?;
        self.g
            .orders(&c, &s.signature, NOW)
            .map(|(o, _)| o)
            .map_err(refusal)
    }
    fn ask(&mut self, s: Signed) -> Result<holger_fleet::Asked, Fault> {
        let c = self.admit(&s)?;
        self.g
            .ask(&c, &s.signature, NOW)
            .map(|a| holger_fleet::Asked {
                case_id: a.case_id,
                words: a.words,
                url: String::new(),
            })
            .map_err(refusal)
    }
}

#[derive(Default)]
struct Main {
    facts: RefCell<BTreeMap<String, EntitlementFact>>,
    did: RefCell<Vec<String>>,
}

impl HolgerBox for Main {
    fn serving(&self) -> bool {
        true
    }
    fn health(&self) -> BTreeMap<String, String> {
        BTreeMap::from([
            ("serving".into(), "yes".into()),
            ("data".into(), "ok".into()),
        ])
    }
    fn product_report(&self) -> Option<String> {
        Some(r#"{"tenants":[{"tenant":"libs","person":false,"seller":false}]}"#.into())
    }
    fn entitle(&self, fact: &EntitlementFact) -> Result<Applied, String> {
        self.facts
            .borrow_mut()
            .insert(fact.tenant.0.clone(), fact.clone());
        Ok(Applied::New(format!(
            "{} is {:?}",
            fact.tenant.0, fact.state
        )))
    }
    fn purge(&self, p: &PurgeTenant) -> Result<PurgeAnswer, String> {
        self.did.borrow_mut().push(format!("purge {}", p.tenant));
        Ok(PurgeAnswer {
            job: format!("purge:{}", p.tenant),
            ..Default::default()
        })
    }
    fn want_room(&self, g: &GrowRoom) -> Result<String, String> {
        self.did.borrow_mut().push(format!("room {}", g.need_bytes));
        Ok("kept".into())
    }
}

#[test]
fn a_holger_main_pins_reports_pulls_runs_and_acks_against_monetizes_guardian() {
    let store = Arc::new(Store::in_memory().unwrap());
    store.declare("acme", "holger").unwrap();
    store.open_pin("acme", "main", NOW + 60_000).unwrap();
    let keys = Arc::new(Keyring::new(Signer::from_bytes(&[6u8; 32])));
    let holger_key = keys.product("holger").public_key();
    let product_signer = keys.product("holger").clone();
    let g = Arc::new(Guardian::new(
        store.clone(),
        keys,
        GuardianConfig::default(),
    ));

    // What monetize queues for the main: a paid repository, a room want, a purge, and a
    // kind Holger has no concept of.
    let mut fact = EntitlementFact {
        tenant: TenantId("libs".into()),
        plan: "o-1".into(),
        state: State::Paid,
        paid_until_unix_ms: Some(NOW + 86_400_000),
        caps: Default::default(),
        source: "payment:stripe:o-1".into(),
        signature: Vec::new(),
        issued_unix_ms: None,
        issued_signature: Vec::new(),
    };
    fact = product_signer.sign_fact(fact);
    for (kind, body) in [
        ("entitlement", serde_json::to_string(&fact).unwrap()),
        (
            "grow_room",
            r#"{"need_bytes":1073741824,"reason":"libs"}"#.to_string(),
        ),
        (
            "purge_tenant",
            r#"{"tenant":"old","reason":"nonpay"}"#.to_string(),
        ),
        (
            "add_principal",
            r#"{"username":"alice","kind":"person","signup":"s-1"}"#.to_string(),
        ),
    ] {
        g.queue_order(
            Guardian::order("holger", "acme", "main", kind, body, NOW, 60_000),
            |_| false,
        )
        .unwrap();
    }

    let me = Addressee {
        product: "holger".into(),
        instance: "acme".into(),
        role: "main".into(),
    };
    let mut p = Puller::new(me, BoxKey::from_seed([3u8; 32]), "holger-test");
    let mut link = InProcess { g: g.clone() };
    let b = Main::default();

    // The first report pins the box's key inside the window.
    p.report(&mut link, b.health(), b.product_report(), None, NOW)
        .expect("the first report pins the box");
    let pulled = p
        .pull(
            &mut link,
            &holger_fleet::Pumped(&b),
            Some(&holger_key),
            0,
            NOW,
        )
        .expect("orders");
    assert!(
        pulled.forged.is_empty(),
        "every order verifies under holger's product key: {pulled:?}"
    );
    let oks: Vec<(String, bool)> = pulled
        .acks
        .iter()
        .map(|a| (a.detail.chars().take(40).collect(), a.ok))
        .collect();
    assert_eq!(pulled.acks.len(), 4, "{oks:?}");
    assert_eq!(
        pulled.acks.iter().map(|a| a.ok).collect::<Vec<_>>(),
        [true, true, true, false],
        "{oks:?}"
    );
    assert!(
        pulled.acks[3].detail.contains("makes no repositories"),
        "a box with no repositories refuses the signup by name: {}",
        pulled.acks[3].detail
    );
    assert!(b.facts.borrow().contains_key("libs"));
    assert_eq!(b.did.borrow().as_slice(), ["room 1073741824", "purge old"]);

    // The acks ride the next report; the guardian records them and the queue empties.
    p.report(&mut link, b.health(), b.product_report(), None, NOW)
        .expect("report");
    let rec = store
        .box_record("acme", "main")
        .expect("the guardian keeps the box's report");
    assert_eq!(
        rec.report.health.get("serving").map(String::as_str),
        Some("yes")
    );
    assert!(rec
        .report
        .product
        .as_deref()
        .is_some_and(|p| p.contains("libs")));
    assert_eq!(rec.acks.len(), 4, "{:?}", rec.acks);
    assert!(
        store.orders_for("acme", "main", 0).is_empty(),
        "the acked orders left the queue"
    );

    // Another box presenting another key outside the window is refused.
    let mut stranger = Puller::new(
        Addressee {
            product: "holger".into(),
            instance: "acme".into(),
            role: "main".into(),
        },
        BoxKey::from_seed([8u8; 32]),
        "x",
    );
    assert_eq!(
        stranger
            .report(&mut link, BTreeMap::new(), None, None, NOW)
            .unwrap_err()
            .code,
        "unauthenticated"
    );
}