use super::*;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
fn rendezvous(listening: IpAddr, port: u16) -> crate::daemon::MachineRendezvous {
crate::daemon::MachineRendezvous {
version: crate::get_version().to_string(),
pid: 4242,
http_address: listening,
http_port: port,
machine_id: "k7f3q9zx2m".to_string(),
machine_name: "desktop".to_string(),
peer_address: std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED),
peer_port: 7420,
started_at: 1_700_000_000,
control_token: String::new(),
}
}
#[test]
fn test9899_an_unspecified_address_is_dialled_on_loopback() {
let everywhere = Client::from_rendezvous(
&rendezvous(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 3000),
)
.expect("a client is built");
assert_eq!(everywhere.workbench_url(), "http://127.0.0.1:3000");
let everywhere_v6 = Client::from_rendezvous(
&rendezvous(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 3000),
)
.expect("a client is built");
assert_eq!(everywhere_v6.workbench_url(), "http://[::1]:3000");
}
#[test]
fn test9900_a_specific_address_is_dialled_where_it_listens() {
let client = Client::from_rendezvous(
&rendezvous("192.168.1.10".parse().expect("an address"), 3100),
)
.expect("a client is built");
assert_eq!(client.workbench_url(), "http://192.168.1.10:3100");
let loopback = Client::from_rendezvous(
&rendezvous(IpAddr::V4(Ipv4Addr::LOCALHOST), 3000),
)
.expect("a client is built");
assert_eq!(loopback.workbench_url(), "http://127.0.0.1:3000");
}
fn one_shot_daemon() -> (u16, std::thread::JoinHandle<String>) {
one_shot_daemon_answering("{}".to_string())
}
fn one_shot_daemon_answering(body: String) -> (u16, std::thread::JoinHandle<String>) {
use std::io::{Read, Write};
let listener = std::net::TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).expect("a port");
let port = listener.local_addr().expect("the bound address").port();
let handle = std::thread::spawn(move || {
let (mut stream, _) = listener.accept().expect("a connection");
let mut request = vec![0u8; 4096];
let read = stream.read(&mut request).expect("the request arrives");
stream
.write_all(
format!(
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: {}\r\n\
Connection: close\r\n\r\n{body}",
body.len()
)
.as_bytes(),
)
.expect("the answer is written");
let _ = stream.flush();
String::from_utf8_lossy(&request[..read]).into_owned()
});
(port, handle)
}
#[test]
fn test9598_stopping_without_a_session_still_reaches_the_daemon() {
let (port, daemon) = one_shot_daemon();
let client = Client::from_rendezvous(
&rendezvous(IpAddr::V4(Ipv4Addr::LOCALHOST), port),
)
.expect("a client");
client.shutdown().expect("the daemon answered, so the request was made");
let request = daemon.join().expect("the daemon thread finishes");
assert!(
request.starts_with("POST /api/daemon/shutdown"),
"the request that reached the daemon was: {request}"
);
assert!(
!request.to_lowercase().contains("authorization:"),
"a client with no session sent one anyway: {request}"
);
}
#[test]
fn test11498_stopping_presents_the_machines_control_secret() {
let (port, daemon) = one_shot_daemon();
let mut published = rendezvous(IpAddr::V4(Ipv4Addr::LOCALHOST), port);
published.control_token = "d3adb33f".repeat(8);
let client = Client::from_rendezvous(&published).expect("a client");
client.shutdown().expect("the daemon answered");
let request = daemon.join().expect("the daemon thread finishes");
let lowered = request.to_lowercase();
assert!(
lowered.contains(crate::daemon::protocol::route::CONTROL_HEADER),
"the request carried no control secret, so a daemon holding a store \
would ask for a passphrase: {request}"
);
assert!(
request.contains(&published.control_token),
"the header is there and the secret is not: {request}"
);
assert!(
!lowered.contains("authorization:"),
"a client with no session sent one anyway: {request}"
);
}
#[test]
fn test11499_no_control_secret_means_no_header() {
let (port, daemon) = one_shot_daemon();
let published = rendezvous(IpAddr::V4(Ipv4Addr::LOCALHOST), port);
assert!(published.control_token.is_empty());
let client = Client::from_rendezvous(&published).expect("a client");
client.shutdown().expect("the daemon answered");
let request = daemon.join().expect("the daemon thread finishes");
assert!(
!request
.to_lowercase()
.contains(crate::daemon::protocol::route::CONTROL_HEADER),
"an empty control secret was sent as though it were one: {request}"
);
}
#[test]
fn test20042_a_resolution_must_contain_the_directory() {
let scratch = tempfile::tempdir().expect("a scratch directory");
let main = scratch.path().join("main-worktree");
let copy = scratch.path().join("main-worktree-copy");
for dir in [main.join("src"), copy.clone()] {
std::fs::create_dir_all(&dir).expect("mkdir");
}
let answer = |root: &std::path::Path| crate::daemon::WorktreeInfo {
id: "alpha:main".to_string(),
project: Some("alpha".to_string()),
name: "main".to_string(),
root: root.display().to_string(),
workspace: None,
no_continuity: Some("no commits".to_string()),
};
check_resolution(&main.join("src"), &answer(&main)).expect("inside its own worktree");
check_resolution(&main, &answer(&main)).expect("at its root");
let refusal = check_resolution(©, &answer(&main)).expect_err("another worktree");
assert!(refusal.contains("does not contain it"), "{refusal}");
let resolving = |cwd: &std::path::Path, answered: &std::path::Path| {
let (port, daemon) =
one_shot_daemon_answering(serde_json::to_string(&answer(answered)).expect("encodes"));
let client = Client::from_rendezvous(&rendezvous(IpAddr::V4(Ipv4Addr::LOCALHOST), port))
.expect("a client");
let outcome = client.resolve_worktree(cwd);
daemon.join().expect("the daemon thread finishes");
(outcome, client.worktree_info())
};
let (outcome, held) = resolving(©, &main);
assert!(outcome.expect_err("another worktree's answer").contains("does not contain it"));
assert_eq!(held, None, "the wrong worktree was kept for later requests");
let (outcome, held) = resolving(&main.join("src"), &main);
assert_eq!(outcome.expect("its own worktree").root, main.display().to_string());
assert!(held.is_some());
}