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

nordisk/monetize

crates/monetize-site-buy/tests/h15_stepped_up_refuses.rs

//! **BuySite's `stepped_up` default REFUSES** (A28 H15). A site that does not prove a fresh key
//! touch can end no estate: the default answers `Err(STEP_UP_WORDS)`, never `Ok(())`. A24 once
//! sketched `Ok(())`; copying it would let a Terminate pass without a fresh touch. The trait's
//! method count is held here too, so the plan and the docs cannot drift from the code again.

use std::collections::BTreeMap;

use axum::http::HeaderMap;
use monetize_site_buy::site::STEP_UP_WORDS;
use monetize_site_buy::{Account, BuySite, Buyer, Refusal, Who};

/// A site that writes only the required methods: every default is the trait's own.
struct Bare;

impl BuySite for Bare {
    fn origin(&self) -> String {
        "https://bare.mock.invalid".into()
    }
    fn product_word(&self) -> String {
        "Bare".into()
    }
    async fn who(&self, _: &HeaderMap) -> Result<Who, Refusal> {
        Ok(Who::Visitor)
    }
    async fn ticket(
        &self,
        _: &HeaderMap,
        _: &Buyer,
        _: &str,
        _: &BTreeMap<String, u64>,
    ) -> Result<Vec<u8>, Refusal> {
        Ok(Vec::new())
    }
    async fn held_caps(&self, _: &HeaderMap, _: &Buyer) -> Result<BTreeMap<String, u64>, Refusal> {
        Ok(BTreeMap::new())
    }
    async fn signup_account(&self, _: &str) -> Result<Account, Refusal> {
        Ok(Account::NoPasskey)
    }
}

#[tokio::test]
async fn the_default_stepped_up_refuses_with_the_step_up_words() {
    let mut fresh = HeaderMap::new();
    fresh.insert("x-test-user", "fresh:acme".parse().unwrap());
    for headers in [HeaderMap::new(), fresh] {
        assert_eq!(
            Bare.stepped_up(&headers).await,
            Err(STEP_UP_WORDS.to_owned()),
            "the default never says yes"
        );
    }
}

/// The trait's methods in site.rs: (required, with a default). A required one ends its
/// signature with `;`, a default one opens a body.
fn trait_counts() -> (usize, usize) {
    let src = include_str!("../src/site.rs");
    let body = src
        .split("pub trait BuySite")
        .nth(1)
        .expect("site.rs declares BuySite");
    let body = &body[..body.find("\n}\n").expect("the trait closes")];
    let (mut required, mut defaulted) = (0, 0);
    let mut sig: Option<String> = None;
    for line in body.lines().map(str::trim) {
        if line.starts_with("fn ") || line.starts_with("async fn ") {
            sig = Some(String::new());
        }
        if let Some(s) = sig.as_mut() {
            s.push_str(line);
            if line.ends_with(';') {
                required += 1;
                sig = None;
            } else if line.ends_with('{') {
                defaulted += 1;
                sig = None;
            }
        }
    }
    (required, defaulted)
}

#[test]
fn buysite_is_six_required_and_six_default_methods() {
    // 6 required (origin, product_word, who, ticket, held_caps, signup_account) and 6 defaults
    // (pages, allowed, trusted_proxies, fixed_buy_url, stepped_up, operator; P4b added the last
    // two to A28's "6 + 5", A32-9 deleted grow_needs_checked_tickets). INTEGRATION-v2 says the same.
    assert_eq!(trait_counts(), (6, 6));
    let doc = include_str!("../../../docs/INTEGRATION-v2.md");
    assert!(
        doc.contains("6 required + 6 defaults"),
        "INTEGRATION-v2 names BuySite's count as the code has it"
    );
}