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 '
hook
' > 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 'ok
' > 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");
}