//! Generated by `contract-codegen` from the checked-in contract schemas.
//!
//! This file is the Rust client's walk over every operation the server declares.
//!
//! Do not edit by hand: change the contracts, regenerate with `just
//! generate-clients`, and commit what that writes.
//!
//! Every method this client exposes is driven here against a stub host on a
//! real socket: what the host received is asserted against what the operation
//! declares, and what the method answered is asserted to carry, field for
//! field, exactly the document the host sent. A client that transformed a
//! value on the way out fails that equality rather than needing a probe that
//! guesses how it transformed it.
#[path = "support/host.rs"]
mod host;
use host::Host;
use printobserver_sdk::{
AcknowledgementDisposition, ActionExecutedPayload, Actor, Client, ClientError, EVENT_KINDS,
EventId, FileName, JobManifest, RejectionReason,
};
use serde::de::DeserializeOwned;
use serde_json::Value;
/// One document of the contracts, as the type it is a document of.
///
/// # Panics
///
/// Panics when the document is not one of that type, which is a walk and a
/// client generated from two different descriptions.
fn parsed<T: DeserializeOwned>(document: &str) -> T {
serde_json::from_str(document).expect("a generated document is of its own type")
}
/// The actor every client of this walk acts as.
fn actor() -> Actor {
parsed::<Actor>(r#"{"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}"#)
}
/// `prints` sends what it declares and answers what the server sent.
#[test]
fn prints_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"active": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prints": [{"end_reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "ended_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "file_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "job_print_time_s": 7, "job_started_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "narrowings": [{"adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied": {"max": 1.5, "min": 1.5}, "requested": {"max": 1.5, "min": 1.5}}], "opened_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "provider_print_id": 7, "state": "operational"}]}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client.prints().expect("`prints` is answered");
let received = host.received();
assert_eq!(received.method, "GET");
assert_eq!(received.target, "/v1/prints");
assert_eq!(received.body, "");
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `status` sends what it declares and answers what the server sent.
#[test]
fn status_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"interventions": [{"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}], "job": {"completion": {"out_of_range": true, "value": 1.5}, "error": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "estimated_print_time_s": 7, "filament": [{"length_mm": 1.5, "tool": 7, "volume_cm3": 1.5}], "file_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "file_origin": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "file_position_bytes": 7, "print_time_left_s": 7, "print_time_s": 7, "size_bytes": 7, "state": "operational"}, "print": {"end_reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "ended_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "file_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "job_print_time_s": 7, "job_started_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "narrowings": [{"adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied": {"max": 1.5, "min": 1.5}, "requested": {"max": 1.5, "min": 1.5}}], "opened_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "provider_print_id": 7, "state": "operational"}, "printer": {"bed": {"actual_c": {"out_of_range": true, "value": 1.5}, "offset_c": {"out_of_range": true, "value": 1.5}, "target_c": {"out_of_range": true, "value": 1.5}}, "chamber": {"actual_c": {"out_of_range": true, "value": 1.5}, "offset_c": {"out_of_range": true, "value": 1.5}, "target_c": {"out_of_range": true, "value": 1.5}}, "connection": "operational", "fan_percent": {"out_of_range": true, "value": 1.5}, "feedrate_factor": {"out_of_range": true, "value": 1.5}, "flowrate_factor": {"out_of_range": true, "value": 1.5}, "observed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "tools": [{"actual_c": {"out_of_range": true, "value": 1.5}, "offset_c": {"out_of_range": true, "value": 1.5}, "target_c": {"out_of_range": true, "value": 1.5}}]}, "session": {"close_reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "closed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "created_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "harness_identity": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "last_turn_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.status("0198f0a1-2b3c-7d4e-8f90-123456789abc")
.expect("`status` is answered");
let received = host.received();
assert_eq!(received.method, "GET");
assert_eq!(
received.target,
"/v1/prints/0198f0a1-2b3c-7d4e-8f90-123456789abc/status"
);
assert_eq!(received.body, "");
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `context` sends what it declares and answers what the server sent.
#[test]
fn context_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"context": {"bounds": {"allowed": {"feedrate": {"max": 1.5, "min": 1.5}}}, "interventions": [{"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}], "job": {"completion": {"out_of_range": true, "value": 1.5}, "error": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "estimated_print_time_s": 7, "filament": [{"length_mm": 1.5, "tool": 7, "volume_cm3": 1.5}], "file_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "file_origin": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "file_position_bytes": 7, "print_time_left_s": 7, "print_time_s": 7, "size_bytes": 7, "state": "operational"}, "latest_image": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "manifest": {"allowed": {"feedrate": {"max": 1.5, "min": 1.5}}, "file_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "metadata": {"feedrate": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "nozzle_diameter_mm": 1.5, "slicer_profile": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "print": {"end_reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "ended_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "file_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "job_print_time_s": 7, "job_started_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "narrowings": [{"adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied": {"max": 1.5, "min": 1.5}, "requested": {"max": 1.5, "min": 1.5}}], "opened_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "provider_print_id": 7, "state": "operational"}, "printer": {"bed": {"actual_c": {"out_of_range": true, "value": 1.5}, "offset_c": {"out_of_range": true, "value": 1.5}, "target_c": {"out_of_range": true, "value": 1.5}}, "chamber": {"actual_c": {"out_of_range": true, "value": 1.5}, "offset_c": {"out_of_range": true, "value": 1.5}, "target_c": {"out_of_range": true, "value": 1.5}}, "connection": "operational", "fan_percent": {"out_of_range": true, "value": 1.5}, "feedrate_factor": {"out_of_range": true, "value": 1.5}, "flowrate_factor": {"out_of_range": true, "value": 1.5}, "observed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "tools": [{"actual_c": {"out_of_range": true, "value": 1.5}, "offset_c": {"out_of_range": true, "value": 1.5}, "target_c": {"out_of_range": true, "value": 1.5}}]}, "recent_events": [{"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "image": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "kind": "action_executed", "payload": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "intervention_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "raw": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "received_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "source": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}]}, "image_path": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.context("0198f0a1-2b3c-7d4e-8f90-123456789abc")
.expect("`context` is answered");
let received = host.received();
assert_eq!(received.method, "GET");
assert_eq!(
received.target,
"/v1/prints/0198f0a1-2b3c-7d4e-8f90-123456789abc/context"
);
assert_eq!(received.body, "");
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `image` sends what it declares and answers what the server sent.
#[test]
fn image_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"path": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"byte_len": 7, "content_type": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "event_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "fetched_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "relative_path": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "source_url": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.image("0198f0a1-2b3c-7d4e-8f90-123456789abc")
.expect("`image` is answered");
let received = host.received();
assert_eq!(received.method, "GET");
assert_eq!(
received.target,
"/v1/images/0198f0a1-2b3c-7d4e-8f90-123456789abc"
);
assert_eq!(received.body, "");
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `history` sends what it declares and answers what the server sent.
#[test]
fn history_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"events": [{"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "image": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "kind": "action_executed", "payload": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "intervention_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "raw": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "received_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "source": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}]}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.history("0198f0a1-2b3c-7d4e-8f90-123456789abc", Some(7))
.expect("`history` is answered");
let received = host.received();
assert_eq!(received.method, "GET");
assert_eq!(
received.target,
"/v1/prints/0198f0a1-2b3c-7d4e-8f90-123456789abc/history?limit=7"
);
assert_eq!(received.body, "");
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `manifest_get` sends what it declares and answers what the server sent.
#[test]
fn manifest_get_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"manifest": {"allowed": {"feedrate": {"max": 1.5, "min": 1.5}}, "file_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "metadata": {"feedrate": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "nozzle_diameter_mm": 1.5, "slicer_profile": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "narrowings": [{"adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied": {"max": 1.5, "min": 1.5}, "requested": {"max": 1.5, "min": 1.5}}]}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.manifest_get("0198f0a1-2b3c-7d4e-8f90-123456789abc")
.expect("`manifest_get` is answered");
let received = host.received();
assert_eq!(received.method, "GET");
assert_eq!(
received.target,
"/v1/prints/0198f0a1-2b3c-7d4e-8f90-123456789abc/manifest"
);
assert_eq!(received.body, "");
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `look` sends what it declares and answers what the server sent.
#[test]
fn look_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"arrived": [{"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "image": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "kind": "action_executed", "payload": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "intervention_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "raw": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "received_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "source": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}], "detector_paused": true, "event": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "image": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "kind": "action_executed", "payload": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "intervention_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "raw": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "received_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "source": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "frame": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "image_path": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "job": {"completion": {"out_of_range": true, "value": 1.5}, "error": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "estimated_print_time_s": 7, "filament": [{"length_mm": 1.5, "tool": 7, "volume_cm3": 1.5}], "file_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "file_origin": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "file_position_bytes": 7, "print_time_left_s": 7, "print_time_s": 7, "size_bytes": 7, "state": "operational"}, "printer": {"bed": {"actual_c": {"out_of_range": true, "value": 1.5}, "offset_c": {"out_of_range": true, "value": 1.5}, "target_c": {"out_of_range": true, "value": 1.5}}, "chamber": {"actual_c": {"out_of_range": true, "value": 1.5}, "offset_c": {"out_of_range": true, "value": 1.5}, "target_c": {"out_of_range": true, "value": 1.5}}, "connection": "operational", "fan_percent": {"out_of_range": true, "value": 1.5}, "feedrate_factor": {"out_of_range": true, "value": 1.5}, "flowrate_factor": {"out_of_range": true, "value": 1.5}, "observed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "tools": [{"actual_c": {"out_of_range": true, "value": 1.5}, "offset_c": {"out_of_range": true, "value": 1.5}, "target_c": {"out_of_range": true, "value": 1.5}}]}}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.look("0198f0a1-2b3c-7d4e-8f90-123456789abc", Some(7))
.expect("`look` is answered");
let received = host.received();
assert_eq!(received.method, "GET");
assert_eq!(
received.target,
"/v1/prints/0198f0a1-2b3c-7d4e-8f90-123456789abc/look?wait_s=7"
);
assert_eq!(received.body, "");
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `manifest_set` sends what it declares and answers what the server sent.
#[test]
fn manifest_set_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"manifest": {"allowed": {"feedrate": {"max": 1.5, "min": 1.5}}, "file_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "metadata": {"feedrate": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "nozzle_diameter_mm": 1.5, "slicer_profile": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "narrowings": [{"adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied": {"max": 1.5, "min": 1.5}, "requested": {"max": 1.5, "min": 1.5}}]}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client.manifest_set("0198f0a1-2b3c-7d4e-8f90-123456789abc", "a generated walk is asking", &parsed::<JobManifest>(r#"{"allowed": {"feedrate": {"max": 1.5, "min": 1.5}}, "file_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "metadata": {"feedrate": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "nozzle_diameter_mm": 1.5, "slicer_profile": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}"#))
.expect("`manifest_set` is answered");
let received = host.received();
assert_eq!(received.method, "PUT");
assert_eq!(
received.target,
"/v1/prints/0198f0a1-2b3c-7d4e-8f90-123456789abc/manifest"
);
let sent: Value = serde_json::from_str(r#"{"manifest": {"allowed": {"feedrate": {"max": 1.5, "min": 1.5}}, "file_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "metadata": {"feedrate": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "nozzle_diameter_mm": 1.5, "slicer_profile": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "reason": "a generated walk is asking"}"#)
.expect("a generated body is a document");
assert_eq!(
serde_json::from_str::<Value>(&received.body).expect("a body"),
sent,
"this call sent something other than what its operation declares"
);
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `manifest_set` makes no request at all when the reason is empty.
#[test]
fn manifest_set_makes_no_request_when_the_reason_is_empty() {
let host = Host::answering(200, "{}");
let client = Client::new(host.address(), actor());
let refused = client.manifest_set("0198f0a1-2b3c-7d4e-8f90-123456789abc", " ", &parsed::<JobManifest>(r#"{"allowed": {"feedrate": {"max": 1.5, "min": 1.5}}, "file_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "metadata": {"feedrate": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "nozzle_diameter_mm": 1.5, "slicer_profile": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}"#))
.expect_err("a call with no reason is refused");
assert!(matches!(refused, ClientError::NoReason));
assert_eq!(host.requests(), 0, "this call reached the server");
}
/// `spools` sends what it declares and answers what the server sent.
#[test]
fn spools_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"at_a_glance": ["0198f0a1-2b3c-7d4e-8f90-123456789abc"], "pending": [{"analysed_length_mm": 1.5, "charge_key": {"key": "print", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "segment": 7, "tool": 7}, "length_mm": 1.5, "method": "slicer_estimate", "note": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "pended_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "position": {"bytes": 7, "observed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "size_bytes": 7}, "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "tool": 7}], "spools": [{"color": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "density_g_cm3": 1.5, "diameter_mm": 1.5, "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "initial_g": 1.5, "loaded_on_tool": 7, "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "registered_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "remaining_g": 1.5, "retired_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "tare_g": 1.5, "trust": {"state": "verified"}, "updated_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}]}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client.spools(Some(true)).expect("`spools` is answered");
let received = host.received();
assert_eq!(received.method, "GET");
assert_eq!(received.target, "/v1/spools?include_retired=true");
assert_eq!(received.body, "");
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `spool` sends what it declares and answers what the server sent.
#[test]
fn spool_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"recent": [{"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "image": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "kind": "action_executed", "payload": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "intervention_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "raw": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "received_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "source": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}], "spool": {"color": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "density_g_cm3": 1.5, "diameter_mm": 1.5, "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "initial_g": 1.5, "loaded_on_tool": 7, "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "registered_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "remaining_g": 1.5, "retired_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "tare_g": 1.5, "trust": {"state": "verified"}, "updated_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.spool("0198f0a1-2b3c-7d4e-8f90-123456789abc")
.expect("`spool` is answered");
let received = host.received();
assert_eq!(received.method, "GET");
assert_eq!(
received.target,
"/v1/spools/0198f0a1-2b3c-7d4e-8f90-123456789abc"
);
assert_eq!(received.body, "");
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `printer_history` sends what it declares and answers what the server sent.
#[test]
fn printer_history_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"events": [{"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "image": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "kind": "action_executed", "payload": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "intervention_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "raw": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "received_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "source": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}], "next": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.printer_history(
Some(vec!["0198f0a1-2b3c-7d4e-8f90-123456789abc".to_owned()]),
Some("0198f0a1-2b3c-7d4e-8f90-123456789abc".to_owned()),
Some("0198f0a1-2b3c-7d4e-8f90-123456789abc".to_owned()),
Some("0198f0a1-2b3c-7d4e-8f90-123456789abc".to_owned()),
Some("0198f0a1-2b3c-7d4e-8f90-123456789abc".to_owned()),
Some(7),
)
.expect("`printer_history` is answered");
let received = host.received();
assert_eq!(received.method, "GET");
assert_eq!(
received.target,
"/v1/history?kinds=0198f0a1-2b3c-7d4e-8f90-123456789abc&since=0198f0a1-2b3c-7d4e-8f90-123456789abc&until=0198f0a1-2b3c-7d4e-8f90-123456789abc&spool_id=0198f0a1-2b3c-7d4e-8f90-123456789abc&before=0198f0a1-2b3c-7d4e-8f90-123456789abc&limit=7"
);
assert_eq!(received.body, "");
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `preflight` sends what it declares and answers what the server sent.
#[test]
fn preflight_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"preflight": {"checked_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "file_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "tools": [{"buffer_g": 1.5, "marks": [{"reason": {"print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "reason": "uncharged_print"}, "since": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}], "remaining_g": 1.5, "required_g": 1.5, "spool_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "spool_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "tool": 7, "verdict": "passed"}], "verdict": "passed"}}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.preflight("0198f0a1-2b3c-7d4e-8f90-123456789abc")
.expect("`preflight` is answered");
let received = host.received();
assert_eq!(received.method, "GET");
assert_eq!(
received.target,
"/v1/preflight?file_name=0198f0a1-2b3c-7d4e-8f90-123456789abc"
);
assert_eq!(received.body, "");
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `pause` sends what it declares and answers what the server sent.
#[test]
fn pause_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"intervention": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "printer_refusal": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"decision": "accepted", "executed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "succeeded", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "request": {"action": {"action": "pause", "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "requested_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.pause(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"a generated walk is asking",
)
.expect("`pause` is answered");
let received = host.received();
assert_eq!(received.method, "POST");
assert_eq!(
received.target,
"/v1/prints/0198f0a1-2b3c-7d4e-8f90-123456789abc/actions/pause"
);
let sent: Value = serde_json::from_str(r#"{"actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "a generated walk is asking"}"#)
.expect("a generated body is a document");
assert_eq!(
serde_json::from_str::<Value>(&received.body).expect("a body"),
sent,
"this call sent something other than what its operation declares"
);
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `pause` surfaces the policy's own refusal, typed.
#[test]
fn pause_surfaces_the_policys_own_refusal() {
let refusal: Value = serde_json::from_str(r#"{"intervention": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "printer_refusal": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"decision": {"rejected": {"out_of_bounds": {"adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "allowed": {"max": 1.25, "min": 0.5}, "requested": 1.5}}}, "executed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "succeeded", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "request": {"action": {"action": "pause", "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "requested_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}"#)
.expect("a generated refusal is a document");
let host = Host::answering(409, &refusal.to_string());
let client = Client::new(host.address(), actor());
let refused = client
.pause(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"a generated walk is asking",
)
.expect_err("`pause` is refused");
let ClientError::Rejected(rejection) = refused else {
panic!("the policy's refusal did not arrive as one");
};
assert_eq!(rejection.requested, Some(1.5));
assert_eq!(
rejection
.allowed
.as_ref()
.map(|range| (range.min, range.max)),
Some((0.5, 1.25))
);
assert!(matches!(
rejection.reason,
RejectionReason::OutOfBounds { .. }
));
assert_eq!(
serde_json::to_value(&rejection.answer).expect("an answer renders"),
refusal
);
}
/// `pause` makes no request at all when the reason is empty.
#[test]
fn pause_makes_no_request_when_the_reason_is_empty() {
let host = Host::answering(200, "{}");
let client = Client::new(host.address(), actor());
let refused = client
.pause("0198f0a1-2b3c-7d4e-8f90-123456789abc", " ")
.expect_err("a call with no reason is refused");
assert!(matches!(refused, ClientError::NoReason));
assert_eq!(host.requests(), 0, "this call reached the server");
}
/// `resume` sends what it declares and answers what the server sent.
#[test]
fn resume_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"intervention": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "printer_refusal": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"decision": "accepted", "executed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "succeeded", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "request": {"action": {"action": "pause", "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "requested_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.resume(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"a generated walk is asking",
)
.expect("`resume` is answered");
let received = host.received();
assert_eq!(received.method, "POST");
assert_eq!(
received.target,
"/v1/prints/0198f0a1-2b3c-7d4e-8f90-123456789abc/actions/resume"
);
let sent: Value = serde_json::from_str(r#"{"actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "a generated walk is asking"}"#)
.expect("a generated body is a document");
assert_eq!(
serde_json::from_str::<Value>(&received.body).expect("a body"),
sent,
"this call sent something other than what its operation declares"
);
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `resume` surfaces the policy's own refusal, typed.
#[test]
fn resume_surfaces_the_policys_own_refusal() {
let refusal: Value = serde_json::from_str(r#"{"intervention": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "printer_refusal": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"decision": {"rejected": {"out_of_bounds": {"adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "allowed": {"max": 1.25, "min": 0.5}, "requested": 1.5}}}, "executed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "succeeded", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "request": {"action": {"action": "pause", "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "requested_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}"#)
.expect("a generated refusal is a document");
let host = Host::answering(409, &refusal.to_string());
let client = Client::new(host.address(), actor());
let refused = client
.resume(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"a generated walk is asking",
)
.expect_err("`resume` is refused");
let ClientError::Rejected(rejection) = refused else {
panic!("the policy's refusal did not arrive as one");
};
assert_eq!(rejection.requested, Some(1.5));
assert_eq!(
rejection
.allowed
.as_ref()
.map(|range| (range.min, range.max)),
Some((0.5, 1.25))
);
assert!(matches!(
rejection.reason,
RejectionReason::OutOfBounds { .. }
));
assert_eq!(
serde_json::to_value(&rejection.answer).expect("an answer renders"),
refusal
);
}
/// `resume` makes no request at all when the reason is empty.
#[test]
fn resume_makes_no_request_when_the_reason_is_empty() {
let host = Host::answering(200, "{}");
let client = Client::new(host.address(), actor());
let refused = client
.resume("0198f0a1-2b3c-7d4e-8f90-123456789abc", " ")
.expect_err("a call with no reason is refused");
assert!(matches!(refused, ClientError::NoReason));
assert_eq!(host.requests(), 0, "this call reached the server");
}
/// `cancel` sends what it declares and answers what the server sent.
#[test]
fn cancel_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"intervention": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "printer_refusal": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"decision": "accepted", "executed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "succeeded", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "request": {"action": {"action": "pause", "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "requested_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.cancel(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"a generated walk is asking",
)
.expect("`cancel` is answered");
let received = host.received();
assert_eq!(received.method, "POST");
assert_eq!(
received.target,
"/v1/prints/0198f0a1-2b3c-7d4e-8f90-123456789abc/actions/cancel"
);
let sent: Value = serde_json::from_str(r#"{"actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "a generated walk is asking"}"#)
.expect("a generated body is a document");
assert_eq!(
serde_json::from_str::<Value>(&received.body).expect("a body"),
sent,
"this call sent something other than what its operation declares"
);
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `cancel` surfaces the policy's own refusal, typed.
#[test]
fn cancel_surfaces_the_policys_own_refusal() {
let refusal: Value = serde_json::from_str(r#"{"intervention": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "printer_refusal": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"decision": {"rejected": {"out_of_bounds": {"adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "allowed": {"max": 1.25, "min": 0.5}, "requested": 1.5}}}, "executed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "succeeded", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "request": {"action": {"action": "pause", "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "requested_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}"#)
.expect("a generated refusal is a document");
let host = Host::answering(409, &refusal.to_string());
let client = Client::new(host.address(), actor());
let refused = client
.cancel(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"a generated walk is asking",
)
.expect_err("`cancel` is refused");
let ClientError::Rejected(rejection) = refused else {
panic!("the policy's refusal did not arrive as one");
};
assert_eq!(rejection.requested, Some(1.5));
assert_eq!(
rejection
.allowed
.as_ref()
.map(|range| (range.min, range.max)),
Some((0.5, 1.25))
);
assert!(matches!(
rejection.reason,
RejectionReason::OutOfBounds { .. }
));
assert_eq!(
serde_json::to_value(&rejection.answer).expect("an answer renders"),
refusal
);
}
/// `cancel` makes no request at all when the reason is empty.
#[test]
fn cancel_makes_no_request_when_the_reason_is_empty() {
let host = Host::answering(200, "{}");
let client = Client::new(host.address(), actor());
let refused = client
.cancel("0198f0a1-2b3c-7d4e-8f90-123456789abc", " ")
.expect_err("a call with no reason is refused");
assert!(matches!(refused, ClientError::NoReason));
assert_eq!(host.requests(), 0, "this call reached the server");
}
/// `start_print` sends what it declares and answers what the server sent.
#[test]
fn start_print_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"intervention": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "printer_refusal": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"decision": "accepted", "executed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "succeeded", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "request": {"action": {"action": "pause", "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "requested_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client.start_print("0198f0a1-2b3c-7d4e-8f90-123456789abc", &parsed::<FileName>(r#""0198f0a1-2b3c-7d4e-8f90-123456789abc""#), &parsed::<JobManifest>(r#"{"allowed": {"feedrate": {"max": 1.5, "min": 1.5}}, "file_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "metadata": {"feedrate": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "nozzle_diameter_mm": 1.5, "slicer_profile": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}"#), "a generated walk is asking", Some(true))
.expect("`start_print` is answered");
let received = host.received();
assert_eq!(received.method, "POST");
assert_eq!(
received.target,
"/v1/prints/0198f0a1-2b3c-7d4e-8f90-123456789abc/actions/start_print"
);
let sent: Value = serde_json::from_str(r#"{"actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "file_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "manifest": {"allowed": {"feedrate": {"max": 1.5, "min": 1.5}}, "file_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "metadata": {"feedrate": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "nozzle_diameter_mm": 1.5, "slicer_profile": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "override_filament_check": true, "reason": "a generated walk is asking"}"#)
.expect("a generated body is a document");
assert_eq!(
serde_json::from_str::<Value>(&received.body).expect("a body"),
sent,
"this call sent something other than what its operation declares"
);
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `start_print` surfaces the policy's own refusal, typed.
#[test]
fn start_print_surfaces_the_policys_own_refusal() {
let refusal: Value = serde_json::from_str(r#"{"intervention": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "printer_refusal": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"decision": {"rejected": {"out_of_bounds": {"adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "allowed": {"max": 1.25, "min": 0.5}, "requested": 1.5}}}, "executed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "succeeded", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "request": {"action": {"action": "pause", "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "requested_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}"#)
.expect("a generated refusal is a document");
let host = Host::answering(409, &refusal.to_string());
let client = Client::new(host.address(), actor());
let refused = client.start_print("0198f0a1-2b3c-7d4e-8f90-123456789abc", &parsed::<FileName>(r#""0198f0a1-2b3c-7d4e-8f90-123456789abc""#), &parsed::<JobManifest>(r#"{"allowed": {"feedrate": {"max": 1.5, "min": 1.5}}, "file_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "metadata": {"feedrate": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "nozzle_diameter_mm": 1.5, "slicer_profile": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}"#), "a generated walk is asking", Some(true))
.expect_err("`start_print` is refused");
let ClientError::Rejected(rejection) = refused else {
panic!("the policy's refusal did not arrive as one");
};
assert_eq!(rejection.requested, Some(1.5));
assert_eq!(
rejection
.allowed
.as_ref()
.map(|range| (range.min, range.max)),
Some((0.5, 1.25))
);
assert!(matches!(
rejection.reason,
RejectionReason::OutOfBounds { .. }
));
assert_eq!(
serde_json::to_value(&rejection.answer).expect("an answer renders"),
refusal
);
}
/// `start_print` makes no request at all when the reason is empty.
#[test]
fn start_print_makes_no_request_when_the_reason_is_empty() {
let host = Host::answering(200, "{}");
let client = Client::new(host.address(), actor());
let refused = client.start_print("0198f0a1-2b3c-7d4e-8f90-123456789abc", &parsed::<FileName>(r#""0198f0a1-2b3c-7d4e-8f90-123456789abc""#), &parsed::<JobManifest>(r#"{"allowed": {"feedrate": {"max": 1.5, "min": 1.5}}, "file_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "metadata": {"feedrate": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "nozzle_diameter_mm": 1.5, "slicer_profile": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}"#), " ", Some(true))
.expect_err("a call with no reason is refused");
assert!(matches!(refused, ClientError::NoReason));
assert_eq!(host.requests(), 0, "this call reached the server");
}
/// `set_feedrate_factor` sends what it declares and answers what the server sent.
#[test]
fn set_feedrate_factor_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"intervention": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "printer_refusal": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"decision": "accepted", "executed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "succeeded", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "request": {"action": {"action": "pause", "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "requested_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.set_feedrate_factor(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
1.5,
"a generated walk is asking",
Some(7),
)
.expect("`set_feedrate_factor` is answered");
let received = host.received();
assert_eq!(received.method, "POST");
assert_eq!(
received.target,
"/v1/prints/0198f0a1-2b3c-7d4e-8f90-123456789abc/actions/set_feedrate_factor"
);
let sent: Value = serde_json::from_str(r#"{"actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "duration_s": 7, "factor": 1.5, "reason": "a generated walk is asking"}"#)
.expect("a generated body is a document");
assert_eq!(
serde_json::from_str::<Value>(&received.body).expect("a body"),
sent,
"this call sent something other than what its operation declares"
);
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `set_feedrate_factor` surfaces the policy's own refusal, typed.
#[test]
fn set_feedrate_factor_surfaces_the_policys_own_refusal() {
let refusal: Value = serde_json::from_str(r#"{"intervention": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "printer_refusal": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"decision": {"rejected": {"out_of_bounds": {"adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "allowed": {"max": 1.25, "min": 0.5}, "requested": 1.5}}}, "executed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "succeeded", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "request": {"action": {"action": "pause", "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "requested_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}"#)
.expect("a generated refusal is a document");
let host = Host::answering(409, &refusal.to_string());
let client = Client::new(host.address(), actor());
let refused = client
.set_feedrate_factor(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
1.5,
"a generated walk is asking",
Some(7),
)
.expect_err("`set_feedrate_factor` is refused");
let ClientError::Rejected(rejection) = refused else {
panic!("the policy's refusal did not arrive as one");
};
assert_eq!(rejection.requested, Some(1.5));
assert_eq!(
rejection
.allowed
.as_ref()
.map(|range| (range.min, range.max)),
Some((0.5, 1.25))
);
assert!(matches!(
rejection.reason,
RejectionReason::OutOfBounds { .. }
));
assert_eq!(
serde_json::to_value(&rejection.answer).expect("an answer renders"),
refusal
);
}
/// `set_feedrate_factor` makes no request at all when the reason is empty.
#[test]
fn set_feedrate_factor_makes_no_request_when_the_reason_is_empty() {
let host = Host::answering(200, "{}");
let client = Client::new(host.address(), actor());
let refused = client
.set_feedrate_factor("0198f0a1-2b3c-7d4e-8f90-123456789abc", 1.5, " ", Some(7))
.expect_err("a call with no reason is refused");
assert!(matches!(refused, ClientError::NoReason));
assert_eq!(host.requests(), 0, "this call reached the server");
}
/// `set_flowrate_factor` sends what it declares and answers what the server sent.
#[test]
fn set_flowrate_factor_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"intervention": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "printer_refusal": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"decision": "accepted", "executed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "succeeded", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "request": {"action": {"action": "pause", "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "requested_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.set_flowrate_factor(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
1.5,
"a generated walk is asking",
Some(7),
)
.expect("`set_flowrate_factor` is answered");
let received = host.received();
assert_eq!(received.method, "POST");
assert_eq!(
received.target,
"/v1/prints/0198f0a1-2b3c-7d4e-8f90-123456789abc/actions/set_flowrate_factor"
);
let sent: Value = serde_json::from_str(r#"{"actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "duration_s": 7, "factor": 1.5, "reason": "a generated walk is asking"}"#)
.expect("a generated body is a document");
assert_eq!(
serde_json::from_str::<Value>(&received.body).expect("a body"),
sent,
"this call sent something other than what its operation declares"
);
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `set_flowrate_factor` surfaces the policy's own refusal, typed.
#[test]
fn set_flowrate_factor_surfaces_the_policys_own_refusal() {
let refusal: Value = serde_json::from_str(r#"{"intervention": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "printer_refusal": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"decision": {"rejected": {"out_of_bounds": {"adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "allowed": {"max": 1.25, "min": 0.5}, "requested": 1.5}}}, "executed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "succeeded", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "request": {"action": {"action": "pause", "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "requested_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}"#)
.expect("a generated refusal is a document");
let host = Host::answering(409, &refusal.to_string());
let client = Client::new(host.address(), actor());
let refused = client
.set_flowrate_factor(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
1.5,
"a generated walk is asking",
Some(7),
)
.expect_err("`set_flowrate_factor` is refused");
let ClientError::Rejected(rejection) = refused else {
panic!("the policy's refusal did not arrive as one");
};
assert_eq!(rejection.requested, Some(1.5));
assert_eq!(
rejection
.allowed
.as_ref()
.map(|range| (range.min, range.max)),
Some((0.5, 1.25))
);
assert!(matches!(
rejection.reason,
RejectionReason::OutOfBounds { .. }
));
assert_eq!(
serde_json::to_value(&rejection.answer).expect("an answer renders"),
refusal
);
}
/// `set_flowrate_factor` makes no request at all when the reason is empty.
#[test]
fn set_flowrate_factor_makes_no_request_when_the_reason_is_empty() {
let host = Host::answering(200, "{}");
let client = Client::new(host.address(), actor());
let refused = client
.set_flowrate_factor("0198f0a1-2b3c-7d4e-8f90-123456789abc", 1.5, " ", Some(7))
.expect_err("a call with no reason is refused");
assert!(matches!(refused, ClientError::NoReason));
assert_eq!(host.requests(), 0, "this call reached the server");
}
/// `set_tool_target_c` sends what it declares and answers what the server sent.
#[test]
fn set_tool_target_c_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"intervention": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "printer_refusal": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"decision": "accepted", "executed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "succeeded", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "request": {"action": {"action": "pause", "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "requested_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.set_tool_target_c(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"a generated walk is asking",
1.5,
7,
Some(7),
)
.expect("`set_tool_target_c` is answered");
let received = host.received();
assert_eq!(received.method, "POST");
assert_eq!(
received.target,
"/v1/prints/0198f0a1-2b3c-7d4e-8f90-123456789abc/actions/set_tool_target_c"
);
let sent: Value = serde_json::from_str(r#"{"actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "duration_s": 7, "reason": "a generated walk is asking", "target_c": 1.5, "tool": 7}"#)
.expect("a generated body is a document");
assert_eq!(
serde_json::from_str::<Value>(&received.body).expect("a body"),
sent,
"this call sent something other than what its operation declares"
);
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `set_tool_target_c` surfaces the policy's own refusal, typed.
#[test]
fn set_tool_target_c_surfaces_the_policys_own_refusal() {
let refusal: Value = serde_json::from_str(r#"{"intervention": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "printer_refusal": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"decision": {"rejected": {"out_of_bounds": {"adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "allowed": {"max": 1.25, "min": 0.5}, "requested": 1.5}}}, "executed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "succeeded", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "request": {"action": {"action": "pause", "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "requested_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}"#)
.expect("a generated refusal is a document");
let host = Host::answering(409, &refusal.to_string());
let client = Client::new(host.address(), actor());
let refused = client
.set_tool_target_c(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"a generated walk is asking",
1.5,
7,
Some(7),
)
.expect_err("`set_tool_target_c` is refused");
let ClientError::Rejected(rejection) = refused else {
panic!("the policy's refusal did not arrive as one");
};
assert_eq!(rejection.requested, Some(1.5));
assert_eq!(
rejection
.allowed
.as_ref()
.map(|range| (range.min, range.max)),
Some((0.5, 1.25))
);
assert!(matches!(
rejection.reason,
RejectionReason::OutOfBounds { .. }
));
assert_eq!(
serde_json::to_value(&rejection.answer).expect("an answer renders"),
refusal
);
}
/// `set_tool_target_c` makes no request at all when the reason is empty.
#[test]
fn set_tool_target_c_makes_no_request_when_the_reason_is_empty() {
let host = Host::answering(200, "{}");
let client = Client::new(host.address(), actor());
let refused = client
.set_tool_target_c(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
" ",
1.5,
7,
Some(7),
)
.expect_err("a call with no reason is refused");
assert!(matches!(refused, ClientError::NoReason));
assert_eq!(host.requests(), 0, "this call reached the server");
}
/// `set_bed_target_c` sends what it declares and answers what the server sent.
#[test]
fn set_bed_target_c_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"intervention": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "printer_refusal": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"decision": "accepted", "executed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "succeeded", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "request": {"action": {"action": "pause", "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "requested_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.set_bed_target_c(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"a generated walk is asking",
1.5,
Some(7),
)
.expect("`set_bed_target_c` is answered");
let received = host.received();
assert_eq!(received.method, "POST");
assert_eq!(
received.target,
"/v1/prints/0198f0a1-2b3c-7d4e-8f90-123456789abc/actions/set_bed_target_c"
);
let sent: Value = serde_json::from_str(r#"{"actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "duration_s": 7, "reason": "a generated walk is asking", "target_c": 1.5}"#)
.expect("a generated body is a document");
assert_eq!(
serde_json::from_str::<Value>(&received.body).expect("a body"),
sent,
"this call sent something other than what its operation declares"
);
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `set_bed_target_c` surfaces the policy's own refusal, typed.
#[test]
fn set_bed_target_c_surfaces_the_policys_own_refusal() {
let refusal: Value = serde_json::from_str(r#"{"intervention": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "printer_refusal": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"decision": {"rejected": {"out_of_bounds": {"adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "allowed": {"max": 1.25, "min": 0.5}, "requested": 1.5}}}, "executed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "succeeded", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "request": {"action": {"action": "pause", "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "requested_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}"#)
.expect("a generated refusal is a document");
let host = Host::answering(409, &refusal.to_string());
let client = Client::new(host.address(), actor());
let refused = client
.set_bed_target_c(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"a generated walk is asking",
1.5,
Some(7),
)
.expect_err("`set_bed_target_c` is refused");
let ClientError::Rejected(rejection) = refused else {
panic!("the policy's refusal did not arrive as one");
};
assert_eq!(rejection.requested, Some(1.5));
assert_eq!(
rejection
.allowed
.as_ref()
.map(|range| (range.min, range.max)),
Some((0.5, 1.25))
);
assert!(matches!(
rejection.reason,
RejectionReason::OutOfBounds { .. }
));
assert_eq!(
serde_json::to_value(&rejection.answer).expect("an answer renders"),
refusal
);
}
/// `set_bed_target_c` makes no request at all when the reason is empty.
#[test]
fn set_bed_target_c_makes_no_request_when_the_reason_is_empty() {
let host = Host::answering(200, "{}");
let client = Client::new(host.address(), actor());
let refused = client
.set_bed_target_c("0198f0a1-2b3c-7d4e-8f90-123456789abc", " ", 1.5, Some(7))
.expect_err("a call with no reason is refused");
assert!(matches!(refused, ClientError::NoReason));
assert_eq!(host.requests(), 0, "this call reached the server");
}
/// `set_fan_percent` sends what it declares and answers what the server sent.
#[test]
fn set_fan_percent_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"intervention": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "printer_refusal": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"decision": "accepted", "executed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "succeeded", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "request": {"action": {"action": "pause", "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "requested_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.set_fan_percent(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
1.5,
"a generated walk is asking",
Some(7),
)
.expect("`set_fan_percent` is answered");
let received = host.received();
assert_eq!(received.method, "POST");
assert_eq!(
received.target,
"/v1/prints/0198f0a1-2b3c-7d4e-8f90-123456789abc/actions/set_fan_percent"
);
let sent: Value = serde_json::from_str(r#"{"actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "duration_s": 7, "percent": 1.5, "reason": "a generated walk is asking"}"#)
.expect("a generated body is a document");
assert_eq!(
serde_json::from_str::<Value>(&received.body).expect("a body"),
sent,
"this call sent something other than what its operation declares"
);
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `set_fan_percent` surfaces the policy's own refusal, typed.
#[test]
fn set_fan_percent_surfaces_the_policys_own_refusal() {
let refusal: Value = serde_json::from_str(r#"{"intervention": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "printer_refusal": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"decision": {"rejected": {"out_of_bounds": {"adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "allowed": {"max": 1.25, "min": 0.5}, "requested": 1.5}}}, "executed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "succeeded", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "request": {"action": {"action": "pause", "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "requested_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}"#)
.expect("a generated refusal is a document");
let host = Host::answering(409, &refusal.to_string());
let client = Client::new(host.address(), actor());
let refused = client
.set_fan_percent(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
1.5,
"a generated walk is asking",
Some(7),
)
.expect_err("`set_fan_percent` is refused");
let ClientError::Rejected(rejection) = refused else {
panic!("the policy's refusal did not arrive as one");
};
assert_eq!(rejection.requested, Some(1.5));
assert_eq!(
rejection
.allowed
.as_ref()
.map(|range| (range.min, range.max)),
Some((0.5, 1.25))
);
assert!(matches!(
rejection.reason,
RejectionReason::OutOfBounds { .. }
));
assert_eq!(
serde_json::to_value(&rejection.answer).expect("an answer renders"),
refusal
);
}
/// `set_fan_percent` makes no request at all when the reason is empty.
#[test]
fn set_fan_percent_makes_no_request_when_the_reason_is_empty() {
let host = Host::answering(200, "{}");
let client = Client::new(host.address(), actor());
let refused = client
.set_fan_percent("0198f0a1-2b3c-7d4e-8f90-123456789abc", 1.5, " ", Some(7))
.expect_err("a call with no reason is refused");
assert!(matches!(refused, ClientError::NoReason));
assert_eq!(host.requests(), 0, "this call reached the server");
}
/// `acknowledge_failure` sends what it declares and answers what the server sent.
#[test]
fn acknowledge_failure_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"intervention": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "printer_refusal": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"decision": "accepted", "executed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "succeeded", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "request": {"action": {"action": "pause", "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "requested_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.acknowledge_failure(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
&parsed::<AcknowledgementDisposition>(r#""continue""#),
&parsed::<EventId>(r#""0198f0a1-2b3c-7d4e-8f90-123456789abc""#),
"a generated walk is asking",
)
.expect("`acknowledge_failure` is answered");
let received = host.received();
assert_eq!(received.method, "POST");
assert_eq!(
received.target,
"/v1/prints/0198f0a1-2b3c-7d4e-8f90-123456789abc/actions/acknowledge_failure"
);
let sent: Value = serde_json::from_str(r#"{"actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "disposition": "continue", "event_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "reason": "a generated walk is asking"}"#)
.expect("a generated body is a document");
assert_eq!(
serde_json::from_str::<Value>(&received.body).expect("a body"),
sent,
"this call sent something other than what its operation declares"
);
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `acknowledge_failure` surfaces the policy's own refusal, typed.
#[test]
fn acknowledge_failure_surfaces_the_policys_own_refusal() {
let refusal: Value = serde_json::from_str(r#"{"intervention": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "applied_value": 1.5, "expires_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "still_active", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "prior_value": 1.5, "restored_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "printer_refusal": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "record": {"decision": {"rejected": {"out_of_bounds": {"adjustable": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "allowed": {"max": 1.25, "min": 0.5}, "requested": 1.5}}}, "executed_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "outcome": "succeeded", "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "request": {"action": {"action": "pause", "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "requested_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}"#)
.expect("a generated refusal is a document");
let host = Host::answering(409, &refusal.to_string());
let client = Client::new(host.address(), actor());
let refused = client
.acknowledge_failure(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
&parsed::<AcknowledgementDisposition>(r#""continue""#),
&parsed::<EventId>(r#""0198f0a1-2b3c-7d4e-8f90-123456789abc""#),
"a generated walk is asking",
)
.expect_err("`acknowledge_failure` is refused");
let ClientError::Rejected(rejection) = refused else {
panic!("the policy's refusal did not arrive as one");
};
assert_eq!(rejection.requested, Some(1.5));
assert_eq!(
rejection
.allowed
.as_ref()
.map(|range| (range.min, range.max)),
Some((0.5, 1.25))
);
assert!(matches!(
rejection.reason,
RejectionReason::OutOfBounds { .. }
));
assert_eq!(
serde_json::to_value(&rejection.answer).expect("an answer renders"),
refusal
);
}
/// `acknowledge_failure` makes no request at all when the reason is empty.
#[test]
fn acknowledge_failure_makes_no_request_when_the_reason_is_empty() {
let host = Host::answering(200, "{}");
let client = Client::new(host.address(), actor());
let refused = client
.acknowledge_failure(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
&parsed::<AcknowledgementDisposition>(r#""continue""#),
&parsed::<EventId>(r#""0198f0a1-2b3c-7d4e-8f90-123456789abc""#),
" ",
)
.expect_err("a call with no reason is refused");
assert!(matches!(refused, ClientError::NoReason));
assert_eq!(host.requests(), 0, "this call reached the server");
}
/// `register_spool` sends what it declares and answers what the server sent.
#[test]
fn register_spool_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"decision": "accepted", "events": [{"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "image": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "kind": "action_executed", "payload": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "intervention_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "raw": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "received_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "source": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}], "spool": {"color": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "density_g_cm3": 1.5, "diameter_mm": 1.5, "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "initial_g": 1.5, "loaded_on_tool": 7, "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "registered_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "remaining_g": 1.5, "retired_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "tare_g": 1.5, "trust": {"state": "verified"}, "updated_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.register_spool(
1.5,
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"a generated walk is asking",
Some("0198f0a1-2b3c-7d4e-8f90-123456789abc".to_owned()),
Some(1.5),
Some(1.5),
Some(1.5),
Some(1.5),
)
.expect("`register_spool` is answered");
let received = host.received();
assert_eq!(received.method, "POST");
assert_eq!(received.target, "/v1/spools/actions/register_spool");
let sent: Value = serde_json::from_str(r#"{"actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "color": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "density_g_cm3": 1.5, "diameter_mm": 1.5, "initial_g": 1.5, "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "reason": "a generated walk is asking", "remaining_g": 1.5, "tare_g": 1.5}"#)
.expect("a generated body is a document");
assert_eq!(
serde_json::from_str::<Value>(&received.body).expect("a body"),
sent,
"this call sent something other than what its operation declares"
);
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `register_spool` surfaces the policy's own refusal, typed.
#[test]
fn register_spool_surfaces_the_policys_own_refusal() {
let refusal: Value = serde_json::from_str(r#"{"decision": {"rejected": {"spool_refused": {"detail": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}, "events": [{"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "image": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "kind": "action_executed", "payload": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "intervention_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "raw": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "received_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "source": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}], "spool": {"color": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "density_g_cm3": 1.5, "diameter_mm": 1.5, "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "initial_g": 1.5, "loaded_on_tool": 7, "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "registered_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "remaining_g": 1.5, "retired_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "tare_g": 1.5, "trust": {"state": "verified"}, "updated_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}"#)
.expect("a generated refusal is a document");
let host = Host::answering(409, &refusal.to_string());
let client = Client::new(host.address(), actor());
let refused = client
.register_spool(
1.5,
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"a generated walk is asking",
Some("0198f0a1-2b3c-7d4e-8f90-123456789abc".to_owned()),
Some(1.5),
Some(1.5),
Some(1.5),
Some(1.5),
)
.expect_err("`register_spool` is refused");
let ClientError::Rejected(rejection) = refused else {
panic!("the policy's refusal did not arrive as one");
};
assert_eq!(rejection.requested, None);
assert_eq!(rejection.allowed, None);
assert!(matches!(
rejection.reason,
RejectionReason::SpoolRefused { .. }
));
assert_eq!(
serde_json::to_value(&rejection.answer).expect("an answer renders"),
refusal
);
}
/// `register_spool` makes no request at all when the reason is empty.
#[test]
fn register_spool_makes_no_request_when_the_reason_is_empty() {
let host = Host::answering(200, "{}");
let client = Client::new(host.address(), actor());
let refused = client
.register_spool(
1.5,
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
" ",
Some("0198f0a1-2b3c-7d4e-8f90-123456789abc".to_owned()),
Some(1.5),
Some(1.5),
Some(1.5),
Some(1.5),
)
.expect_err("a call with no reason is refused");
assert!(matches!(refused, ClientError::NoReason));
assert_eq!(host.requests(), 0, "this call reached the server");
}
/// `load_spool` sends what it declares and answers what the server sent.
#[test]
fn load_spool_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"decision": "accepted", "events": [{"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "image": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "kind": "action_executed", "payload": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "intervention_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "raw": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "received_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "source": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}], "spool": {"color": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "density_g_cm3": 1.5, "diameter_mm": 1.5, "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "initial_g": 1.5, "loaded_on_tool": 7, "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "registered_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "remaining_g": 1.5, "retired_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "tare_g": 1.5, "trust": {"state": "verified"}, "updated_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.load_spool(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"a generated walk is asking",
7,
)
.expect("`load_spool` is answered");
let received = host.received();
assert_eq!(received.method, "POST");
assert_eq!(
received.target,
"/v1/spools/0198f0a1-2b3c-7d4e-8f90-123456789abc/actions/load_spool"
);
let sent: Value = serde_json::from_str(r#"{"actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "a generated walk is asking", "tool": 7}"#)
.expect("a generated body is a document");
assert_eq!(
serde_json::from_str::<Value>(&received.body).expect("a body"),
sent,
"this call sent something other than what its operation declares"
);
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `load_spool` surfaces the policy's own refusal, typed.
#[test]
fn load_spool_surfaces_the_policys_own_refusal() {
let refusal: Value = serde_json::from_str(r#"{"decision": {"rejected": {"spool_refused": {"detail": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}, "events": [{"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "image": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "kind": "action_executed", "payload": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "intervention_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "raw": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "received_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "source": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}], "spool": {"color": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "density_g_cm3": 1.5, "diameter_mm": 1.5, "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "initial_g": 1.5, "loaded_on_tool": 7, "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "registered_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "remaining_g": 1.5, "retired_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "tare_g": 1.5, "trust": {"state": "verified"}, "updated_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}"#)
.expect("a generated refusal is a document");
let host = Host::answering(409, &refusal.to_string());
let client = Client::new(host.address(), actor());
let refused = client
.load_spool(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"a generated walk is asking",
7,
)
.expect_err("`load_spool` is refused");
let ClientError::Rejected(rejection) = refused else {
panic!("the policy's refusal did not arrive as one");
};
assert_eq!(rejection.requested, None);
assert_eq!(rejection.allowed, None);
assert!(matches!(
rejection.reason,
RejectionReason::SpoolRefused { .. }
));
assert_eq!(
serde_json::to_value(&rejection.answer).expect("an answer renders"),
refusal
);
}
/// `load_spool` makes no request at all when the reason is empty.
#[test]
fn load_spool_makes_no_request_when_the_reason_is_empty() {
let host = Host::answering(200, "{}");
let client = Client::new(host.address(), actor());
let refused = client
.load_spool("0198f0a1-2b3c-7d4e-8f90-123456789abc", " ", 7)
.expect_err("a call with no reason is refused");
assert!(matches!(refused, ClientError::NoReason));
assert_eq!(host.requests(), 0, "this call reached the server");
}
/// `unload_spool` sends what it declares and answers what the server sent.
#[test]
fn unload_spool_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"decision": "accepted", "events": [{"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "image": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "kind": "action_executed", "payload": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "intervention_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "raw": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "received_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "source": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}], "spool": {"color": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "density_g_cm3": 1.5, "diameter_mm": 1.5, "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "initial_g": 1.5, "loaded_on_tool": 7, "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "registered_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "remaining_g": 1.5, "retired_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "tare_g": 1.5, "trust": {"state": "verified"}, "updated_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.unload_spool(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"a generated walk is asking",
)
.expect("`unload_spool` is answered");
let received = host.received();
assert_eq!(received.method, "POST");
assert_eq!(
received.target,
"/v1/spools/0198f0a1-2b3c-7d4e-8f90-123456789abc/actions/unload_spool"
);
let sent: Value = serde_json::from_str(r#"{"actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "a generated walk is asking"}"#)
.expect("a generated body is a document");
assert_eq!(
serde_json::from_str::<Value>(&received.body).expect("a body"),
sent,
"this call sent something other than what its operation declares"
);
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `unload_spool` surfaces the policy's own refusal, typed.
#[test]
fn unload_spool_surfaces_the_policys_own_refusal() {
let refusal: Value = serde_json::from_str(r#"{"decision": {"rejected": {"spool_refused": {"detail": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}, "events": [{"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "image": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "kind": "action_executed", "payload": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "intervention_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "raw": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "received_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "source": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}], "spool": {"color": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "density_g_cm3": 1.5, "diameter_mm": 1.5, "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "initial_g": 1.5, "loaded_on_tool": 7, "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "registered_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "remaining_g": 1.5, "retired_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "tare_g": 1.5, "trust": {"state": "verified"}, "updated_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}"#)
.expect("a generated refusal is a document");
let host = Host::answering(409, &refusal.to_string());
let client = Client::new(host.address(), actor());
let refused = client
.unload_spool(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"a generated walk is asking",
)
.expect_err("`unload_spool` is refused");
let ClientError::Rejected(rejection) = refused else {
panic!("the policy's refusal did not arrive as one");
};
assert_eq!(rejection.requested, None);
assert_eq!(rejection.allowed, None);
assert!(matches!(
rejection.reason,
RejectionReason::SpoolRefused { .. }
));
assert_eq!(
serde_json::to_value(&rejection.answer).expect("an answer renders"),
refusal
);
}
/// `unload_spool` makes no request at all when the reason is empty.
#[test]
fn unload_spool_makes_no_request_when_the_reason_is_empty() {
let host = Host::answering(200, "{}");
let client = Client::new(host.address(), actor());
let refused = client
.unload_spool("0198f0a1-2b3c-7d4e-8f90-123456789abc", " ")
.expect_err("a call with no reason is refused");
assert!(matches!(refused, ClientError::NoReason));
assert_eq!(host.requests(), 0, "this call reached the server");
}
/// `adjust_spool` sends what it declares and answers what the server sent.
#[test]
fn adjust_spool_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"decision": "accepted", "events": [{"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "image": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "kind": "action_executed", "payload": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "intervention_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "raw": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "received_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "source": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}], "spool": {"color": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "density_g_cm3": 1.5, "diameter_mm": 1.5, "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "initial_g": 1.5, "loaded_on_tool": 7, "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "registered_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "remaining_g": 1.5, "retired_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "tare_g": 1.5, "trust": {"state": "verified"}, "updated_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.adjust_spool(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"a generated walk is asking",
Some(1.5),
Some(1.5),
)
.expect("`adjust_spool` is answered");
let received = host.received();
assert_eq!(received.method, "POST");
assert_eq!(
received.target,
"/v1/spools/0198f0a1-2b3c-7d4e-8f90-123456789abc/actions/adjust_spool"
);
let sent: Value = serde_json::from_str(r#"{"actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "gross_g": 1.5, "reason": "a generated walk is asking", "remaining_g": 1.5}"#)
.expect("a generated body is a document");
assert_eq!(
serde_json::from_str::<Value>(&received.body).expect("a body"),
sent,
"this call sent something other than what its operation declares"
);
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `adjust_spool` surfaces the policy's own refusal, typed.
#[test]
fn adjust_spool_surfaces_the_policys_own_refusal() {
let refusal: Value = serde_json::from_str(r#"{"decision": {"rejected": {"spool_refused": {"detail": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}, "events": [{"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "image": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "kind": "action_executed", "payload": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "intervention_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "raw": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "received_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "source": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}], "spool": {"color": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "density_g_cm3": 1.5, "diameter_mm": 1.5, "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "initial_g": 1.5, "loaded_on_tool": 7, "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "registered_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "remaining_g": 1.5, "retired_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "tare_g": 1.5, "trust": {"state": "verified"}, "updated_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}"#)
.expect("a generated refusal is a document");
let host = Host::answering(409, &refusal.to_string());
let client = Client::new(host.address(), actor());
let refused = client
.adjust_spool(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"a generated walk is asking",
Some(1.5),
Some(1.5),
)
.expect_err("`adjust_spool` is refused");
let ClientError::Rejected(rejection) = refused else {
panic!("the policy's refusal did not arrive as one");
};
assert_eq!(rejection.requested, None);
assert_eq!(rejection.allowed, None);
assert!(matches!(
rejection.reason,
RejectionReason::SpoolRefused { .. }
));
assert_eq!(
serde_json::to_value(&rejection.answer).expect("an answer renders"),
refusal
);
}
/// `adjust_spool` makes no request at all when the reason is empty.
#[test]
fn adjust_spool_makes_no_request_when_the_reason_is_empty() {
let host = Host::answering(200, "{}");
let client = Client::new(host.address(), actor());
let refused = client
.adjust_spool(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
" ",
Some(1.5),
Some(1.5),
)
.expect_err("a call with no reason is refused");
assert!(matches!(refused, ClientError::NoReason));
assert_eq!(host.requests(), 0, "this call reached the server");
}
/// `retire_spool` sends what it declares and answers what the server sent.
#[test]
fn retire_spool_sends_what_it_declares_and_answers_what_the_server_sent() {
let answer: Value = serde_json::from_str(r#"{"decision": "accepted", "events": [{"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "image": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "kind": "action_executed", "payload": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "intervention_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "raw": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "received_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "source": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}], "spool": {"color": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "density_g_cm3": 1.5, "diameter_mm": 1.5, "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "initial_g": 1.5, "loaded_on_tool": 7, "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "registered_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "remaining_g": 1.5, "retired_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "tare_g": 1.5, "trust": {"state": "verified"}, "updated_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.retire_spool(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"a generated walk is asking",
)
.expect("`retire_spool` is answered");
let received = host.received();
assert_eq!(received.method, "POST");
assert_eq!(
received.target,
"/v1/spools/0198f0a1-2b3c-7d4e-8f90-123456789abc/actions/retire_spool"
);
let sent: Value = serde_json::from_str(r#"{"actor": {"agent": {"session_name": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}, "reason": "a generated walk is asking"}"#)
.expect("a generated body is a document");
assert_eq!(
serde_json::from_str::<Value>(&received.body).expect("a body"),
sent,
"this call sent something other than what its operation declares"
);
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"this call answered something other than what the server sent"
);
}
/// `retire_spool` surfaces the policy's own refusal, typed.
#[test]
fn retire_spool_surfaces_the_policys_own_refusal() {
let refusal: Value = serde_json::from_str(r#"{"decision": {"rejected": {"spool_refused": {"detail": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}}, "events": [{"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "image": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "kind": "action_executed", "payload": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "intervention_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "raw": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "received_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "source": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}], "spool": {"color": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "density_g_cm3": 1.5, "diameter_mm": 1.5, "id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "initial_g": 1.5, "loaded_on_tool": 7, "material": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "name": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "registered_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "remaining_g": 1.5, "retired_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "tare_g": 1.5, "trust": {"state": "verified"}, "updated_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}}"#)
.expect("a generated refusal is a document");
let host = Host::answering(409, &refusal.to_string());
let client = Client::new(host.address(), actor());
let refused = client
.retire_spool(
"0198f0a1-2b3c-7d4e-8f90-123456789abc",
"a generated walk is asking",
)
.expect_err("`retire_spool` is refused");
let ClientError::Rejected(rejection) = refused else {
panic!("the policy's refusal did not arrive as one");
};
assert_eq!(rejection.requested, None);
assert_eq!(rejection.allowed, None);
assert!(matches!(
rejection.reason,
RejectionReason::SpoolRefused { .. }
));
assert_eq!(
serde_json::to_value(&rejection.answer).expect("an answer renders"),
refusal
);
}
/// `retire_spool` makes no request at all when the reason is empty.
#[test]
fn retire_spool_makes_no_request_when_the_reason_is_empty() {
let host = Host::answering(200, "{}");
let client = Client::new(host.address(), actor());
let refused = client
.retire_spool("0198f0a1-2b3c-7d4e-8f90-123456789abc", " ")
.expect_err("a call with no reason is refused");
assert!(matches!(refused, ClientError::NoReason));
assert_eq!(host.requests(), 0, "this call reached the server");
}
/// `history` carries every event through, and the kind table reads a
/// known kind's payload as its type and answers nothing for a kind no client knows.
#[test]
fn history_carries_a_known_and_an_unknown_kind_through_the_kind_table() {
let answer: Value = serde_json::from_str(r#"{"events": [{"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "image": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "kind": "action_executed", "payload": {"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "intervention_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "raw": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "received_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "source": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "image": {"id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "sha256": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "kind": "kind_from_a_newer_server", "payload": {"anything": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}, "print_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "raw": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "received_at": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "source": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}]}"#)
.expect("a generated answer is a document");
let host = Host::answering(200, &answer.to_string());
let client = Client::new(host.address(), actor());
let answered = client
.history("0198f0a1-2b3c-7d4e-8f90-123456789abc", Some(7))
.expect("`history` is answered");
assert_eq!(
serde_json::to_value(&answered).expect("an answer is a document"),
answer,
"an event under a kind this client knows, or one it does not, \
was changed on the way through"
);
let [known, unknown] = answered.events.as_slice() else {
panic!("the answer carried something other than the two events served");
};
assert_eq!(known.kind, "action_executed");
let read: ActionExecutedPayload = known
.payload_as::<ActionExecutedPayload>()
.expect("a known kind reads through the table")
.expect("the payload is of its own type");
assert_eq!(
serde_json::to_value(&read).expect("a payload is a document"),
serde_json::from_str::<Value>(r#"{"action_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc", "intervention_id": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}"#)
.expect("a generated payload is a document")
);
assert_eq!(unknown.kind, "kind_from_a_newer_server");
assert!(unknown.payload_as::<ActionExecutedPayload>().is_none());
assert_eq!(
unknown.payload,
serde_json::from_str::<Value>(r#"{"anything": "0198f0a1-2b3c-7d4e-8f90-123456789abc"}"#)
.expect("a generated payload is a document"),
"the opaque payload of a kind this client does not know was not carried through"
);
assert!(EVENT_KINDS.contains(&"action_executed"));
assert!(!EVENT_KINDS.contains(&"kind_from_a_newer_server"));
}