memoryd: a Honcho client per workspace, and its workspace, peers and sessions made sure of
Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
@@ -0,0 +1,439 @@
|
||||
//! `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 })
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user