use crate::harness::{SiteBuilder, TestServer, test_config_with_site}; use serial_test::serial; use std::time::Duration; /// Send SIGHUP to the current process. fn send_sighup_to_self() { use nix::sys::signal::{Signal, kill}; use nix::unistd::Pid; kill(Pid::this(), Signal::SIGHUP).expect("failed to send SIGHUP"); } /// Install the SIGHUP handler and wait for it to be registered. async fn install_sighup_handler(server: &TestServer) { witryna::test_support::setup_sighup_handler(&server.state); // Yield to allow the spawned handler task to register the signal listener tokio::task::yield_now().await; tokio::time::sleep(std::time::Duration::from_millis(50)).await; } #[tokio::test] #[serial] async fn sighup_reload_keeps_server_healthy() { let dir = tempfile::tempdir().unwrap().keep(); let site = SiteBuilder::new("my-site", "https://example.com/repo.git", "test-token").build(); let server = TestServer::start(test_config_with_site(dir, site)).await; install_sighup_handler(&server).await; // Verify server is healthy before SIGHUP let resp = TestServer::client() .get(server.url("/health")) .send() .await .unwrap(); assert_eq!(resp.status().as_u16(), 200); // Send SIGHUP (reload config) send_sighup_to_self(); // Give the handler time to process tokio::time::sleep(std::time::Duration::from_millis(500)).await; // Server should still be healthy let resp = TestServer::client() .get(server.url("/health")) .send() .await .unwrap(); assert_eq!(resp.status().as_u16(), 200); } #[tokio::test] #[serial] async fn rapid_sighup_does_not_crash() { let dir = tempfile::tempdir().unwrap().keep(); let site = SiteBuilder::new("my-site", "https://example.com/repo.git", "test-token").build(); let server = TestServer::start(test_config_with_site(dir, site)).await; install_sighup_handler(&server).await; // Send multiple SIGHUPs in quick succession for _ in 0..3 { send_sighup_to_self(); tokio::time::sleep(std::time::Duration::from_millis(50)).await; } // Wait for stabilization tokio::time::sleep(std::time::Duration::from_millis(500)).await; // Server should survive let resp = TestServer::client() .get(server.url("/health")) .send() .await .unwrap(); assert_eq!(resp.status().as_u16(), 200); } #[tokio::test] #[serial] async fn sighup_preserves_listen_address() { let dir = tempfile::tempdir().unwrap().keep(); let site = SiteBuilder::new("my-site", "https://example.com/repo.git", "test-token").build(); let server = TestServer::start(test_config_with_site(dir, site)).await; install_sighup_handler(&server).await; // Verify server is healthy before SIGHUP let resp = TestServer::client() .get(server.url("/health")) .send() .await .unwrap(); assert_eq!(resp.status().as_u16(), 200); // Rewrite the on-disk config with a different listen_address (unreachable port) // and an additional site to verify reloadable fields are updated let config_path = server.state.config_path.as_ref(); let new_toml = format!( r#"listen_address = "127.0.0.1:19999" container_runtime = "podman" base_dir = "{}" log_dir = "{}" log_level = "debug" [[sites]] name = "my-site" repo_url = "https://example.com/repo.git" branch = "main" webhook_token = "test-token" [[sites]] name = "new-site" repo_url = "https://example.com/new.git" branch = "main" webhook_token = "new-token" "#, server .state .config .read() .expect("config lock poisoned") .base_dir .display(), server .state .config .read() .expect("config lock poisoned") .log_dir .display(), ); tokio::fs::write(config_path, &new_toml).await.unwrap(); // Send SIGHUP to reload send_sighup_to_self(); tokio::time::sleep(Duration::from_millis(500)).await; // Server should still respond on the original port (listen_address preserved) let resp = TestServer::client() .get(server.url("/health")) .send() .await .unwrap(); assert_eq!(resp.status().as_u16(), 200); // Verify the reloadable field (sites) was updated let (sites_len, has_new_site, listen_addr) = { let config = server.state.config.read().expect("config lock poisoned"); ( config.sites.len(), config.find_site("new-site").is_some(), config.listen_address.clone(), ) }; assert_eq!(sites_len, 2, "sites should have been reloaded"); assert!(has_new_site, "new-site should exist after reload"); // Verify non-reloadable field was preserved (not overwritten with "127.0.0.1:19999") assert_ne!( listen_addr, "127.0.0.1:19999", "listen_address should be preserved from original config" ); }