first pass of merging in warp (doesn't build)
This commit is contained in:
@@ -0,0 +1,336 @@
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
#[cfg(unix)]
|
||||
use command::blocking::Command;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn control_endpoint_composes_loopback_control_route() {
|
||||
assert_eq!(
|
||||
ControlEndpoint::localhost(4000).url(),
|
||||
"http://127.0.0.1:4000/v1/control"
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn broker_socket_reference_is_bound_to_instance_identity() {
|
||||
let record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4000)),
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
record
|
||||
.credential_broker
|
||||
.expect("credential broker")
|
||||
.socket_path,
|
||||
PathBuf::from(format!("{}.broker.sock", record.instance_id.0))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registered_instance_round_trips_discovery_record() {
|
||||
let dir = tempfile::tempdir().expect("temp dir");
|
||||
let record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4000)),
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
let _registered = RegisteredInstance::register_in_dir_for_test(record.clone(), dir.path())
|
||||
.expect("registered");
|
||||
let records = list_instances_from_dir(dir.path(), "local");
|
||||
assert_eq!(records, vec![record]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn incompatible_protocol_record_is_ignored() {
|
||||
let dir = tempfile::tempdir().expect("temp dir");
|
||||
let mut record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4000)),
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
record.protocol_version = PROTOCOL_VERSION + 1;
|
||||
let _registered =
|
||||
RegisteredInstance::register_in_dir_for_test(record, dir.path()).expect("registered");
|
||||
|
||||
assert!(list_instances_from_dir(dir.path(), "local").is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_record_and_matching_broker_socket_are_pruned() {
|
||||
let dir = tempfile::tempdir().expect("temp dir");
|
||||
let record_path = dir.path().join("inst_malformed.json");
|
||||
let socket_path = dir.path().join("inst_malformed.broker.sock");
|
||||
fs::write(&record_path, "not json").expect("write malformed record");
|
||||
fs::write(&socket_path, "").expect("write broker socket");
|
||||
|
||||
assert!(list_instances_from_dir(dir.path(), "local").is_empty());
|
||||
assert!(!record_path.exists());
|
||||
assert!(!socket_path.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn orphan_broker_sockets_are_pruned_after_grace_period() {
|
||||
let dir = tempfile::tempdir().expect("temp dir");
|
||||
let orphan_path = dir.path().join("inst_orphan.broker.sock");
|
||||
let retained_filename = PathBuf::from("inst_retained.broker.sock");
|
||||
let retained_path = dir.path().join(&retained_filename);
|
||||
fs::write(&orphan_path, "").expect("write orphan socket");
|
||||
fs::write(&retained_path, "").expect("write retained socket");
|
||||
|
||||
sweep_orphan_broker_sockets(
|
||||
dir.path(),
|
||||
&HashSet::from([retained_filename]),
|
||||
Duration::ZERO,
|
||||
);
|
||||
|
||||
assert!(!orphan_path.exists());
|
||||
assert!(retained_path.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn abandoned_temp_records_are_pruned_after_grace_period() {
|
||||
let dir = tempfile::tempdir().expect("temp dir");
|
||||
let temp_path = dir.path().join("inst_abandoned.json.tmp");
|
||||
fs::write(&temp_path, "").expect("write temporary record");
|
||||
|
||||
sweep_abandoned_temp_records(dir.path(), Duration::ZERO);
|
||||
|
||||
assert!(!temp_path.exists());
|
||||
}
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn stale_process_record_is_pruned() {
|
||||
let dir = tempfile::tempdir().expect("temp dir");
|
||||
let mut child = Command::new("true")
|
||||
.spawn()
|
||||
.expect("short-lived process starts");
|
||||
let pid = child.id();
|
||||
child.wait().expect("short-lived process exits");
|
||||
let mut record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4000)),
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
record.pid = pid;
|
||||
let socket_path = dir.path().join(broker_socket_filename(&record.instance_id));
|
||||
fs::write(&socket_path, "").expect("write broker socket");
|
||||
let registered =
|
||||
RegisteredInstance::register_in_dir_for_test(record, dir.path()).expect("registered");
|
||||
|
||||
assert!(list_instances_from_dir(dir.path(), "local").is_empty());
|
||||
assert!(!registered.path.exists());
|
||||
assert!(!socket_path.exists());
|
||||
}
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn multiple_live_process_records_are_discovered() {
|
||||
let dir = tempfile::tempdir().expect("temp dir");
|
||||
let mut first_process = Command::new("sleep")
|
||||
.arg("10")
|
||||
.spawn()
|
||||
.expect("first process starts");
|
||||
let mut second_process = Command::new("sleep")
|
||||
.arg("10")
|
||||
.spawn()
|
||||
.expect("second process starts");
|
||||
let mut first_record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4000)),
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
first_record.pid = first_process.id();
|
||||
let mut second_record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4001)),
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
second_record.pid = second_process.id();
|
||||
let first_id = first_record.instance_id.clone();
|
||||
let second_id = second_record.instance_id.clone();
|
||||
let _first = RegisteredInstance::register_in_dir_for_test(first_record, dir.path())
|
||||
.expect("first registered");
|
||||
let _second = RegisteredInstance::register_in_dir_for_test(second_record, dir.path())
|
||||
.expect("second registered");
|
||||
|
||||
let ids = list_instances_from_dir(dir.path(), "local")
|
||||
.into_iter()
|
||||
.map(|record| record.instance_id)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(ids.len(), 2);
|
||||
assert!(ids.contains(&first_id));
|
||||
assert!(ids.contains(&second_id));
|
||||
|
||||
first_process.kill().expect("first process stops");
|
||||
first_process.wait().expect("first process reaped");
|
||||
second_process.kill().expect("second process stops");
|
||||
second_process.wait().expect("second process reaped");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn records_from_other_channels_are_ignored() {
|
||||
let dir = tempfile::tempdir().expect("temp dir");
|
||||
let record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4000)),
|
||||
"dev",
|
||||
"dev.warp.Warp-Dev",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
let socket_path = dir.path().join(broker_socket_filename(&record.instance_id));
|
||||
fs::write(&socket_path, "").expect("write broker socket");
|
||||
let _registered =
|
||||
RegisteredInstance::register_in_dir_for_test(record, dir.path()).expect("registered");
|
||||
|
||||
assert!(list_instances_from_dir(dir.path(), "local").is_empty());
|
||||
assert!(socket_path.exists());
|
||||
}
|
||||
#[test]
|
||||
fn serialized_discovery_record_does_not_contain_raw_credential_material() {
|
||||
let raw_secret = "raw-secret-token-material";
|
||||
let record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4000)),
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
let serialized = serde_json::to_string_pretty(&record).expect("serialize");
|
||||
assert!(!serialized.contains(raw_secret));
|
||||
assert!(!serialized.contains("auth_token"));
|
||||
assert!(!serialized.contains("bearer_token"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_record_does_not_expose_actionable_authority() {
|
||||
let record = InstanceRecord::for_current_process(
|
||||
None,
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
assert!(record.endpoint.is_none());
|
||||
assert!(record.credential_broker.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_unsafe_or_divergent_discovery_authority() {
|
||||
let mut record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4000)),
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
record
|
||||
.validate_local_control_authority()
|
||||
.expect("matching 127.0.0.1 endpoints are accepted");
|
||||
|
||||
record.endpoint.as_mut().expect("endpoint").host = "localhost".to_owned();
|
||||
let err = record
|
||||
.validate_local_control_authority()
|
||||
.expect_err("localhost alias is rejected");
|
||||
assert_eq!(err.code, ErrorCode::UnauthorizedLocalClient);
|
||||
|
||||
record.endpoint = Some(ControlEndpoint::localhost(4000));
|
||||
record
|
||||
.credential_broker
|
||||
.as_mut()
|
||||
.expect("credential broker")
|
||||
.socket_path = "different.broker.sock".into();
|
||||
let err = record
|
||||
.validate_local_control_authority()
|
||||
.expect_err("divergent broker socket is rejected");
|
||||
assert_eq!(err.code, ErrorCode::UnauthorizedLocalClient);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn discovery_directory_is_owner_only_on_unix() {
|
||||
use std::os::unix::fs::PermissionsExt as _;
|
||||
|
||||
let dir = tempfile::tempdir().expect("temp dir");
|
||||
let record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4000)),
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
let _registered =
|
||||
RegisteredInstance::register_in_dir_for_test(record, dir.path()).expect("registered");
|
||||
let mode = fs::metadata(dir.path())
|
||||
.expect("metadata")
|
||||
.permissions()
|
||||
.mode()
|
||||
& 0o777;
|
||||
assert_eq!(mode, 0o700);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn discovery_record_is_owner_only_on_unix() {
|
||||
|
||||
let dir = tempfile::tempdir().expect("temp dir");
|
||||
let record = InstanceRecord::for_current_process(
|
||||
Some(ControlEndpoint::localhost(4000)),
|
||||
"local",
|
||||
"dev.warp.WarpLocal",
|
||||
Some("test".to_owned()),
|
||||
crate::protocol::ActionKind::implemented_metadata(),
|
||||
);
|
||||
let registered =
|
||||
RegisteredInstance::register_in_dir_for_test(record, dir.path()).expect("registered");
|
||||
let mode = fs::metadata(®istered.path)
|
||||
.expect("metadata")
|
||||
.permissions()
|
||||
.mode()
|
||||
& 0o777;
|
||||
assert_eq!(mode, 0o600);
|
||||
}
|
||||
|
||||
impl RegisteredInstance {
|
||||
fn register_in_dir_for_test(record: InstanceRecord, dir: &Path) -> Result<Self, ControlError> {
|
||||
fs::create_dir_all(dir).expect("create dir");
|
||||
#[cfg(unix)]
|
||||
set_private_dir_permissions(dir)?;
|
||||
let path = record_path(dir, &record.instance_id);
|
||||
let bytes = serde_json::to_vec_pretty(&record).map_err(|err| {
|
||||
ControlError::with_details(
|
||||
ErrorCode::Internal,
|
||||
"failed to serialize local-control discovery test record",
|
||||
err.to_string(),
|
||||
)
|
||||
})?;
|
||||
fs::write(&path, bytes).map_err(|err| {
|
||||
ControlError::with_details(
|
||||
ErrorCode::Internal,
|
||||
"failed to write local-control discovery test record",
|
||||
err.to_string(),
|
||||
)
|
||||
})?;
|
||||
#[cfg(unix)]
|
||||
set_private_permissions(&path)?;
|
||||
Ok(Self {
|
||||
record,
|
||||
path,
|
||||
broker_socket_path: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user