forked from Crockan/MercuryToolbox
chore(release): prepare public source release
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
//! Integration tests for the `portping` command.
|
||||
|
||||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::process::{Child, Command as StdCommand, Stdio};
|
||||
use std::sync::mpsc;
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use assert_cmd::Command;
|
||||
use portping::{Target, parse_target};
|
||||
use predicates::prelude::*;
|
||||
|
||||
fn cargo_command() -> Command {
|
||||
Command::cargo_bin("portping").expect("binary")
|
||||
}
|
||||
|
||||
fn spawn_http_server() -> u16 {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").expect("listener");
|
||||
let port = listener.local_addr().expect("address").port();
|
||||
|
||||
thread::spawn(move || {
|
||||
if let Ok((mut stream, _)) = listener.accept() {
|
||||
let mut buffer = [0_u8; 1024];
|
||||
let _ = stream.read(&mut buffer);
|
||||
let response = b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\nConnection: close\r\n\r\nok";
|
||||
let _ = stream.write_all(response);
|
||||
}
|
||||
});
|
||||
|
||||
port
|
||||
}
|
||||
|
||||
fn spawn_capturing_http_server(status_line: &'static str) -> (u16, mpsc::Receiver<String>) {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").expect("listener");
|
||||
let port = listener.local_addr().expect("address").port();
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
|
||||
thread::spawn(move || {
|
||||
if let Ok((mut stream, _)) = listener.accept() {
|
||||
let mut buffer = [0_u8; 1024];
|
||||
let read = stream.read(&mut buffer).expect("read request");
|
||||
let request = String::from_utf8_lossy(&buffer[..read]).to_string();
|
||||
sender.send(request).expect("send request");
|
||||
let response =
|
||||
format!("{status_line}\r\nContent-Length: 0\r\nConnection: close\r\n\r\n");
|
||||
stream
|
||||
.write_all(response.as_bytes())
|
||||
.expect("write response");
|
||||
}
|
||||
});
|
||||
|
||||
(port, receiver)
|
||||
}
|
||||
|
||||
fn unused_local_port() -> u16 {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").expect("listener");
|
||||
let port = listener.local_addr().expect("address").port();
|
||||
drop(listener);
|
||||
port
|
||||
}
|
||||
|
||||
struct ChildGuard(Child);
|
||||
|
||||
impl Drop for ChildGuard {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.0.kill();
|
||||
let _ = self.0.wait();
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_passive_listener_child(port: u16) -> ChildGuard {
|
||||
let mut child = StdCommand::new("pwsh")
|
||||
.arg("-NoProfile")
|
||||
.arg("-Command")
|
||||
.arg(format!(
|
||||
"$listener = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, {port}); \
|
||||
$listener.Start(); \
|
||||
Write-Output 'ready'; \
|
||||
try {{ Start-Sleep -Seconds 30 }} finally {{ $listener.Stop() }}"
|
||||
))
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("spawn passive listener child");
|
||||
let stdout = child.stdout.take().expect("passive listener stdout");
|
||||
let (sender, receiver) = mpsc::channel();
|
||||
thread::spawn(move || {
|
||||
let mut line = String::new();
|
||||
let _ = BufReader::new(stdout).read_line(&mut line);
|
||||
let _ = sender.send(line);
|
||||
});
|
||||
|
||||
let ready_line = receiver
|
||||
.recv_timeout(Duration::from_secs(15))
|
||||
.expect("passive listener child did not report readiness");
|
||||
assert!(
|
||||
ready_line.trim() == "ready",
|
||||
"unexpected passive listener readiness line: {ready_line:?}"
|
||||
);
|
||||
|
||||
ChildGuard(child)
|
||||
}
|
||||
|
||||
fn wait_for_tcp_listener(port: u16) {
|
||||
let deadline = Instant::now() + Duration::from_secs(5);
|
||||
while Instant::now() < deadline {
|
||||
if TcpStream::connect(("127.0.0.1", port)).is_ok() {
|
||||
return;
|
||||
}
|
||||
thread::sleep(Duration::from_millis(25));
|
||||
}
|
||||
panic!("passive listener on port {port} did not become ready");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_targets() {
|
||||
assert!(matches!(
|
||||
parse_target("tcp://127.0.0.1:9000").expect("tcp"),
|
||||
Target::Tcp { port, .. } if port == 9000
|
||||
));
|
||||
assert!(matches!(
|
||||
parse_target("http://127.0.0.1:9000/health").expect("http"),
|
||||
Target::Http { port, .. } if port == 9000
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probes_http_endpoint_as_json() {
|
||||
let port = spawn_http_server();
|
||||
let mut command = cargo_command();
|
||||
command
|
||||
.arg(format!("http://127.0.0.1:{port}/health"))
|
||||
.arg("--json")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("\"status_code\":200"))
|
||||
.stdout(predicate::str::contains("\"ok\":true"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reports_closed_tcp_port() {
|
||||
let port = unused_local_port();
|
||||
|
||||
let mut command = cargo_command();
|
||||
command
|
||||
.arg(format!("tcp://127.0.0.1:{port}"))
|
||||
.arg("--json")
|
||||
.assert()
|
||||
.code(1)
|
||||
.stdout(predicate::str::contains("\"ok\":false"))
|
||||
.stdout(predicate::str::contains("\"error\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn report_mode_keeps_diagnostic_sweeps_successful_when_probe_fails() {
|
||||
let port = unused_local_port();
|
||||
|
||||
let mut command = cargo_command();
|
||||
command
|
||||
.arg(format!("tcp://127.0.0.1:{port}"))
|
||||
.arg("--report")
|
||||
.arg("--json")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("\"ok\":false"))
|
||||
.stdout(predicate::str::contains("\"error_code\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supports_powershell_pipeline() {
|
||||
let port = spawn_http_server();
|
||||
let binary = assert_cmd::cargo::cargo_bin("portping");
|
||||
let script = format!(
|
||||
"'http://127.0.0.1:{port}/health' | & '{}'",
|
||||
binary.display()
|
||||
);
|
||||
|
||||
let mut command = Command::new("pwsh");
|
||||
command
|
||||
.arg("-NoProfile")
|
||||
.arg("-Command")
|
||||
.arg(script)
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("200"))
|
||||
.stdout(predicate::str::contains("ok"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supports_head_requests_and_expected_status_checks() {
|
||||
let (port, receiver) = spawn_capturing_http_server("HTTP/1.1 204 No Content");
|
||||
let mut command = cargo_command();
|
||||
command
|
||||
.arg(format!("http://127.0.0.1:{port}/health"))
|
||||
.arg("--method")
|
||||
.arg("HEAD")
|
||||
.arg("--expect-status")
|
||||
.arg("204")
|
||||
.arg("--json")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("\"method\":\"HEAD\""))
|
||||
.stdout(predicate::str::contains("\"expected_status\":204"))
|
||||
.stdout(predicate::str::contains(
|
||||
"\"status_matches_expectation\":true",
|
||||
))
|
||||
.stdout(predicate::str::contains("\"ok\":true"));
|
||||
|
||||
let request = receiver.recv().expect("captured request");
|
||||
assert!(request.starts_with("HEAD /health HTTP/1.1\r\n"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn probes_passive_loopback_listener_started_by_child_process() {
|
||||
let port = unused_local_port();
|
||||
let _listener = spawn_passive_listener_child(port);
|
||||
wait_for_tcp_listener(port);
|
||||
|
||||
let mut command = cargo_command();
|
||||
command
|
||||
.arg(format!("tcp://127.0.0.1:{port}"))
|
||||
.arg("--json")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("\"ok\":true"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn marks_status_mismatches_as_failed_results() {
|
||||
let port = spawn_http_server();
|
||||
let mut command = cargo_command();
|
||||
command
|
||||
.arg(format!("http://127.0.0.1:{port}/health"))
|
||||
.arg("--expect-status")
|
||||
.arg("204")
|
||||
.arg("--json")
|
||||
.assert()
|
||||
.code(1)
|
||||
.stdout(predicate::str::contains("\"status_code\":200"))
|
||||
.stdout(predicate::str::contains(
|
||||
"\"status_matches_expectation\":false",
|
||||
))
|
||||
.stdout(predicate::str::contains("\"ok\":false"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help_includes_health_check_examples() {
|
||||
let mut command = cargo_command();
|
||||
command
|
||||
.arg("--help")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("--method"))
|
||||
.stdout(predicate::str::contains("--expect-status"))
|
||||
.stdout(predicate::str::contains("--report"))
|
||||
.stdout(predicate::str::contains("Exit code"))
|
||||
.stdout(predicate::str::contains("--toon"));
|
||||
}
|
||||
Reference in New Issue
Block a user