use crate::door::assistant_test_support::*;
use serde_json::{Value, json};
use std::{sync::atomic::Ordering, time::Duration};
const REASON: &str = "The dose is wrong for this child.";
fn invalid(f: &Fixture, body: &Value) -> Result {
let text = written(f.answer("POST", "/decide", body))?;
anyhow::ensure!(
text.starts_with("HTTP/1.1 400 ") && text.contains("assistant_decision_invalid"),
"invalid decision was admitted: {text}"
);
assert_eq!(f.door.issuer().draft_reads.load(Ordering::SeqCst), 0);
assert_eq!(f.door.issuer().decisions.load(Ordering::SeqCst), 0);
Ok(())
}
#[test]
fn r4_1_refusal_without_the_persons_reason_never_asks_lys() -> Result {
let f = Fixture::new(json!({}))?;
let received = ack(f.seat("agent-pass", "wards")?);
invalid(&f, &json!({"draft":"draft-accepted","decision":"refuse"}))?;
drop(received);
Ok(())
}
#[test]
fn r4_1_refusal_reason_is_trimmed_and_reaches_the_issuer_exactly() -> Result {
let f = Fixture::new(json!({}))?;
let received = ack(f.seat("agent-pass", "wards")?);
let text = written(f.answer(
"POST",
"/decide",
&json!({"draft":"draft-accepted","decision":"refuse","reason":format!(" \n{REASON}\t ")}),
))?;
anyhow::ensure!(text.starts_with("HTTP/1.1 200 "), "{text}");
assert_eq!(
f.door
.issuer()
.decision_requests
.lock()
.map_err(|_| anyhow::anyhow!("requests"))?
.as_slice(),
&[json!({"draft":"draft-accepted","decision":"refuse","reason":REASON})]
);
let told = received.recv()?;
assert_eq!(told["op"]["decided"]["decision"], "refuse");
assert_eq!(told["op"]["decided"]["reason"], REASON);
Ok(())
}
#[test]
fn r4_1_approval_refuses_any_reason_before_asking_lys() -> Result {
let f = Fixture::new(json!({}))?;
let seat = f.seat("agent-pass", "wards")?;
for reason in [json!(REASON), json!(""), Value::Null, json!(3)] {
invalid(
&f,
&json!({"draft":"draft-accepted","decision":"approve","reason":reason}),
)?;
}
drop(seat);
Ok(())
}
#[test]
fn r4_1_refusal_reason_has_one_to_five_hundred_characters() -> Result {
let f = Fixture::new(json!({}))?;
let seat = f.seat("agent-pass", "wards")?;
for reason in [
json!(" \t\n"),
json!("é".repeat(501)),
Value::Null,
json!(3),
] {
invalid(
&f,
&json!({"draft":"draft-accepted","decision":"refuse","reason":reason}),
)?;
}
drop(seat);
Ok(())
}
#[test]
fn r4_1_five_hundred_unicode_characters_are_accepted() -> Result {
let f = Fixture::new(json!({}))?;
let received = ack(f.seat("agent-pass", "wards")?);
let reason = "é".repeat(500);
check(
f.answer(
"POST",
"/decide",
&json!({"draft":"draft-accepted","decision":"refuse","reason":reason}),
),
200,
"refused",
)?;
assert_eq!(received.recv()?["op"]["decided"]["decision"], "refuse");
Ok(())
}
#[tokio::test(flavor = "current_thread")]
async fn r4_1_native_lys_post_carries_the_persons_reason_exactly() -> Result {
let listener = std::net::TcpListener::bind("127.0.0.1:0")?;
let issuer = format!("http://{}", listener.local_addr()?);
let worker = std::thread::spawn(move || -> Result {
for step in 0..2 {
let (socket, _) = listener.accept()?;
let request =
crate::door::http::read(&mut std::io::BufReader::new(&socket), 16384, 1_048_576)?;
assert_eq!(request.header("authorization"), Some("Bearer person-pass"));
let response = if step == 0 {
crate::door::http::Response::json(
200,
&json!({"drafts":[{"id":"draft-native","app":"haematite","holder":"agent-assistant","responsible":PERSON,"target":{"kind":"haematite.database","id":"wards","action":"agent_permission"},"request_digest":"a".repeat(64)}],"next":null}),
)
} else {
assert_eq!(request.path, "/api/product-drafts/draft-native/refuse");
let body: Value = serde_json::from_slice(&request.body)?;
assert_eq!(
body["reason"], REASON,
"Lys did not receive the person's own words"
);
crate::door::http::Response::json(
200,
&json!({"draft":"draft-native","state":"refused"}),
)
};
response.write_to(&socket)?;
}
Ok(())
});
let client = crate::door::assistant_lys::Client {
issuer,
http: reqwest::Client::builder()
.no_proxy()
.timeout(Duration::from_secs(5))
.build()?,
};
let answer = client
.decide("person-pass", "draft-native", "refuse", Some(REASON))
.await;
worker.join().map_err(|_| anyhow::anyhow!("issuer"))??;
assert_eq!(answer?["state"], "refused");
Ok(())
}