use content_addressable::{canonical, ContentAddressable, ContentId};
use newt_interaction::{
binding::{validate_response, HandlerId, RegisteredAction, ResponderContext},
publish, Audience, ControlValue, HostMint, InteractionDefinition, InteractionInstance,
OptionId, Refusal, Response,
};
const EXTERNAL: &str = include_str!("../conformance/responses.json");
const VECTORS: &str = include_str!("data/interaction-vectors.json");
#[derive(serde::Deserialize)]
struct Record {
name: String,
json: serde_json::Value,
dagcbor_hex: String,
content_id: String,
}
fn from_hex(text: &str) -> Vec<u8> {
assert!(text.len().is_multiple_of(2), "hex must be byte-aligned");
(0..text.len())
.step_by(2)
.map(|i| u8::from_str_radix(&text[i..i + 2], 16).expect("hex digit"))
.collect()
}
fn external() -> Vec<Record> {
let records: Vec<Record> = serde_json::from_str(EXTERNAL).expect("external responses parse");
assert!(
!records.is_empty(),
"the external consumer published no responses; every assertion below \
would pass over an empty corpus"
);
records
}
fn offer() -> (InteractionDefinition, InteractionInstance) {
let vectors: Vec<Record> = serde_json::from_str(VECTORS).expect("vectors parse");
let mut definition = None;
let mut instance = None;
for v in &vectors {
let bytes = from_hex(&v.dagcbor_hex);
if v.name.starts_with("definition/") {
definition = Some(canonical::from_canonical_dagcbor_checked(&bytes).expect("def"));
} else if v.name.starts_with("instance/") {
instance = Some(canonical::from_canonical_dagcbor_checked(&bytes).expect("inst"));
}
}
(
definition.expect("a definition vector"),
instance.expect("an instance vector"),
)
}
fn registered() -> Vec<RegisteredAction> {
vec![RegisteredAction {
option: OptionId::new("deny").unwrap(),
handler: HandlerId::new("gate::deny").unwrap(),
audiences: vec![Audience::Terminal, Audience::Web],
}]
}
#[test]
fn every_externally_authored_response_is_accepted() {
let (definition, instance) = offer();
let lifecycle = publish(&HostMint::assert_host_authority(), &instance, &definition)
.expect("the offer publishes");
let actions = registered();
let context = ResponderContext {
workspace_key: &instance.scope.workspace_key,
registered: &actions,
};
for record in external() {
let bytes = from_hex(&record.dagcbor_hex);
let response: Response = canonical::from_canonical_dagcbor_checked(&bytes)
.unwrap_or_else(|e| panic!("`{}`: foreign bytes rejected: {e}", record.name));
assert_eq!(
response.canonical_form().expect("re-encode"),
bytes,
"`{}`: Rust re-encodes the foreign record differently",
record.name
);
assert_eq!(
ContentId::from_canonical_bytes(&bytes).to_string(),
record.content_id,
"`{}`: the consumer predicted a different id",
record.name
);
let from_json: Response = serde_json::from_value(record.json.clone())
.unwrap_or_else(|e| panic!("`{}`: JSON face does not decode: {e}", record.name));
assert_eq!(
from_json.canonical_form().expect("re-encode"),
bytes,
"`{}`: the published JSON and the published bytes are different records",
record.name
);
let accepted = validate_response(&definition, &instance, &lifecycle, &response, &context)
.unwrap_or_else(|e| panic!("`{}`: refused: {e}", record.name));
assert_eq!(
accepted.response.to_string(),
record.content_id,
"`{}`: accepted under a different id than it was published with",
record.name
);
}
}
#[test]
fn a_perturbed_external_record_is_refused_or_reidentified() {
for record in external() {
let mut bytes = from_hex(&record.dagcbor_hex);
let last = bytes.last_mut().expect("non-empty");
*last ^= 0x01;
if let Ok(response) = canonical::from_canonical_dagcbor_checked::<Response>(&bytes) {
assert_ne!(
response.response_id().expect("id").to_string(),
record.content_id,
"`{}`: a flipped byte produced the same identity",
record.name
);
}
}
}
#[test]
fn a_type_confused_external_response_is_refused() {
let (definition, instance) = offer();
let lifecycle = publish(&HostMint::assert_host_authority(), &instance, &definition)
.expect("the offer publishes");
let actions = registered();
let context = ResponderContext {
workspace_key: &instance.scope.workspace_key,
registered: &actions,
};
let mut mutated = 0;
for record in external() {
let bytes = from_hex(&record.dagcbor_hex);
let mut response: Response =
canonical::from_canonical_dagcbor_checked(&bytes).expect("decodes");
let submission = response.values.first_mut().expect("one submission");
submission.value = match &submission.value {
ControlValue::Choice { .. } => ControlValue::Text {
text: "not a choice".to_string(),
},
_ => ControlValue::Choice {
option: OptionId::new("deny").unwrap(),
},
};
mutated += 1;
match validate_response(&definition, &instance, &lifecycle, &response, &context) {
Err(Refusal::WrongControlType { .. }) => {}
Err(other) => panic!(
"`{}`: refused, but for the wrong reason: {other}",
record.name
),
Ok(_) => panic!("`{}`: a value of the wrong type was accepted", record.name),
}
}
assert!(
mutated > 0,
"no record was mutated; the twin proved nothing"
);
}