From 064a1d01c5c14f5ecc032fa9b8346a4a88b893f6 Mon Sep 17 00:00:00 2001 From: Dawid Rycerz Date: Thu, 22 Jan 2026 22:07:32 +0100 Subject: witryna 0.1.0 — initial release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Minimalist Git-based static site deployment orchestrator. Webhook-triggered builds in Podman/Docker containers with atomic symlink publishing, SIGHUP hot-reload, and zero-downtime deploys. See README.md for usage, CHANGELOG.md for details. --- tests/integration/cli_run.rs | 277 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 tests/integration/cli_run.rs (limited to 'tests/integration/cli_run.rs') diff --git a/tests/integration/cli_run.rs b/tests/integration/cli_run.rs new file mode 100644 index 0000000..0ea8d20 --- /dev/null +++ b/tests/integration/cli_run.rs @@ -0,0 +1,277 @@ +use crate::git_helpers::create_bare_repo; +use crate::runtime::{detect_container_runtime, skip_without_git, skip_without_runtime}; +use std::process::Stdio; +use tempfile::TempDir; +use tokio::process::Command; + +/// Build the binary path for the witryna executable. +fn witryna_bin() -> std::path::PathBuf { + // cargo test sets CARGO_BIN_EXE_witryna when the binary exists, + // but for integration tests we use the debug build path directly. + let mut path = std::path::PathBuf::from(env!("CARGO_BIN_EXE_witryna")); + if !path.exists() { + // Fallback to target/debug/witryna + path = std::path::PathBuf::from("target/debug/witryna"); + } + path +} + +/// Write a minimal witryna.toml config file. +async fn write_config( + dir: &std::path::Path, + site_name: &str, + repo_url: &str, + base_dir: &std::path::Path, + log_dir: &std::path::Path, + command: &str, + public: &str, +) -> std::path::PathBuf { + let config_path = dir.join("witryna.toml"); + let runtime = detect_container_runtime(); + let config = format!( + r#"listen_address = "127.0.0.1:0" +container_runtime = "{runtime}" +base_dir = "{base_dir}" +log_dir = "{log_dir}" +log_level = "debug" + +[[sites]] +name = "{site_name}" +repo_url = "{repo_url}" +branch = "main" +webhook_token = "unused" +image = "alpine:latest" +command = "{command}" +public = "{public}" +"#, + base_dir = base_dir.display(), + log_dir = log_dir.display(), + ); + tokio::fs::write(&config_path, config).await.unwrap(); + config_path +} + +// --------------------------------------------------------------------------- +// Tier 1: no container runtime needed +// --------------------------------------------------------------------------- + +#[tokio::test] +async fn cli_run_site_not_found_exits_nonzero() { + let tempdir = TempDir::new().unwrap(); + let base_dir = tempdir.path().join("data"); + let log_dir = tempdir.path().join("logs"); + tokio::fs::create_dir_all(&base_dir).await.unwrap(); + tokio::fs::create_dir_all(&log_dir).await.unwrap(); + + // Write config with no sites matching "nonexistent" + let config_path = tempdir.path().join("witryna.toml"); + let config = format!( + r#"listen_address = "127.0.0.1:0" +container_runtime = "podman" +base_dir = "{}" +log_dir = "{}" +log_level = "info" +sites = [] +"#, + base_dir.display(), + log_dir.display(), + ); + tokio::fs::write(&config_path, config).await.unwrap(); + + let output = Command::new(witryna_bin()) + .args([ + "--config", + config_path.to_str().unwrap(), + "run", + "nonexistent", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .unwrap(); + + assert!( + !output.status.success(), + "should exit non-zero for unknown site" + ); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("not found"), + "stderr should mention site not found, got: {stderr}" + ); +} + +#[tokio::test] +async fn cli_run_build_failure_exits_nonzero() { + skip_without_git!(); + skip_without_runtime!(); + + let tempdir = TempDir::new().unwrap(); + let base_dir = tempdir.path().join("data"); + let log_dir = tempdir.path().join("logs"); + tokio::fs::create_dir_all(&base_dir).await.unwrap(); + tokio::fs::create_dir_all(&log_dir).await.unwrap(); + + let repo_dir = tempdir.path().join("repos"); + tokio::fs::create_dir_all(&repo_dir).await.unwrap(); + let repo_url = create_bare_repo(&repo_dir, "main").await; + + let config_path = write_config( + tempdir.path(), + "fail-site", + &repo_url, + &base_dir, + &log_dir, + "exit 42", + "dist", + ) + .await; + + let output = Command::new(witryna_bin()) + .args([ + "--config", + config_path.to_str().unwrap(), + "run", + "fail-site", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .unwrap(); + + assert!( + !output.status.success(), + "should exit non-zero on build failure" + ); + + // Verify a log file was created + let logs_dir = log_dir.join("fail-site"); + if logs_dir.is_dir() { + let mut entries = tokio::fs::read_dir(&logs_dir).await.unwrap(); + let mut found_log = false; + while let Some(entry) = entries.next_entry().await.unwrap() { + if entry.file_name().to_string_lossy().ends_with(".log") { + found_log = true; + break; + } + } + assert!(found_log, "should have a .log file after failed build"); + } +} + +// --------------------------------------------------------------------------- +// Tier 2: requires git + container runtime +// --------------------------------------------------------------------------- + +#[tokio::test] +async fn cli_run_builds_site_successfully() { + skip_without_git!(); + skip_without_runtime!(); + + let tempdir = TempDir::new().unwrap(); + let base_dir = tempdir.path().join("data"); + let log_dir = tempdir.path().join("logs"); + tokio::fs::create_dir_all(&base_dir).await.unwrap(); + tokio::fs::create_dir_all(&log_dir).await.unwrap(); + + let repo_dir = tempdir.path().join("repos"); + tokio::fs::create_dir_all(&repo_dir).await.unwrap(); + let repo_url = create_bare_repo(&repo_dir, "main").await; + + let config_path = write_config( + tempdir.path(), + "test-site", + &repo_url, + &base_dir, + &log_dir, + "mkdir -p out && echo hello > out/index.html", + "out", + ) + .await; + + let output = Command::new(witryna_bin()) + .args([ + "--config", + config_path.to_str().unwrap(), + "run", + "test-site", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .unwrap(); + + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + output.status.success(), + "should exit 0 on success, stderr: {stderr}" + ); + + // Verify symlink exists + let current = base_dir.join("builds/test-site/current"); + assert!(current.is_symlink(), "current symlink should exist"); + + // Verify published content + let target = tokio::fs::read_link(¤t).await.unwrap(); + let content = tokio::fs::read_to_string(target.join("index.html")) + .await + .unwrap(); + assert!(content.contains("hello"), "published content should match"); + + // Verify log file exists + let logs_dir = log_dir.join("test-site"); + assert!(logs_dir.is_dir(), "logs directory should exist"); +} + +#[tokio::test] +async fn cli_run_verbose_shows_build_output() { + skip_without_git!(); + skip_without_runtime!(); + + let tempdir = TempDir::new().unwrap(); + let base_dir = tempdir.path().join("data"); + let log_dir = tempdir.path().join("logs"); + tokio::fs::create_dir_all(&base_dir).await.unwrap(); + tokio::fs::create_dir_all(&log_dir).await.unwrap(); + + let repo_dir = tempdir.path().join("repos"); + tokio::fs::create_dir_all(&repo_dir).await.unwrap(); + let repo_url = create_bare_repo(&repo_dir, "main").await; + + let config_path = write_config( + tempdir.path(), + "verbose-site", + &repo_url, + &base_dir, + &log_dir, + "echo VERBOSE_MARKER && mkdir -p out && echo ok > out/index.html", + "out", + ) + .await; + + let output = Command::new(witryna_bin()) + .args([ + "--config", + config_path.to_str().unwrap(), + "run", + "verbose-site", + "--verbose", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .unwrap(); + + let stderr = String::from_utf8_lossy(&output.stderr); + assert!(output.status.success(), "should exit 0, stderr: {stderr}"); + + // In verbose mode, build output should appear in stderr + assert!( + stderr.contains("VERBOSE_MARKER"), + "stderr should contain build output in verbose mode, got: {stderr}" + ); +} -- cgit v1.2.3