use super::*;
use crate::server::connection::declared_holder::DeclaredHolder;
const TEMPLATE: &str = "echo";
fn open_services_runtime(
door: &Arc<Door>,
dir: &std::path::Path,
) -> Result<ConnectionRuntime, Failure> {
let runtime = ConnectionRuntime::for_tests(Arc::clone(door) as Arc<dyn ConnectionServices>);
runtime.register_for_tests(PID)?;
let lifecycle = crate::services::lifecycle::Lifecycle::open(
&[crate::config::types::ServiceTemplateConfig {
name: TEMPLATE.to_owned(),
command: std::path::PathBuf::from("/bin/sh"),
args: vec!["-c".to_owned(), "exec sleep 30".to_owned()],
subjects: vec![TEMPLATE.to_owned()],
env: Vec::new(),
stop_timeout_ms: 2_000,
}],
dir.to_path_buf(),
crate::services::lifecycle::Lifecycle::OPEN_NETWORK.to_owned(),
None,
)
.map_err(|error| format!("the registry opens: {error:?}"))?;
lifecycle.listening("127.0.0.1:1".to_owned());
assert!(runtime.install_lifecycle_for_tests(lifecycle));
Ok(runtime)
}
fn connect(
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
credential: &[u8],
) -> FrameAction {
let version = ProtocolVersion::new(1, 0);
apply_frame(
PID,
runtime,
state,
Frame::Connect {
flags: 0,
min_version: version,
max_version: version,
auth_token: credential.to_vec(),
},
)
}
fn lifecycle_verb(
runtime: &ConnectionRuntime,
state: &mut ConnectionProcessState,
conversation: u64,
verb: &str,
operation: &str,
payload: &serde_json::Value,
) -> Result<serde_json::Value, String> {
let opened = apply_frame(
PID,
runtime,
state,
Frame::ConversationOpen {
flags: 0,
stream_id: STREAM,
conversation_id: conversation,
subject: format!("services.{verb}"),
},
);
if !matches!(opened, FrameAction::NoResponse) {
return Err(format!("services.{verb} did not open: {opened:?}"));
}
let request = serde_json::json!({"operation": operation, "payload": payload});
let action = apply_frame(
PID,
runtime,
state,
Frame::ConversationMessage {
flags: liminal::protocol::CONVERSATION_REPLY_REQUESTED_FLAG,
stream_id: STREAM,
conversation_id: conversation,
envelope: MessageEnvelope::new(
SchemaId::new([1; SchemaId::WIRE_LEN]),
CausalContext::independent(),
request.to_string().into_bytes(),
),
},
);
match action {
FrameAction::Respond(Frame::ConversationMessage { envelope, .. }) => {
serde_json::from_slice(&envelope.payload).map_err(|error| error.to_string())
}
FrameAction::Respond(Frame::ConversationError {
message: Some(message),
..
}) => Err(message),
other => Err(format!("unexpected answer {other:?}")),
}
}
#[test]
fn access012_an_open_exchange_records_the_holder_the_record_declares() -> Result<(), Failure> {
let door = Arc::new(Door::default());
let runtime = ConnectionRuntime::for_tests(Arc::clone(&door) as Arc<dyn ConnectionServices>);
runtime.register_for_tests(PID)?;
let mut state = ConnectionProcessState::default();
assert!(matches!(
connect(&runtime, &mut state, b"open:hermes"),
FrameAction::Respond(Frame::ConnectAck { .. })
));
assert_eq!(
state.declared_holder,
Some(DeclaredHolder::Open("hermes".to_owned()))
);
assert_eq!(state.holder_record(), Some("open:hermes".to_owned()));
assert!(state.verified_holder.is_none() && state.lys_session.is_none());
Ok(())
}
#[test]
fn access012_every_other_record_on_an_open_exchange_is_anonymous() -> Result<(), Failure> {
let door = Arc::new(Door::default());
for credential in [
Vec::new(),
WireCredential::lys_record(b"not-a-pass"),
vec![9, 9],
b"open:Not Valid!".to_vec(),
b"open:".to_vec(),
] {
let runtime =
ConnectionRuntime::for_tests(Arc::clone(&door) as Arc<dyn ConnectionServices>);
runtime.register_for_tests(PID)?;
let mut state = ConnectionProcessState::default();
assert!(matches!(
connect(&runtime, &mut state, &credential),
FrameAction::Respond(Frame::ConnectAck { .. })
));
assert_eq!(
state.holder_record(),
Some("open:anonymous".to_owned()),
"{credential:?}"
);
}
Ok(())
}
#[test]
fn access012_a_gated_exchange_refuses_an_open_record_as_an_unknown_credential()
-> Result<(), Failure> {
let door = Arc::new(Door::default());
let runtime = ConnectionRuntime::for_tests_with_auth_token(
Arc::clone(&door) as Arc<dyn ConnectionServices>,
b"bearer-under-test".to_vec(),
);
runtime.register_for_tests(PID)?;
let mut state = ConnectionProcessState::default();
let action = connect(&runtime, &mut state, b"open:hermes");
let FrameAction::RespondThenClose(Frame::ConnectError {
message: Some(words),
..
}) = action
else {
return Err(format!("the open record was not refused: {action:?}").into());
};
assert_eq!(words, "connection authentication token rejected");
assert!(state.declared_holder.is_none() && !state.authenticated);
Ok(())
}
#[test]
fn access012_the_lifecycle_serves_an_open_holder_with_no_session() -> Result<(), Failure> {
let door = Arc::new(Door::default());
let dir = tempfile::tempdir()?;
let runtime = open_services_runtime(&door, dir.path())?;
let mut state = ConnectionProcessState::default();
connect(&runtime, &mut state, b"open:hermes");
let made = lifecycle_verb(
&runtime,
&mut state,
1,
"create",
"op-1",
&serde_json::json!({"template": TEMPLATE, "name": "one"}),
)?;
assert_eq!(made["outcome"], "acted", "{made}");
assert_eq!(made["holder"], "open:hermes", "{made}");
assert_eq!(made["service"]["created_by"], "open:hermes", "{made}");
assert_eq!(made["service"]["state"], "asleep", "{made}");
let id = made["service"]["id"]
.as_str()
.ok_or("the record has an id")?
.to_owned();
let listed = lifecycle_verb(
&runtime,
&mut state,
2,
"list",
"op-2",
&serde_json::Value::Null,
)?;
let services = listed["services"]
.as_array()
.ok_or("list answers services")?;
assert!(
services.iter().any(|service| service["id"] == id),
"{listed}"
);
let started = lifecycle_verb(
&runtime,
&mut state,
3,
"deploy",
"op-3",
&serde_json::json!({"id": id}),
)?;
assert_eq!(started["outcome"], "acted", "{started}");
let held = lifecycle_verb(
&runtime,
&mut state,
4,
"hold",
"op-4",
&serde_json::json!({"id": id}),
)?;
assert_eq!(held["outcome"], "acted", "{held}");
let retired = lifecycle_verb(
&runtime,
&mut state,
5,
"retire",
"op-5",
&serde_json::json!({"id": id}),
)?;
assert_eq!(retired["outcome"], "acted", "{retired}");
assert_eq!(retired["service"]["state"], "retired", "{retired}");
Ok(())
}
#[test]
fn access012_an_anonymous_open_connection_is_served_too() -> Result<(), Failure> {
let door = Arc::new(Door::default());
let dir = tempfile::tempdir()?;
let runtime = open_services_runtime(&door, dir.path())?;
let mut state = ConnectionProcessState::default();
connect(&runtime, &mut state, b"");
let made = lifecycle_verb(
&runtime,
&mut state,
1,
"create",
"op-1",
&serde_json::json!({"template": TEMPLATE, "name": "anon"}),
)?;
assert_eq!(made["service"]["created_by"], "open:anonymous", "{made}");
Ok(())
}
#[test]
fn access012_the_verbs_that_ask_a_person_are_refused_on_an_open_exchange() -> Result<(), Failure> {
let door = Arc::new(Door::default());
let dir = tempfile::tempdir()?;
let runtime = open_services_runtime(&door, dir.path())?;
let mut state = ConnectionProcessState::default();
connect(&runtime, &mut state, b"open:hermes");
for verb in [
"create_site",
"bind_builder",
"unbind_builder",
"plan_set",
"plan_step",
"propose_template",
"complete",
] {
let refused =
conversation_refusal(open(&runtime, &mut state, &format!("services.{verb}")))?;
assert_eq!(
refused,
format!(
"needs_a_lys_exchange: {verb} asks a person or a draft; this exchange has no [auth]"
)
);
}
for verb in ["update", "retire"] {
let refused =
conversation_refusal(open(&runtime, &mut state, &format!("templates.{verb}")))?;
assert_eq!(
refused,
format!(
"needs_a_lys_exchange: templates.{verb} asks a person or a draft; this exchange has no [auth]"
)
);
}
Ok(())
}