summaryrefslogtreecommitdiff
path: root/tests/integration/cli_status.rs
diff options
context:
space:
mode:
Diffstat (limited to 'tests/integration/cli_status.rs')
-rw-r--r--tests/integration/cli_status.rs313
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}"
+ );
+}