//! The published agreed-terms pack (`terms.json`), read through the //! reference library. `conformance/verify_terms.py` reads it without. use serde_json::Value; use std::path::PathBuf; #[test] fn every_published_case_reconciles_as_the_pack_says() { let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../../conformance/v1/terms.json"); let doc: Value = serde_json::from_slice(&std::fs::read(path).expect("read the pack")).expect("the pack parses"); let bytes = |name: &Value| hex::decode(doc["docs"][name.as_str().unwrap()].as_str().unwrap()).unwrap(); let key: [u8; 32] = hex::decode(doc["pubkey_hex"].as_str().unwrap()).unwrap().try_into().unwrap(); let cases = doc["cases"].as_array().unwrap(); assert!(cases.len() >= 14, "the pack lost cases"); let mut codes = std::collections::BTreeSet::new(); for case in cases { let signing = match case.get("policy").and_then(Value::as_str) { Some("require-hybrid") => jcp_terms::Signing::RequireHybrid, _ => jcp_terms::Signing::AcceptClassical, }; let got = match jcp_terms::reconcile_under(&bytes(&case["quote"]), &bytes(&case["receipt"]), &bytes(&case["ack"]), doc["steward"].as_str().unwrap(), &key, signing) { Ok(r) => serde_json::json!({ "debited": r.debited, "received": r.received }), Err(e) => { codes.insert(e.code()); Value::String(e.code().to_string()) } }; assert_eq!(got, case["expect"], "{}: {}", case["name"], case["about"]); } // payments in parts let sessions = doc["sessions"].as_array().unwrap(); assert!(sessions.len() >= 8, "the pack lost sessions"); for case in sessions { let receipts: Vec> = case["receipts"].as_array().unwrap().iter().map(&bytes).collect(); let slices: Vec<&[u8]> = receipts.iter().map(Vec::as_slice).collect(); let got = match jcp_terms::reconcile_running(&bytes(&case["quote"]), &slices, &bytes(&case["ack"]), doc["steward"].as_str().unwrap(), &key, jcp_terms::Signing::AcceptClassical) { Ok(r) => serde_json::json!({ "debited": r.debited, "received": r.received, "closed": r.closed }), Err(e) => Value::String(e.code().to_string()), }; assert_eq!(got, case["expect"], "session {}: {}", case["name"], case["about"]); } // routes use jcp_terms::route::{check_route_converting, read_conversion, Hop, RouteError}; let routes = doc["routes"].as_array().unwrap(); assert!(routes.len() >= 12, "the pack lost routes"); for case in routes { let hops: Vec = case["hops"] .as_array() .unwrap() .iter() .map(|h| Hop { currency: h["currency"].as_str().unwrap().into(), amount: h["amount"].as_u64().unwrap(), deadline: h["deadline"].as_u64().unwrap(), condition: jcp_schema::ContentId::from_hex(h["condition"].as_str().unwrap()).unwrap(), }) .collect(); let read: Result, _> = case["conversions"] .as_object() .unwrap() .iter() .map(|(at, c)| read_conversion(&hex::decode(c.as_str().unwrap()).unwrap()).map(|c| (at.parse().unwrap(), c))) .collect(); let got = match read { Err(e) => e.code().to_string(), Ok(conversions) => match check_route_converting(&hops, case["margin"].as_u64().unwrap(), case["now"].as_u64().unwrap(), &conversions) { Ok(()) => "ok".to_string(), Err(e) => match e { RouteError::Empty => "route_empty", RouteError::ConditionDiffers(_) => "route_condition_differs", RouteError::ValueShort { .. } => "route_value_short", RouteError::MarginShort { .. } => "route_margin_short", RouteError::ConversionMissing(_) => "route_conversion_missing", RouteError::ConversionInvalid(_) => "route_conversion_invalid", RouteError::ConversionShort { .. } => "route_conversion_short", RouteError::Lapsed => "route_lapsed", } .to_string(), }, }; assert_eq!(Value::String(got), case["expect"], "route {}: {}", case["name"], case["about"]); } // consent use jcp_terms::consent::{check_decision_under, Outcome}; let consents = doc["consents"].as_array().unwrap(); assert!(consents.len() >= 12, "the pack lost consents"); for case in consents { let h = |v: &Value| hex::decode(v.as_str().unwrap()).unwrap(); let grant = case["grant"].as_object().map(|g| jcp_schema::SignedGrant { grant: jcp_schema::from_canonical_cbor(&h(&g["cbor_hex"])).expect("a grant decodes"), signature: h(&g["signature_hex"]), }); let by: [u8; 32] = h(&case["issuer_pubkey_hex"]).try_into().unwrap(); let signing = match case["policy"].as_str() { Some("require-hybrid") => jcp_terms::Signing::RequireHybrid, _ => jcp_terms::Signing::AcceptClassical, }; let got = match check_decision_under(&h(&case["request"]), &h(&case["decision"]), grant.as_ref(), &jcp_schema::Did(by), signing) { Ok(Outcome::Approved(_)) => "approved".to_string(), Ok(Outcome::Denied) => "denied".to_string(), Err(e) => e.code().to_string(), }; assert_eq!(Value::String(got), case["expect"], "consent {}: {}", case["name"], case["about"]); } // what two intermediaries state about one another use jcp_relay::statement::{agree, follows, read, Disagreement}; let p = &doc["positions"]; let st = |name: &Value| hex::decode(p["statements"][name.as_str().unwrap()].as_str().unwrap()).unwrap(); let who = |name: &Value| { let k: [u8; 32] = hex::decode(p["keys"][name.as_str().unwrap()].as_str().unwrap()).unwrap().try_into().unwrap(); jcp_schema::Did(k) }; let code = |d: Disagreement| match d { Disagreement::Unreadable(why) => why.split(':').next().unwrap_or("").trim().to_string(), Disagreement::ChainBroken => "position_chain_broken".into(), Disagreement::WentBack { .. } => "position_went_back".into(), Disagreement::NotBilateral => "position_not_bilateral".into(), Disagreement::Disagrees { .. } => "position_disagrees".into(), }; assert!(p["agree"].as_array().unwrap().len() >= 6 && p["follows"].as_array().unwrap().len() >= 4, "the pack lost positions"); for case in p["agree"].as_array().unwrap() { let got = match agree(&st(&case["upstream"]), &who(&case["upstream_is"]), &st(&case["downstream"]), &who(&case["downstream_is"])) { Ok(n) => serde_json::json!(n), Err(d) => Value::String(code(d)), }; assert_eq!(got, case["expect"], "agree {}: {}", case["name"], case["about"]); } for case in p["follows"].as_array().unwrap() { let got = match follows(&st(&case["earlier"]), &st(&case["later"])) { Ok(s) => serde_json::json!(s.seq), Err(d) => Value::String(code(d)), }; assert_eq!(got, case["expect"], "follows {}: {}", case["name"], case["about"]); } for case in p["read"].as_array().unwrap() { assert_eq!(serde_json::json!(read(&st(&case["statement"])).unwrap().figures.balance), case["balance"], "read {}", case["name"]); } assert_eq!(codes.len(), 10, "the pack no longer exercises each way the records disagree: {codes:?}"); }