use quanttide_work::executor::AGENT;
use quanttide_work::workflow::Step;
use quanttide_work::{criterion, outcome, task, workflow};
use serde_json::{Value, json};
use serde_yaml::{Mapping, Value as Yaml};
use std::fs;
fn as_yaml(value: &Value) -> Yaml {
serde_yaml::to_value(value).unwrap_or(Yaml::Null)
}
fn vectors() -> Vec<(String, Value)> {
let dir = std::path::Path::new("../../tests/contract");
let mut found: Vec<(String, Value)> = fs::read_dir(dir)
.expect("找不到 contract 目录")
.flatten()
.map(|entry| entry.path())
.filter(|path| path.extension().map(|e| e == "json").unwrap_or(false))
.map(|path| {
let name = path.file_name().unwrap().to_string_lossy().to_string();
let text = fs::read_to_string(&path).expect("向量读不了");
(
name,
serde_json::from_str(&text).expect("向量不是合法 JSON"),
)
})
.collect();
found.sort_by(|a, b| a.0.cmp(&b.0));
found
}
fn workflow_of(steps: &Value) -> workflow::Workflow {
workflow::Workflow {
name: "v".to_string(),
description: String::new(),
steps: steps
.as_array()
.cloned()
.unwrap_or_default()
.iter()
.filter_map(|value| value.as_str())
.map(|name| Step {
name: name.to_string(),
description: String::new(),
executor: AGENT.to_string(),
criteria: Vec::new(),
})
.collect(),
}
}
fn task_of(events: &Value) -> task::Task {
let mut payload = Mapping::new();
payload.insert(
Yaml::String("log".into()),
Yaml::Sequence(
events
.as_array()
.cloned()
.unwrap_or_default()
.iter()
.map(as_yaml)
.collect(),
),
);
task::Task::of("v", &Yaml::Mapping(payload))
}
#[test]
fn contract() {
let vectors = vectors();
for (name, vector) in vectors.iter() {
let kind = vector["kind"].as_str().unwrap_or("");
match kind {
"validate" => {
let file = vector["file"].as_str().unwrap_or("");
let got = workflow::validate(&as_yaml(&vector["input"]), file);
match vector["expect"].get("error") {
Some(Value::String(wanted)) => match got {
Ok(()) => panic!("{name}:期望报错,却通过了"),
Err(error) => assert_eq!(&error.0, wanted, "{name}:报错文字不一样"),
},
_ => {
if let Err(error) = got {
panic!("{name}:期望通过,却报错:{}", error.0);
}
}
}
}
"items" => {
let criteria: Vec<Value> = vector["input"].as_array().cloned().unwrap_or_default();
let criteria: Vec<criterion::Criterion> = criteria
.iter()
.map(|value| workflow::criterion_of(&as_yaml(value)))
.collect();
let got: Vec<Value> = criterion::items_of(&criteria)
.into_iter()
.map(|item| {
json!({
"description": item.description,
"kind": item.kind.map(|kind| format!("{kind:?}").to_lowercase()),
"args": item.args,
})
})
.collect();
assert_eq!(
Value::Array(got),
vector["expect"],
"{name}:判据翻出来的不一样"
);
}
"done" => {
let got = task_of(&vector["events"]).done_steps(&workflow_of(&vector["steps"]));
assert_eq!(json!(got), vector["expect"], "{name}:走过哪几步不一样");
}
"section" => {
for case in vector["cases"].as_array().cloned().unwrap_or_default() {
let input = case["input"].as_str().unwrap_or("");
let got = workflow::looks_like_section(input);
assert_eq!(json!(got), case["expect"], "{name}:{input} 算不算小节名");
}
}
"outcome" => {
for case in vector["cases"].as_array().cloned().unwrap_or_default() {
let got = outcome::Outcome::from_json(&case["input"]).to_json();
let note = case["note"].as_str().unwrap_or("");
assert_eq!(got, case["expect"], "{name}:{note} 编解码不一样");
}
}
"expand" => {
let data = vector["data"].as_str().unwrap_or("");
for case in vector["cases"].as_array().cloned().unwrap_or_default() {
let input = case["input"].as_str().unwrap_or("");
let got = workflow::expand_placeholders(input, data);
assert_eq!(json!(got), case["expect"], "{name}:{input} 展开得不对");
}
}
other => panic!("{name}:不认得的向量类型 {other}"),
}
}
assert!(vectors.len() >= 10, "向量太少:{}", vectors.len());
println!("契约:{} 份向量,两侧一致", vectors.len());
}