440 lines
13 KiB
Rust
440 lines
13 KiB
Rust
//! `memoryd`'s Honcho clients (M5c spec, sections 3 and 6), against a fake Honcho that answers
|
|
//! from the responses recorded from rift: the workspace, the peers and the session made before
|
|
//! anything is written, each once per run, with the fleet's settings and the agent unobserved;
|
|
//! the errors by kind and status, never a body. The keys are in `honcho_keys.rs`. Do not edit.
|
|
|
|
#[path = "support/honcho.rs"]
|
|
mod honcho;
|
|
|
|
use std::collections::BTreeMap;
|
|
use std::ffi::OsString;
|
|
use std::time::Duration;
|
|
|
|
use honcho::{FakeHoncho, Mode, Request};
|
|
use memoryd::honcho::client::{
|
|
CHAT_READ, CONNECT, Clients, HonchoError, READ, Timeouts, classify, fleet_configuration,
|
|
};
|
|
use memoryd::honcho::config::HonchoConfig;
|
|
use memoryd::honcho::ledger::Workspace;
|
|
use memoryd::honcho::naming::HonchoSession;
|
|
use serde_json::{Value, json};
|
|
|
|
const FAST: Timeouts = Timeouts {
|
|
connect: Duration::from_secs(5),
|
|
read: Duration::from_millis(300),
|
|
};
|
|
|
|
fn config(url: &str) -> HonchoConfig {
|
|
HonchoConfig {
|
|
url: url.to_string(),
|
|
agent: "boxmaker0".to_string(),
|
|
owner: "kyle".to_string(),
|
|
trusted: "boxmaker0-trusted".to_string(),
|
|
untrusted: "boxmaker0-untrusted".to_string(),
|
|
fleet: "fleet".to_string(),
|
|
read: Vec::new(),
|
|
keys: BTreeMap::new(),
|
|
}
|
|
}
|
|
|
|
fn no_env(_: &str) -> Option<OsString> {
|
|
None
|
|
}
|
|
|
|
fn clients(fake: &FakeHoncho) -> Clients {
|
|
Clients::new(&config(&fake.url), &no_env, FAST).unwrap().0
|
|
}
|
|
|
|
fn session(name: &str, speakers: &[&str]) -> HonchoSession {
|
|
HonchoSession {
|
|
name: name.to_string(),
|
|
speakers: speakers.iter().map(|s| s.to_string()).collect(),
|
|
}
|
|
}
|
|
|
|
/// A request as (method, path, body).
|
|
fn seen(requests: &[Request]) -> Vec<(String, String, Value)> {
|
|
requests
|
|
.iter()
|
|
.map(|r| {
|
|
(
|
|
r.method.clone(),
|
|
r.path.clone(),
|
|
r.body.clone().unwrap_or(Value::Null),
|
|
)
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn post(path: &str, body: Value) -> (String, String, Value) {
|
|
("POST".to_string(), path.to_string(), body)
|
|
}
|
|
|
|
#[test]
|
|
fn the_timeouts() {
|
|
assert_eq!(CONNECT, Duration::from_secs(5));
|
|
assert_eq!(READ, Duration::from_secs(30));
|
|
assert_eq!(CHAT_READ, Duration::from_secs(180));
|
|
assert_eq!(
|
|
Timeouts::WRITES,
|
|
Timeouts {
|
|
connect: CONNECT,
|
|
read: READ
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn each_client_has_its_workspace_and_timeouts() {
|
|
let fake = FakeHoncho::start();
|
|
let c = clients(&fake);
|
|
for (workspace, name) in [
|
|
(Workspace::Trusted, "boxmaker0-trusted"),
|
|
(Workspace::Untrusted, "boxmaker0-untrusted"),
|
|
(Workspace::Fleet, "fleet"),
|
|
] {
|
|
let client = c.client(workspace);
|
|
assert_eq!(client.workspace_id(), name);
|
|
// `roncho`'s `Debug` shows the timeouts, and whether there is a key, never the key.
|
|
let shown = format!("{client:?}");
|
|
assert!(shown.contains("connect_timeout: 5s"), "{shown}");
|
|
assert!(shown.contains("read_timeout: 300ms"), "{shown}");
|
|
assert!(shown.contains("api_key_present: false"), "{shown}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn the_fleets_configuration_is_the_recorded_one() {
|
|
let path = concat!(
|
|
env!("CARGO_MANIFEST_DIR"),
|
|
"/tests/fixtures/honcho/01-workspace-create.json"
|
|
);
|
|
let recorded: Value = serde_json::from_str(&std::fs::read_to_string(path).unwrap()).unwrap();
|
|
assert_eq!(
|
|
Value::Object(fleet_configuration()),
|
|
recorded["request"]["body"]["configuration"]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn a_session_is_made_after_its_workspace_and_peers() {
|
|
let fake = FakeHoncho::start();
|
|
let mut c = clients(&fake);
|
|
c.ensure_session(Workspace::Trusted, &session("chat-1", &["kyle"]))
|
|
.unwrap();
|
|
let w = "/v3/workspaces/boxmaker0-trusted";
|
|
assert_eq!(
|
|
seen(&fake.requests()),
|
|
vec![
|
|
post("/v3/workspaces", json!({ "id": "boxmaker0-trusted" })),
|
|
post(
|
|
&format!("{w}/peers"),
|
|
json!({ "id": "boxmaker0", "configuration": { "observe_me": false } })
|
|
),
|
|
post(&format!("{w}/peers"), json!({ "id": "kyle" })),
|
|
post(
|
|
&format!("{w}/sessions"),
|
|
json!({ "id": "chat-1", "peers": {
|
|
"boxmaker0": { "observe_me": false, "observe_others": false },
|
|
"kyle": { "observe_me": true, "observe_others": false },
|
|
} })
|
|
),
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn what_was_made_is_not_asked_for_again_in_this_run() {
|
|
let fake = FakeHoncho::start();
|
|
let mut c = clients(&fake);
|
|
c.ensure_session(Workspace::Trusted, &session("chat-1", &["kyle"]))
|
|
.unwrap();
|
|
fake.clear();
|
|
c.ensure_session(Workspace::Trusted, &session("chat-1", &["kyle"]))
|
|
.unwrap();
|
|
assert!(fake.requests().is_empty(), "{:?}", fake.requests());
|
|
// A new speaker: only its peer and the next session.
|
|
c.ensure_session(Workspace::Trusted, &session("chat-1-2", &["kyle", "ana_b"]))
|
|
.unwrap();
|
|
let w = "/v3/workspaces/boxmaker0-trusted";
|
|
assert_eq!(
|
|
seen(&fake.requests()),
|
|
vec![
|
|
post(&format!("{w}/peers"), json!({ "id": "ana_b" })),
|
|
post(
|
|
&format!("{w}/sessions"),
|
|
json!({ "id": "chat-1-2", "peers": {
|
|
"boxmaker0": { "observe_me": false, "observe_others": false },
|
|
"kyle": { "observe_me": true, "observe_others": false },
|
|
"ana_b": { "observe_me": true, "observe_others": false },
|
|
} })
|
|
),
|
|
]
|
|
);
|
|
// Another workspace is made sure of on its own.
|
|
fake.clear();
|
|
c.ensure_session(Workspace::Untrusted, &session("chat-1", &["kyle"]))
|
|
.unwrap();
|
|
assert_eq!(fake.requests().len(), 4);
|
|
assert!(
|
|
fake.requests()
|
|
.iter()
|
|
.skip(1)
|
|
.all(|r| r.path.starts_with("/v3/workspaces/boxmaker0-untrusted/"))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn after_a_restart_everything_is_made_sure_of_again() {
|
|
let fake = FakeHoncho::start();
|
|
clients(&fake)
|
|
.ensure_session(Workspace::Trusted, &session("chat-1", &["kyle"]))
|
|
.unwrap();
|
|
fake.clear();
|
|
// Honcho answers 200 now: it has them all. That is success too.
|
|
clients(&fake)
|
|
.ensure_session(Workspace::Trusted, &session("chat-1", &["kyle"]))
|
|
.unwrap();
|
|
assert_eq!(fake.requests().len(), 4);
|
|
}
|
|
|
|
#[test]
|
|
fn the_fleet_is_made_with_its_settings_and_the_agent_alone() {
|
|
let fake = FakeHoncho::start();
|
|
let mut c = clients(&fake);
|
|
c.ensure_session(Workspace::Fleet, &session("chat-1", &[]))
|
|
.unwrap();
|
|
assert_eq!(
|
|
seen(&fake.requests()),
|
|
vec![
|
|
post(
|
|
"/v3/workspaces",
|
|
json!({ "id": "fleet", "configuration": {
|
|
"reasoning": { "enabled": false },
|
|
"summary": { "enabled": false },
|
|
"dream": { "enabled": false },
|
|
} })
|
|
),
|
|
post(
|
|
"/v3/workspaces/fleet/peers",
|
|
json!({ "id": "boxmaker0", "configuration": { "observe_me": false } })
|
|
),
|
|
post(
|
|
"/v3/workspaces/fleet/sessions",
|
|
json!({ "id": "chat-1", "peers": {
|
|
"boxmaker0": { "observe_me": false, "observe_others": false },
|
|
} })
|
|
),
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn a_409_on_create_means_it_is_there() {
|
|
let fake = FakeHoncho::start();
|
|
let mut c = clients(&fake);
|
|
fake.set(Mode::Status(409));
|
|
c.ensure_workspace(Workspace::Trusted).unwrap();
|
|
c.ensure_peer(Workspace::Trusted, "kyle").unwrap();
|
|
fake.clear();
|
|
c.ensure_workspace(Workspace::Trusted).unwrap();
|
|
assert!(fake.requests().is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn errors_by_kind_and_status() {
|
|
let fake = FakeHoncho::start();
|
|
let mut c = clients(&fake);
|
|
let cases = [
|
|
(
|
|
500,
|
|
HonchoError::Unreachable {
|
|
kind: "server",
|
|
status: Some(500),
|
|
},
|
|
),
|
|
(
|
|
503,
|
|
HonchoError::Unreachable {
|
|
kind: "server",
|
|
status: Some(503),
|
|
},
|
|
),
|
|
(401, HonchoError::KeyRefused { status: 401 }),
|
|
(403, HonchoError::KeyRefused { status: 403 }),
|
|
(
|
|
400,
|
|
HonchoError::Refused {
|
|
kind: "status",
|
|
status: Some(400),
|
|
},
|
|
),
|
|
(
|
|
404,
|
|
HonchoError::Refused {
|
|
kind: "status",
|
|
status: Some(404),
|
|
},
|
|
),
|
|
(
|
|
422,
|
|
HonchoError::Refused {
|
|
kind: "status",
|
|
status: Some(422),
|
|
},
|
|
),
|
|
];
|
|
for (status, expected) in cases {
|
|
fake.set(Mode::Status(status));
|
|
assert_eq!(
|
|
c.ensure_workspace(Workspace::Trusted),
|
|
Err(expected.clone())
|
|
);
|
|
assert_eq!(c.ensure_peer(Workspace::Fleet, "kyle"), Err(expected));
|
|
}
|
|
// Nothing failed is remembered: the next call asks again, and succeeds.
|
|
fake.set(Mode::Fixtures);
|
|
fake.clear();
|
|
c.ensure_workspace(Workspace::Trusted).unwrap();
|
|
assert_eq!(fake.requests().len(), 1);
|
|
fake.set(Mode::Hang);
|
|
assert_eq!(
|
|
c.ensure_peer(Workspace::Trusted, "ana_b"),
|
|
Err(HonchoError::Unreachable {
|
|
kind: "timeout",
|
|
status: None
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn nothing_listening_is_unreachable() {
|
|
let port = {
|
|
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
|
|
listener.local_addr().unwrap().port()
|
|
};
|
|
let (mut c, _) =
|
|
Clients::new(&config(&format!("http://127.0.0.1:{port}")), &no_env, FAST).unwrap();
|
|
assert_eq!(
|
|
c.ensure_workspace(Workspace::Trusted),
|
|
Err(HonchoError::Unreachable {
|
|
kind: "connect",
|
|
status: None
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn an_error_never_holds_the_response() {
|
|
let fake = FakeHoncho::start();
|
|
let mut c = clients(&fake);
|
|
fake.set(Mode::Status(422));
|
|
let err = c
|
|
.ensure_peer(Workspace::Trusted, "private-note-text")
|
|
.unwrap_err();
|
|
let body = fake.requests()[0].body.clone().unwrap();
|
|
assert_eq!(body["id"], "private-note-text", "the fake did not echo it");
|
|
for shown in [err.to_string(), format!("{err:?}")] {
|
|
assert!(!shown.contains("private-note-text"), "{shown}");
|
|
}
|
|
assert_eq!(
|
|
err.to_string(),
|
|
"Honcho refused the request (status, HTTP 422)"
|
|
);
|
|
assert_eq!((err.kind(), err.status()), ("status", Some(422)));
|
|
let lines = [
|
|
HonchoError::Unreachable {
|
|
kind: "timeout",
|
|
status: None,
|
|
}
|
|
.to_string(),
|
|
HonchoError::KeyRefused { status: 401 }.to_string(),
|
|
];
|
|
assert_eq!(lines[0], "Honcho is unreachable (timeout)");
|
|
assert_eq!(lines[1], "Honcho refused the key (key-refused, HTTP 401)");
|
|
}
|
|
|
|
#[test]
|
|
fn every_roncho_error_is_classified() {
|
|
use roncho::blocking::Error;
|
|
let io = || std::io::Error::other("x");
|
|
let cases = [
|
|
(Error::Connect(io()), "connect", None, true),
|
|
(Error::Timeout, "timeout", None, true),
|
|
(Error::Closed, "closed", None, true),
|
|
(Error::Io(io()), "io", None, true),
|
|
(
|
|
Error::Status {
|
|
status: 502,
|
|
body: "b".into(),
|
|
},
|
|
"server",
|
|
Some(502),
|
|
true,
|
|
),
|
|
(
|
|
Error::Status {
|
|
status: 499,
|
|
body: "b".into(),
|
|
},
|
|
"status",
|
|
Some(499),
|
|
false,
|
|
),
|
|
(
|
|
Error::Redirect { status: 301 },
|
|
"redirect",
|
|
Some(301),
|
|
false,
|
|
),
|
|
(
|
|
Error::ActiveSessions { body: "b".into() },
|
|
"active-sessions",
|
|
Some(409),
|
|
false,
|
|
),
|
|
(Error::TooLarge, "too-large", None, false),
|
|
(Error::Decode("b".into()), "decode", None, false),
|
|
(Error::Stream("b".into()), "stream", None, false),
|
|
(Error::Config("b".into()), "config", None, false),
|
|
];
|
|
for (error, kind, status, unreachable) in cases {
|
|
let got = classify(&error);
|
|
assert_eq!((got.kind(), got.status()), (kind, status), "{error:?}");
|
|
assert_eq!(
|
|
matches!(got, HonchoError::Unreachable { .. }),
|
|
unreachable,
|
|
"{error:?}"
|
|
);
|
|
}
|
|
let got = classify(&Error::Unauthorized { status: 403 });
|
|
assert_eq!(got, HonchoError::KeyRefused { status: 403 });
|
|
}
|
|
|
|
#[test]
|
|
fn a_conclusion_deleted_or_gone_is_done() {
|
|
let fake = FakeHoncho::start();
|
|
let c = clients(&fake);
|
|
c.delete_conclusion(Workspace::Trusted, "YUBMqIqHhuJprV4o-inrg")
|
|
.unwrap();
|
|
c.delete_conclusion(Workspace::Trusted, "YUBMqIqHhuJprV4o-inrg")
|
|
.unwrap();
|
|
let requests = fake.requests();
|
|
assert_eq!(requests.len(), 2);
|
|
assert!(requests.iter().all(|r| r.method == "DELETE"
|
|
&& r.path == "/v3/workspaces/boxmaker0-trusted/conclusions/YUBMqIqHhuJprV4o-inrg"));
|
|
fake.set(Mode::Status(500));
|
|
assert_eq!(
|
|
c.delete_conclusion(Workspace::Trusted, "x"),
|
|
Err(HonchoError::Unreachable {
|
|
kind: "server",
|
|
status: Some(500)
|
|
})
|
|
);
|
|
fake.set(Mode::Status(403));
|
|
assert_eq!(
|
|
c.delete_conclusion(Workspace::Trusted, "x"),
|
|
Err(HonchoError::KeyRefused { status: 403 })
|
|
);
|
|
}
|