use crate::{
generated,
invoke::Invoker,
runtime::{process::Transport, routing},
Client, Error, HostOptions, InvokeOptions, Result,
};
use serde_json::{json, Value};
use std::{collections::BTreeSet, path::Path, sync::Mutex, time::Duration};
#[derive(Default)]
struct Recorder(Mutex<Vec<String>>);
impl Invoker for Recorder {
async fn invoke(&self, id: &str, _: Value, _: InvokeOptions) -> Result<Value> {
self.0.lock().unwrap().push(id.to_owned());
Err(Error::new("RECORDED", "recorded invocation"))
}
}
fn error(error: Error) -> Value {
let mut value = json!({"code":error.code});
if let Some(details) = error.details {
value["details"] = *details;
}
value
}
pub async fn run(cases: &[Value], host: &Path) -> Value {
let recorder = Recorder::default();
generated::operations::exercise_callables(&recorder).await;
let mut ids = recorder.0.into_inner().unwrap();
let rows: Vec<Value> = serde_json::from_str(generated::OPERATION_INVENTORY_JSON).unwrap();
assert_eq!(
ids,
rows.iter()
.map(|row| row["operationId"].as_str().unwrap().to_owned())
.collect::<Vec<_>>()
);
let callables: Vec<_> = rows.iter().map(|row| json!({"path":format!("doc.{}",row["methodPath"].as_str().unwrap()),"operationId":row["operationId"]})).collect();
ids.sort();
let inventory = json!({"operations":ids,"callables":callables});
let mut results = serde_json::Map::new();
for case in cases {
let value = run_case(case, host).await.unwrap_or_else(error);
results.insert(case["id"].as_str().unwrap().to_owned(), value);
}
json!({"inventory":inventory,"cases":results,"policy":crate::runtime::policy()})
}
async fn run_case(case: &Value, host: &Path) -> Result<Value> {
let options = InvokeOptions::default();
let kind = case["kind"].as_str().unwrap();
if kind == "document-route" {
return Ok(json!(routing::can_open_document(&case["params"], &options)));
}
if kind == "document-invoke" {
let (method, params) = routing::document_request(
case["params"]["sessionId"].as_str().unwrap(),
&case["operation"],
&case["params"],
&options,
&BTreeSet::new(),
)?;
return Ok(json!({"method":method,"params":params}));
}
let mut host_options =
HostOptions::new(std::env::var("SUPERDOC_SDK_TEST_NODE").unwrap_or_else(|_| "node".into()));
host_options.prefix_args.push(host.to_str().unwrap().into());
host_options.env.insert(
"SUPERDOC_CONFORMANCE_MODE".into(),
case["hostMode"].as_str().unwrap_or(kind).into(),
);
if kind == "session" {
let client = Client::connect(host_options).await?;
let result = async {
let doc = client
.open(&generated::types::DocOpenParams::new().with_doc("fixture.docx".into()))
.await?;
let text = doc
.api()
.get_text(&generated::types::DocGetTextBoundParams::new())
.await?;
doc.close(&generated::types::DocCloseBoundParams::new())
.await?;
let code = doc
.api()
.get_text(&generated::types::DocGetTextBoundParams::new())
.await
.unwrap_err()
.code;
Ok(json!({"text":text,"closedCode":code}))
}
.await;
client.shutdown().await?;
return result;
}
let transport = Transport::connect(host_options).await?;
let result=async {
let mut request=json!({"argv":routing::argv(&case["operation"],&case["params"],&options,None)?,"stdinBase64":case["stdinBase64"].as_str().unwrap_or("")});
if let Some(auth)=case.get("auth") {
if !transport.features().contains("collaboration.auth.perOpen") {return Err(Error::new("CAPABILITY_UNSUPPORTED","host does not support per-open authentication"));}
request["collaborationAuth"]=auth.clone();
}
let result=transport.request("cli.invoke",request,None,Duration::from_secs(10),false,false).await?;
routing::map_cli(&case["operation"],result)
}.await;
transport.shutdown().await?;
result
}