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
|
use crate::git_helpers::create_local_repo;
use crate::harness::{SiteBuilder, TestServer, test_config_with_site};
use crate::runtime::{skip_without_git, skip_without_runtime};
use std::time::Duration;
// ---------------------------------------------------------------------------
// Tier 2 (requires container runtime + git)
// ---------------------------------------------------------------------------
#[tokio::test]
async fn post_deploy_hook_runs_after_build() {
skip_without_git!();
skip_without_runtime!();
let tempdir = tempfile::tempdir().unwrap();
let base_dir = tempdir.path().to_path_buf();
let repo_dir = tempdir.path().join("repos");
tokio::fs::create_dir_all(&repo_dir).await.unwrap();
let repo_url = create_local_repo(&repo_dir, "main").await;
// The hook creates a "hook-ran" marker file in the build output directory
let site = SiteBuilder::new("hook-test", &repo_url, "test-token")
.overrides(
"alpine:latest",
"mkdir -p out && echo '<h1>hook</h1>' > out/index.html",
"out",
)
.post_deploy(vec!["touch".to_owned(), "hook-ran".to_owned()])
.build();
let server = TestServer::start(test_config_with_site(base_dir.clone(), site)).await;
let resp = TestServer::client()
.post(server.url("/hook-test"))
.header("Authorization", "Bearer test-token")
.send()
.await
.unwrap();
assert_eq!(resp.status().as_u16(), 202);
// Wait for build + hook to complete
let builds_dir = base_dir.join("builds/hook-test");
let max_wait = Duration::from_secs(120);
let start = std::time::Instant::now();
loop {
assert!(start.elapsed() <= max_wait, "build timed out");
if builds_dir.join("current").is_symlink() {
// Give the hook a moment to finish after symlink switch
tokio::time::sleep(Duration::from_secs(3)).await;
break;
}
tokio::time::sleep(Duration::from_millis(500)).await;
}
// Verify the hook ran — marker file should exist in the build directory
let current_target = tokio::fs::read_link(builds_dir.join("current"))
.await
.expect("current symlink should exist");
assert!(
current_target.join("hook-ran").exists(),
"hook marker file should exist in build directory"
);
}
#[tokio::test]
async fn post_deploy_hook_failure_nonfatal() {
skip_without_git!();
skip_without_runtime!();
let tempdir = tempfile::tempdir().unwrap();
let base_dir = tempdir.path().to_path_buf();
let repo_dir = tempdir.path().join("repos");
tokio::fs::create_dir_all(&repo_dir).await.unwrap();
let repo_url = create_local_repo(&repo_dir, "main").await;
// The hook will fail (exit 1), but the deploy should still succeed
let site = SiteBuilder::new("hook-fail", &repo_url, "test-token")
.overrides(
"alpine:latest",
"mkdir -p out && echo '<h1>ok</h1>' > out/index.html",
"out",
)
.post_deploy(vec!["false".to_owned()])
.build();
let server = TestServer::start(test_config_with_site(base_dir.clone(), site)).await;
let resp = TestServer::client()
.post(server.url("/hook-fail"))
.header("Authorization", "Bearer test-token")
.send()
.await
.unwrap();
assert_eq!(resp.status().as_u16(), 202);
// Wait for build to complete
let builds_dir = base_dir.join("builds/hook-fail");
let max_wait = Duration::from_secs(120);
let start = std::time::Instant::now();
loop {
assert!(start.elapsed() <= max_wait, "build timed out");
if builds_dir.join("current").is_symlink() {
tokio::time::sleep(Duration::from_secs(3)).await;
break;
}
tokio::time::sleep(Duration::from_millis(500)).await;
}
// Deploy succeeded despite hook failure
let current_target = tokio::fs::read_link(builds_dir.join("current"))
.await
.expect("current symlink should exist");
assert!(
current_target.join("index.html").exists(),
"built assets should exist despite hook failure"
);
// Hook log should have been written with failure status
let logs_dir = base_dir.join("logs/hook-fail");
let mut found_hook_log = false;
let mut entries = tokio::fs::read_dir(&logs_dir).await.unwrap();
while let Some(entry) = entries.next_entry().await.unwrap() {
let name = entry.file_name();
if name.to_string_lossy().ends_with("-hook.log") {
found_hook_log = true;
let content = tokio::fs::read_to_string(entry.path()).await.unwrap();
assert!(content.contains("=== HOOK LOG ==="));
assert!(content.contains("Status: failed"));
break;
}
}
assert!(found_hook_log, "hook log should exist for failed hook");
}
|