crates/gunnar-front/tests/one_sold_door.rs
//! **ONE shop-door client** (T16 / P4-REST, A28 M11): Gunnar's front calls the ONE monetize
//! through the shared client, `monetize-estate-side::sold_door`, as Holger's front does (P4a),
//! over `Fleet.Ask` (P3-FLEET). monetize H16 deleted the old `SoldEstate` door after its one
//! release of overlap: every call rides Fleet, signed as the estate's front, and a door that
//! names no product calls nothing. Over a real gRPC `Fleet` server in this process, every ask
//! verified under the estate's client key with monetize-embed's own verifier.
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use monetize_estate_side::sold_door as shared;
use monetize_proto as pb;
const CLIENT_SEED: [u8; 32] = [0x11; 32];
const AT_MS: i64 = 1_761_400_000_000;
fn hex(b: &[u8]) -> String {
b.iter().map(|x| format!("{x:02x}")).collect()
}
fn settings(addr: SocketAddr) -> toml::Table {
let shop_key = hex(&ed25519_dalek::SigningKey::from_bytes(&[7; 32])
.verifying_key()
.to_bytes());
format!("door = \"http://{addr}\"\ncompany = \"acme\"\nshop_pubkey = \"{shop_key}\"\n")
.parse()
.unwrap()
}
fn clock() -> Arc<dyn Fn() -> i64 + Send + Sync> {
Arc::new(|| AT_MS)
}
/// ★ The bounds and the words are the shop's (`monetize-server::shop_cloud`, held equal in
/// `shop_pairing_contract.rs`) and the bench seam is refused by name as before.
#[test]
fn the_shared_clients_bounds_seam_and_settings_are_gunnars() {
assert_eq!(
shared::UPGRADE_WITHIN,
Duration::from_secs(120 * 60),
"the shop's whole keep boot"
);
assert_eq!(shared::READ_WITHIN, Duration::from_secs(30));
// The one bound that moved (accepted, T16): Gunnar's front gave a Recharge 30 min, the shop's
// own bound; the ONE client waits Holger's hour, so the front never gives up first.
assert_eq!(shared::RECHARGE_WITHIN, Duration::from_secs(60 * 60));
assert!(
shared::RECHARGE_WITHIN >= Duration::from_secs(30 * 60),
"never before the shop's 30 min"
);
let t = settings("127.0.0.1:1".parse().unwrap());
let door = shared::ShopDoor::from_settings(&t, &hex(&CLIENT_SEED)).unwrap();
assert_eq!(door.shop_host(), "127.0.0.1");
let pem = "-----BEGIN CERTIFICATE-----\nBENCH\n-----END CERTIFICATE-----";
let bench = door
.clone()
.with_seam(Some("10.0.2.2:35219"), Some(pem))
.expect("a bench seam");
assert_eq!(
shared::dialled("https://monetize-test.ignalina.dk", bench.seam.as_ref()),
"https://10.0.2.2:35219"
);
assert!(
door.clone()
.with_seam(Some("81.27.107.63:443"), Some(pem))
.unwrap_err()
.contains("fleet-seam-bench-only"),
"a public address is no seam"
);
assert!(
door.clone()
.with_seam(None, Some(pem))
.unwrap_err()
.contains("fleet-seam-bench-only"),
"a CA with no bench address"
);
// The one setting that moved (accepted, T16): Gunnar's front refused a door with no
// `shop_pubkey`, though it verified nothing under it; the ONE client takes one, and a key
// that is named must still be a key.
let mut keyless = t.clone();
keyless.remove("shop_pubkey");
assert!(shared::ShopDoor::from_settings(&keyless, &hex(&CLIENT_SEED)).is_ok());
let mut bad = t.clone();
bad.insert("shop_pubkey".into(), toml::Value::String("zz".repeat(32)));
assert!(shared::ShopDoor::from_settings(&bad, &hex(&CLIENT_SEED))
.unwrap_err()
.contains("shop_pubkey"));
}
// ── P3-FLEET: the calls ride Fleet (H16: no old door to fall back to) ───────────────────────
/// A ONE box after the fold: `Fleet` only (no `SoldEstate` mounted). Every front's ask is a
/// `FleetCall` of `role = front`, signed by the estate's client key, for product `gunnar`.
#[derive(Clone, Default)]
struct FleetOnly {
asked: Arc<Mutex<Vec<String>>>,
}
#[tonic::async_trait]
impl pb::fleet_server::Fleet for FleetOnly {
async fn report(
&self,
_: tonic::Request<pb::FleetSigned>,
) -> Result<tonic::Response<pb::FleetReportAnswer>, tonic::Status> {
Err(tonic::Status::unimplemented("not this test's"))
}
async fn orders(
&self,
_: tonic::Request<pb::FleetSigned>,
) -> Result<tonic::Response<pb::FleetOrdersAnswer>, tonic::Status> {
Err(tonic::Status::unimplemented("not this test's"))
}
async fn ask(
&self,
r: tonic::Request<pb::FleetSigned>,
) -> Result<tonic::Response<pb::FleetAskAnswer>, tonic::Status> {
use prost::Message as _;
let s = r.into_inner();
let c = monetize_embed::fleet::FleetCall {
product: s.product.clone(),
instance: s.instance.clone(),
role: s.role.clone(),
call: s.call.clone(),
body: s.body.clone(),
at_unix_ms: s.at_unix_ms,
};
let client = monetize_embed::VerifyingKey::from_bytes(
&ed25519_dalek::SigningKey::from_bytes(&CLIENT_SEED)
.verifying_key()
.to_bytes(),
)
.unwrap();
monetize_embed::fleet::verify_fleet_call(&c, &s.signature, &client, s.at_unix_ms)
.map_err(|e| tonic::Status::unauthenticated(format!("{e:?}")))?;
if (s.product.as_str(), s.instance.as_str(), s.role.as_str()) != ("gunnar", "acme", "front")
{
return Err(tonic::Status::unauthenticated(format!(
"not gunnar/acme's front: {}/{}/{}",
s.product, s.instance, s.role
)));
}
let ask: monetize_embed::fleet::Ask = serde_json::from_str(&s.body)
.map_err(|e| tonic::Status::invalid_argument(e.to_string()))?;
self.asked.lock().unwrap().push(ask.kind.clone());
let answer = match ask.kind.as_str() {
"update_take" => pb::EstateUpdateTakeResponse {
open: true,
release: "03".into(),
role: "twin".into(),
..Default::default()
}
.encode_to_vec(),
"recharge" => pb::EstateRechargeResponse {
words: "the main laid again over Fleet".into(),
..Default::default()
}
.encode_to_vec(),
other => {
return Err(tonic::Status::invalid_argument(format!(
"no front ask {other}"
)))
}
};
Ok(tonic::Response::new(pb::FleetAskAnswer {
answer,
..Default::default()
}))
}
}
fn serve_fleet(rt: &tokio::runtime::Runtime, fleet: FleetOnly) -> SocketAddr {
let addr = std::net::TcpListener::bind("127.0.0.1:0")
.unwrap()
.local_addr()
.unwrap();
rt.spawn(
tonic::transport::Server::builder()
.add_service(pb::fleet_server::FleetServer::new(fleet))
.serve(addr),
);
for _ in 0..200 {
if std::net::TcpStream::connect(addr).is_ok() {
return addr;
}
std::thread::sleep(Duration::from_millis(10));
}
panic!("the test Fleet did not listen at {addr}");
}
/// Gunnar's front's door as the feed raises it (no `product` in its `shop_door.toml`: a feed
/// from before the fold).
fn fed_door(addr: SocketAddr) -> shared::ShopDoor {
let shop_key = hex(&ed25519_dalek::SigningKey::from_bytes(&[7; 32])
.verifying_key()
.to_bytes());
let feed = std::collections::BTreeMap::from([
(
shared::FEED_SHOP_DOOR.to_string(),
format!("door = \"http://{addr}\"\ncompany = \"acme\"\nshop_pubkey = \"{shop_key}\"\n"),
),
(
format!("secret.{}", shared::CLIENT_KEY_SECRET),
hex(&CLIENT_SEED),
),
]);
gunnar_front::recharge::shop_door(&feed)
.expect("a sold front")
.expect("its door")
}
/// ★ **Gunnar's front works against a ONE box that has only `Fleet`** (the fold, P3-FLEET):
/// its fed door rides `Fleet.Ask` as product `gunnar`, signed as the estate's front.
#[test]
fn gunnars_front_rides_fleet_on_a_fleet_only_one_box() {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build()
.unwrap();
let fleet = FleetOnly::default();
let door = fed_door(serve_fleet(&rt, fleet.clone()));
assert!(
door.on_fleet(),
"Gunnar's front names itself: its calls ride Fleet"
);
assert_eq!(door.fleet_product().as_deref(), Some("gunnar"));
assert_eq!(
door.update_take().expect("the take over Fleet").role,
"twin"
);
assert_eq!(
door.recharge_box("main", "seed-toml", false)
.expect("a recharge over Fleet")
.words,
"the main laid again over Fleet"
);
assert_eq!(
*fleet.asked.lock().unwrap(),
vec!["update_take".to_string(), "recharge".to_string()]
);
}
/// ★ **H16: there is no old door to fall back to.** Every one of the five calls rides `Fleet.Ask`
/// (the shared client from Gunnar's own settings, told its product), and a door told no product
/// calls nothing, by name — the ONE box serves no `SoldEstate` any more.
#[test]
fn every_call_rides_fleet_and_a_door_with_no_product_calls_nothing() {
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(2)
.enable_all()
.build()
.unwrap();
let fleet = FleetOnly::default();
let addr = serve_fleet(&rt, fleet.clone());
let bare = shared::ShopDoor::from_settings(&settings(addr), &hex(&CLIENT_SEED))
.expect("the door")
.with_clock(clock());
assert_eq!(
bare.update_take().unwrap_err(),
shared::NO_PRODUCT,
"no product: nothing is called"
);
assert!(fleet.asked.lock().unwrap().is_empty());
let door = bare.or_over_fleet("gunnar");
assert_eq!(
door.update_take().expect("the take over Fleet").role,
"twin"
);
assert!(door
.recharge_box("main", "seed-toml", false)
.expect("a recharge over Fleet")
.words
.contains("over Fleet"));
assert_eq!(
*fleet.asked.lock().unwrap(),
vec!["update_take".to_string(), "recharge".to_string()]
);
}