summaryrefslogtreecommitdiff
path: root/tests/integration/sighup.rs
blob: 23c0dfd5cc5ce6d9b47323f9266eb7cfe5d05f61 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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().await.base_dir.display(),
        server.state.config.read().await.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 config = server.state.config.read().await;
    assert_eq!(config.sites.len(), 2, "sites should have been reloaded");
    assert!(
        config.find_site("new-site").is_some(),
        "new-site should exist after reload"
    );

    // Verify non-reloadable field was preserved (not overwritten with "127.0.0.1:19999")
    assert_ne!(
        config.listen_address, "127.0.0.1:19999",
        "listen_address should be preserved from original config"
    );
}