Files
ngit-grasp/tests/common/relay.rs
T
DanConwayDev 0ab5b17d47 fix: remove nix crate usage from test code
Replace nix::sys::signal usage with standard library process::kill().
This completes the removal of the unnecessary nix dev dependency.

All tests passing:
- NIP-01: 6 passed, 1 ignored
- NIP-34: 12 passed, 1 ignored
2025-11-04 22:02:49 +00:00

173 lines
4.8 KiB
Rust

//! Test relay fixture
//!
//! Provides automatic relay lifecycle management for integration tests.
use std::process::{Child, Command, Stdio};
use std::time::Duration;
use tokio::time::sleep;
/// Test relay fixture that manages relay lifecycle
///
/// Automatically starts and stops the ngit-grasp relay for testing.
/// Uses a random port to avoid conflicts.
pub struct TestRelay {
process: Child,
url: String,
port: u16,
}
impl TestRelay {
/// Start a test relay instance
///
/// # Example
///
/// ```no_run
/// use common::TestRelay;
///
/// #[tokio::test]
/// async fn test_something() {
/// let relay = TestRelay::start().await;
/// // Use relay.url() for testing
/// relay.stop().await;
/// }
/// ```
pub async fn start() -> Self {
Self::start_with_port(Self::find_free_port()).await
}
/// Start relay on a specific port
pub async fn start_with_port(port: u16) -> Self {
let bind_address = format!("127.0.0.1:{}", port);
let url = format!("ws://127.0.0.1:{}", port);
// Use the built binary directly (faster than cargo run)
let binary_path = std::env::current_exe()
.expect("Failed to get current exe")
.parent()
.expect("Failed to get parent dir")
.parent()
.expect("Failed to get grandparent dir")
.join("ngit-grasp");
// Start the relay process
let process = Command::new(&binary_path)
.env("NGIT_BIND_ADDRESS", &bind_address)
.env("NGIT_DOMAIN", &bind_address) // Set domain to match bind address
.env("RUST_LOG", "warn") // Less logging during tests
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("Failed to start relay process");
let relay = Self {
process,
url,
port,
};
// Wait for relay to be ready
relay.wait_for_ready().await;
relay
}
/// Get the relay WebSocket URL
pub fn url(&self) -> &str {
&self.url
}
/// Get the relay port
pub fn port(&self) -> u16 {
self.port
}
/// Get the relay domain (host:port)
pub fn domain(&self) -> String {
format!("127.0.0.1:{}", self.port)
}
/// Wait for the relay to be ready to accept connections
async fn wait_for_ready(&self) {
let max_attempts = 50; // 5 seconds total
let delay = Duration::from_millis(100);
for attempt in 0..max_attempts {
// Try to connect to the relay
match tokio::net::TcpStream::connect(format!("127.0.0.1:{}", self.port)).await {
Ok(_) => {
// Connection successful, relay is ready
// Give it a tiny bit more time to fully initialize
sleep(Duration::from_millis(100)).await;
return;
}
Err(_) => {
if attempt == max_attempts - 1 {
panic!("Relay failed to start after {} attempts", max_attempts);
}
sleep(delay).await;
}
}
}
}
/// Stop the relay
pub async fn stop(mut self) {
// Kill the process (gracefully if possible)
let _ = self.process.kill();
// Wait a bit for graceful shutdown
sleep(Duration::from_millis(100)).await;
// Force kill if still running
let _ = self.process.kill();
let _ = self.process.wait();
}
/// Find a free port to use for testing
fn find_free_port() -> u16 {
use std::net::TcpListener;
// Bind to port 0 to get a random free port
let listener = TcpListener::bind("127.0.0.1:0")
.expect("Failed to bind to random port");
let port = listener.local_addr()
.expect("Failed to get local address")
.port();
// Drop the listener to free the port
drop(listener);
port
}
}
impl Drop for TestRelay {
fn drop(&mut self) {
// Ensure process is killed when TestRelay is dropped
let _ = self.process.kill();
let _ = self.process.wait();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
#[ignore] // Requires relay binary to be built
async fn test_relay_lifecycle() {
let relay = TestRelay::start().await;
assert!(relay.url().starts_with("ws://127.0.0.1:"));
assert!(relay.port() > 0);
relay.stop().await;
}
#[test]
fn test_find_free_port() {
let port = TestRelay::find_free_port();
assert!(port > 0);
// Port is u16, so it's always < 65536
}
}