diff options
Diffstat (limited to 'tests/integration/cli_status.rs')
| -rw-r--r-- | tests/integration/cli_status.rs | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/tests/integration/cli_status.rs b/tests/integration/cli_status.rs new file mode 100644 index 0000000..25135fb --- /dev/null +++ b/tests/integration/cli_status.rs @@ -0,0 +1,313 @@ +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 { + let mut path = std::path::PathBuf::from(env!("CARGO_BIN_EXE_witryna")); + if !path.exists() { + path = std::path::PathBuf::from("target/debug/witryna"); + } + path +} + +/// Write a minimal witryna.toml config for status tests. +async fn write_status_config( + dir: &std::path::Path, + sites: &[&str], + log_dir: &std::path::Path, +) -> std::path::PathBuf { + let base_dir = dir.join("data"); + tokio::fs::create_dir_all(&base_dir).await.unwrap(); + + let mut sites_toml = String::new(); + for name in sites { + sites_toml.push_str(&format!( + r#" +[[sites]] +name = "{name}" +repo_url = "https://example.com/{name}.git" +branch = "main" +webhook_token = "unused" +"# + )); + } + + let config_path = dir.join("witryna.toml"); + let config = format!( + r#"listen_address = "127.0.0.1:0" +container_runtime = "podman" +base_dir = "{base_dir}" +log_dir = "{log_dir}" +log_level = "info" +{sites_toml}"#, + base_dir = base_dir.display(), + log_dir = log_dir.display(), + ); + tokio::fs::write(&config_path, config).await.unwrap(); + config_path +} + +/// Write a fake build log with a valid header. +async fn write_test_build_log( + log_dir: &std::path::Path, + site_name: &str, + timestamp: &str, + status: &str, + commit: &str, + image: &str, + duration: &str, +) { + let site_log_dir = log_dir.join(site_name); + tokio::fs::create_dir_all(&site_log_dir).await.unwrap(); + + let content = format!( + "=== BUILD LOG ===\n\ + Site: {site_name}\n\ + Timestamp: {timestamp}\n\ + Git Commit: {commit}\n\ + Image: {image}\n\ + Duration: {duration}\n\ + Status: {status}\n\ + \n\ + === STDOUT ===\n\ + build output\n\ + \n\ + === STDERR ===\n" + ); + + let log_file = site_log_dir.join(format!("{timestamp}.log")); + tokio::fs::write(&log_file, content).await.unwrap(); +} + +/// Write a fake hook log with a valid header. +async fn write_test_hook_log( + log_dir: &std::path::Path, + site_name: &str, + timestamp: &str, + status: &str, +) { + let site_log_dir = log_dir.join(site_name); + tokio::fs::create_dir_all(&site_log_dir).await.unwrap(); + + let content = format!( + "=== HOOK LOG ===\n\ + Site: {site_name}\n\ + Timestamp: {timestamp}\n\ + Command: hook-cmd\n\ + Duration: 1s\n\ + Status: {status}\n\ + \n\ + === STDOUT ===\n\ + \n\ + === STDERR ===\n" + ); + + let log_file = site_log_dir.join(format!("{timestamp}-hook.log")); + tokio::fs::write(&log_file, content).await.unwrap(); +} + +// --------------------------------------------------------------------------- +// Tier 1: no container runtime / git needed +// --------------------------------------------------------------------------- + +#[tokio::test] +async fn cli_status_no_builds() { + let tempdir = TempDir::new().unwrap(); + let log_dir = tempdir.path().join("logs"); + tokio::fs::create_dir_all(&log_dir).await.unwrap(); + + let config_path = write_status_config(tempdir.path(), &["empty-site"], &log_dir).await; + + let output = Command::new(witryna_bin()) + .args(["--config", config_path.to_str().unwrap(), "status"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .unwrap(); + + assert!(output.status.success(), "should exit 0"); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!(stdout.contains("SITE"), "should have table header"); + assert!( + stdout.contains("(no builds)"), + "should show (no builds), got: {stdout}" + ); +} + +#[tokio::test] +async fn cli_status_single_build() { + let tempdir = TempDir::new().unwrap(); + let log_dir = tempdir.path().join("logs"); + tokio::fs::create_dir_all(&log_dir).await.unwrap(); + + write_test_build_log( + &log_dir, + "my-site", + "20260126-143000-123456", + "success", + "abc123d", + "node:20-alpine", + "45s", + ) + .await; + + let config_path = write_status_config(tempdir.path(), &["my-site"], &log_dir).await; + + let output = Command::new(witryna_bin()) + .args(["--config", config_path.to_str().unwrap(), "status"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .unwrap(); + + assert!(output.status.success(), "should exit 0"); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!(stdout.contains("my-site"), "should show site name"); + assert!(stdout.contains("success"), "should show status"); + assert!(stdout.contains("abc123d"), "should show commit"); + assert!(stdout.contains("45s"), "should show duration"); +} + +#[tokio::test] +async fn cli_status_json_output() { + let tempdir = TempDir::new().unwrap(); + let log_dir = tempdir.path().join("logs"); + tokio::fs::create_dir_all(&log_dir).await.unwrap(); + + write_test_build_log( + &log_dir, + "json-site", + "20260126-143000-123456", + "success", + "abc123d", + "node:20-alpine", + "45s", + ) + .await; + + let config_path = write_status_config(tempdir.path(), &["json-site"], &log_dir).await; + + let output = Command::new(witryna_bin()) + .args([ + "--config", + config_path.to_str().unwrap(), + "status", + "--json", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .unwrap(); + + assert!(output.status.success(), "should exit 0"); + let stdout = String::from_utf8_lossy(&output.stdout); + let parsed: serde_json::Value = serde_json::from_str(&stdout).unwrap(); + let arr = parsed.as_array().unwrap(); + assert_eq!(arr.len(), 1); + assert_eq!(arr[0]["site_name"], "json-site"); + assert_eq!(arr[0]["status"], "success"); + assert_eq!(arr[0]["git_commit"], "abc123d"); + assert_eq!(arr[0]["duration"], "45s"); +} + +#[tokio::test] +async fn cli_status_site_filter() { + let tempdir = TempDir::new().unwrap(); + let log_dir = tempdir.path().join("logs"); + tokio::fs::create_dir_all(&log_dir).await.unwrap(); + + // Create logs for two sites + write_test_build_log( + &log_dir, + "site-a", + "20260126-143000-000000", + "success", + "aaa1111", + "alpine:latest", + "10s", + ) + .await; + + write_test_build_log( + &log_dir, + "site-b", + "20260126-150000-000000", + "success", + "bbb2222", + "alpine:latest", + "20s", + ) + .await; + + let config_path = write_status_config(tempdir.path(), &["site-a", "site-b"], &log_dir).await; + + let output = Command::new(witryna_bin()) + .args([ + "--config", + config_path.to_str().unwrap(), + "status", + "--site", + "site-a", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .unwrap(); + + assert!(output.status.success(), "should exit 0"); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!(stdout.contains("site-a"), "should show filtered site"); + assert!( + !stdout.contains("site-b"), + "should NOT show other site, got: {stdout}" + ); +} + +#[tokio::test] +async fn cli_status_hook_failed() { + let tempdir = TempDir::new().unwrap(); + let log_dir = tempdir.path().join("logs"); + tokio::fs::create_dir_all(&log_dir).await.unwrap(); + + // Build succeeded, but hook failed + write_test_build_log( + &log_dir, + "hook-site", + "20260126-143000-123456", + "success", + "abc123d", + "alpine:latest", + "12s", + ) + .await; + + write_test_hook_log( + &log_dir, + "hook-site", + "20260126-143000-123456", + "failed (exit code 1)", + ) + .await; + + let config_path = write_status_config(tempdir.path(), &["hook-site"], &log_dir).await; + + let output = Command::new(witryna_bin()) + .args(["--config", config_path.to_str().unwrap(), "status"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .unwrap(); + + assert!(output.status.success(), "should exit 0"); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!( + stdout.contains("hook failed"), + "should show 'hook failed', got: {stdout}" + ); +} |
