use crate::{
generated,
invoke::{Error, InvokeOptions, Invoker, Result},
};
use serde_json::Value;
use std::sync::Mutex;
#[derive(Default)]
struct Recorder(Mutex<Vec<(String, Value)>>);
impl Invoker for Recorder {
async fn invoke(&self, operation_id: &str, params: Value, _: InvokeOptions) -> Result<Value> {
self.0
.lock()
.unwrap()
.push((operation_id.to_owned(), params));
Err(Error::new("RECORDED", "recorded invocation"))
}
}
#[tokio::test]
async fn every_generated_callable_invokes_exactly_its_contract_operation() {
let recorder = Recorder::default();
generated::operations::exercise_callables(&recorder).await;
let recorded = recorder.0.into_inner().unwrap();
let contract: Value = serde_json::from_str(generated::CONTRACT_JSON).unwrap();
for (id, params) in &recorded {
let operation = &contract["operations"][id];
let declared = operation["params"].as_array().unwrap();
for field in params.as_object().unwrap().keys() {
assert!(
declared.iter().any(|param| param["name"] == *field),
"{} sent undeclared parameter {}",
id,
field
);
if operation["sdkSurface"] == "document" {
assert!(
!["doc", "sessionId"].contains(&field.as_str()),
"bound calls must inject session identity in their runtime"
);
}
}
}
let actual: Vec<_> = recorded.into_iter().map(|(id, _)| id).collect();
let mut expected: Vec<_> = contract["operations"]
.as_object()
.unwrap()
.keys()
.cloned()
.collect();
expected.sort();
let mut sorted = actual.clone();
sorted.sort();
assert_eq!(
sorted, expected,
"each operation must be called exactly once"
);
let inventory: Vec<Value> = serde_json::from_str(generated::OPERATION_INVENTORY_JSON).unwrap();
assert_eq!(
actual,
inventory
.iter()
.map(|row| row["operationId"].as_str().unwrap().to_owned())
.collect::<Vec<_>>(),
"each method must preserve its own operation identity"
);
}