diff options
| author | Dawid Rycerz <dawid@rycerz.xyz> | 2026-02-15 21:27:00 +0100 |
|---|---|---|
| committer | Dawid Rycerz <dawid@rycerz.xyz> | 2026-02-15 21:27:00 +0100 |
| commit | ce0dbf6b249956700c6a1705bf4ad85a09d53e8c (patch) | |
| tree | d7c3236807cfbf75d7f3a355eb5df5a5e2cc4ad7 /tests/integration/hooks.rs | |
| parent | 064a1d01c5c14f5ecc032fa9b8346a4a88b893f6 (diff) | |
Switch, cleanup, and status CLI commands. Persistent build state via
state.json. Post-deploy hooks on success and failure with
WITRYNA_BUILD_STATUS. Dependency diet (axum→tiny_http, clap→argh,
tracing→log). Drop built-in rate limiting. Nix flake with NixOS module.
Arch Linux PKGBUILD. Centralized version management.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'tests/integration/hooks.rs')
| -rw-r--r-- | tests/integration/hooks.rs | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/tests/integration/hooks.rs b/tests/integration/hooks.rs index 86684cc..d8b4fa3 100644 --- a/tests/integration/hooks.rs +++ b/tests/integration/hooks.rs @@ -1,6 +1,7 @@ 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::path::Path; use std::time::Duration; // --------------------------------------------------------------------------- @@ -135,3 +136,150 @@ async fn post_deploy_hook_failure_nonfatal() { } assert!(found_hook_log, "hook log should exist for failed hook"); } + +#[tokio::test] +async fn post_deploy_hook_runs_on_build_failure() { + 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; + + // Build command fails (exit 1); hook writes WITRYNA_BUILD_STATUS to a file in clone dir + let site = SiteBuilder::new("hook-on-fail", &repo_url, "test-token") + .overrides("alpine:latest", "exit 1", "out") + .post_deploy(vec![ + "sh".to_owned(), + "-c".to_owned(), + "echo \"$WITRYNA_BUILD_STATUS\" > hook-status.txt".to_owned(), + ]) + .build(); + + let server = TestServer::start(test_config_with_site(base_dir.clone(), site)).await; + + let resp = TestServer::client() + .post(server.url("/hook-on-fail")) + .header("Authorization", "Bearer test-token") + .send() + .await + .unwrap(); + assert_eq!(resp.status().as_u16(), 202); + + // Wait for state.json to show "failed" (no current symlink on build failure) + let state_path = base_dir.join("builds/hook-on-fail/state.json"); + let max_wait = Duration::from_secs(120); + let start = std::time::Instant::now(); + + loop { + assert!(start.elapsed() <= max_wait, "build timed out"); + if state_path.exists() { + let content = tokio::fs::read_to_string(&state_path) + .await + .unwrap_or_default(); + if content.contains("\"failed\"") { + // Give the hook a moment to finish writing + tokio::time::sleep(Duration::from_secs(2)).await; + break; + } + } + tokio::time::sleep(Duration::from_millis(500)).await; + } + + // Verify hook ran and received build_status=failed + let clone_dir = base_dir.join("clones/hook-on-fail"); + let hook_status_path = clone_dir.join("hook-status.txt"); + assert!( + hook_status_path.exists(), + "hook should have created hook-status.txt in clone dir" + ); + let status = tokio::fs::read_to_string(&hook_status_path).await.unwrap(); + assert_eq!( + status.trim(), + "failed", + "hook should receive build_status=failed" + ); + + // Verify state.json says "failed" (not "hook failed") + let state_content = tokio::fs::read_to_string(&state_path).await.unwrap(); + assert!( + state_content.contains("\"failed\""), + "state.json should show failed status" + ); + + // No current symlink should exist (build failed) + assert!( + !Path::new(&base_dir.join("builds/hook-on-fail/current")).is_symlink(), + "current symlink should not exist on build failure" + ); +} + +#[tokio::test] +async fn post_deploy_hook_receives_success_status() { + 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; + + // Successful build; hook writes WITRYNA_BUILD_STATUS to build dir + let site = SiteBuilder::new("hook-success-status", &repo_url, "test-token") + .overrides( + "alpine:latest", + "mkdir -p out && echo test > out/index.html", + "out", + ) + .post_deploy(vec![ + "sh".to_owned(), + "-c".to_owned(), + "echo \"$WITRYNA_BUILD_STATUS\" > \"$WITRYNA_BUILD_DIR/build-status.txt\"".to_owned(), + ]) + .build(); + + let server = TestServer::start(test_config_with_site(base_dir.clone(), site)).await; + + let resp = TestServer::client() + .post(server.url("/hook-success-status")) + .header("Authorization", "Bearer test-token") + .send() + .await + .unwrap(); + assert_eq!(resp.status().as_u16(), 202); + + // Wait for current symlink + let builds_dir = base_dir.join("builds/hook-success-status"); + 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; + } + + // Read build-status.txt from build dir + let current_target = tokio::fs::read_link(builds_dir.join("current")) + .await + .expect("current symlink should exist"); + let status_path = current_target.join("build-status.txt"); + assert!( + status_path.exists(), + "hook should have created build-status.txt" + ); + let status = tokio::fs::read_to_string(&status_path).await.unwrap(); + assert_eq!( + status.trim(), + "success", + "hook should receive build_status=success" + ); +} |
