diff options
Diffstat (limited to 'tests/integration/cli_switch.rs')
| -rw-r--r-- | tests/integration/cli_switch.rs | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/tests/integration/cli_switch.rs b/tests/integration/cli_switch.rs new file mode 100644 index 0000000..fcbced6 --- /dev/null +++ b/tests/integration/cli_switch.rs @@ -0,0 +1,330 @@ +use std::fmt::Write as _; +use std::process::Stdio; +use tempfile::TempDir; +use tokio::process::Command; + +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 +} + +async fn write_switch_config( + dir: &std::path::Path, + sites: &[&str], +) -> (std::path::PathBuf, 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 { + write!( + sites_toml, + r#" +[[sites]] +name = "{name}" +repo_url = "https://example.com/{name}.git" +branch = "main" +webhook_token = "unused" +"# + ) + .unwrap(); + } + + 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 = dir.join("logs").display(), + ); + tokio::fs::write(&config_path, config).await.unwrap(); + (config_path, base_dir) +} + +// --------------------------------------------------------------------------- +// Tier 1: no container runtime / git needed +// --------------------------------------------------------------------------- + +#[tokio::test] +async fn cli_switch_unknown_site_exits_nonzero() { + let tempdir = TempDir::new().unwrap(); + let (config_path, _) = write_switch_config(tempdir.path(), &["real-site"]).await; + + let output = Command::new(witryna_bin()) + .args([ + "switch", + "--config", + config_path.to_str().unwrap(), + "nonexistent", + "20260126-143000-123456", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .unwrap(); + + assert!(!output.status.success(), "should exit non-zero"); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("not found"), + "should mention 'not found', got: {stderr}" + ); +} + +#[tokio::test] +async fn cli_switch_nonexistent_build_exits_nonzero() { + let tempdir = TempDir::new().unwrap(); + let (config_path, base_dir) = write_switch_config(tempdir.path(), &["my-site"]).await; + + // Create builds dir with one existing build + let builds_dir = base_dir.join("builds").join("my-site"); + tokio::fs::create_dir_all(builds_dir.join("20260126-100000-000001")) + .await + .unwrap(); + + let output = Command::new(witryna_bin()) + .args([ + "switch", + "--config", + config_path.to_str().unwrap(), + "my-site", + "20260126-999999-999999", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .unwrap(); + + assert!(!output.status.success(), "should exit non-zero"); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("not found"), + "should mention 'not found', got: {stderr}" + ); + assert!( + stderr.contains("20260126-100000-000001"), + "should list available builds, got: {stderr}" + ); +} + +#[tokio::test] +async fn cli_switch_invalid_timestamp_format_exits_nonzero() { + let tempdir = TempDir::new().unwrap(); + let (config_path, base_dir) = write_switch_config(tempdir.path(), &["my-site"]).await; + + // Create builds dir so the "no builds dir" check doesn't fire first + let builds_dir = base_dir.join("builds").join("my-site"); + tokio::fs::create_dir_all(&builds_dir).await.unwrap(); + + let output = Command::new(witryna_bin()) + .args([ + "switch", + "--config", + config_path.to_str().unwrap(), + "my-site", + "not-a-timestamp", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .unwrap(); + + assert!(!output.status.success(), "should exit non-zero"); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("not a valid build timestamp"), + "should mention invalid timestamp, got: {stderr}" + ); +} + +#[tokio::test] +async fn cli_switch_updates_symlink() { + let tempdir = TempDir::new().unwrap(); + let (config_path, base_dir) = write_switch_config(tempdir.path(), &["my-site"]).await; + + let builds_dir = base_dir.join("builds").join("my-site"); + let build1 = builds_dir.join("20260126-100000-000001"); + let build2 = builds_dir.join("20260126-100000-000002"); + tokio::fs::create_dir_all(&build1).await.unwrap(); + tokio::fs::create_dir_all(&build2).await.unwrap(); + + // Point current at build1 + let current = builds_dir.join("current"); + tokio::fs::symlink(&build1, ¤t).await.unwrap(); + + // Switch to build2 + let output = Command::new(witryna_bin()) + .args([ + "switch", + "--config", + config_path.to_str().unwrap(), + "my-site", + "20260126-100000-000002", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .unwrap(); + + assert!( + output.status.success(), + "should exit 0, stderr: {}", + String::from_utf8_lossy(&output.stderr) + ); + + // Verify symlink now points to build2 + let target = tokio::fs::read_link(¤t).await.unwrap(); + assert_eq!(target, build2, "symlink should point to build2"); + + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("switched my-site to build 20260126-100000-000002"), + "should confirm switch, got: {stderr}" + ); +} + +#[tokio::test] +async fn cli_switch_preserves_builds() { + let tempdir = TempDir::new().unwrap(); + let (config_path, base_dir) = write_switch_config(tempdir.path(), &["my-site"]).await; + + let builds_dir = base_dir.join("builds").join("my-site"); + let build1 = builds_dir.join("20260126-100000-000001"); + let build2 = builds_dir.join("20260126-100000-000002"); + tokio::fs::create_dir_all(&build1).await.unwrap(); + tokio::fs::create_dir_all(&build2).await.unwrap(); + + // Point current at build1 + let current = builds_dir.join("current"); + tokio::fs::symlink(&build1, ¤t).await.unwrap(); + + // Switch to build2 + let output = Command::new(witryna_bin()) + .args([ + "switch", + "--config", + config_path.to_str().unwrap(), + "my-site", + "20260126-100000-000002", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .unwrap(); + + assert!(output.status.success(), "should exit 0"); + + // Both builds should still exist + assert!(build1.exists(), "build1 should still exist after switch"); + assert!(build2.exists(), "build2 should still exist after switch"); +} + +#[tokio::test] +async fn cli_switch_updates_state_json() { + let tempdir = TempDir::new().unwrap(); + let (config_path, base_dir) = write_switch_config(tempdir.path(), &["my-site"]).await; + + let builds_dir = base_dir.join("builds").join("my-site"); + let build1 = builds_dir.join("20260126-100000-000001"); + let build2 = builds_dir.join("20260126-100000-000002"); + tokio::fs::create_dir_all(&build1).await.unwrap(); + tokio::fs::create_dir_all(&build2).await.unwrap(); + + // Write initial state.json with current pointing to build1 + let state_json = serde_json::json!({ + "current": "20260126-100000-000001", + "builds": [ + { + "status": "success", + "timestamp": "20260126-100000-000002", + "started_at": "2026-01-26T10:00:00Z", + "git_commit": "bbb2222", + "duration": "20s", + "log": "/logs/2.log", + }, + { + "status": "success", + "timestamp": "20260126-100000-000001", + "started_at": "2026-01-26T10:00:00Z", + "git_commit": "aaa1111", + "duration": "10s", + "log": "/logs/1.log", + } + ], + }); + tokio::fs::write(builds_dir.join("state.json"), state_json.to_string()) + .await + .unwrap(); + + // Create symlink + let current = builds_dir.join("current"); + tokio::fs::symlink(&build1, ¤t).await.unwrap(); + + // Switch to build2 + let output = Command::new(witryna_bin()) + .args([ + "switch", + "--config", + config_path.to_str().unwrap(), + "my-site", + "20260126-100000-000002", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .unwrap(); + + assert!(output.status.success(), "should exit 0"); + + // Verify state.json was updated + let state_content = tokio::fs::read_to_string(builds_dir.join("state.json")) + .await + .unwrap(); + let state: serde_json::Value = serde_json::from_str(&state_content).unwrap(); + assert_eq!( + state["current"], "20260126-100000-000002", + "state.json current should be updated after switch" + ); +} + +#[tokio::test] +async fn cli_switch_no_builds_dir_exits_nonzero() { + let tempdir = TempDir::new().unwrap(); + let (config_path, _) = write_switch_config(tempdir.path(), &["my-site"]).await; + + // Don't create builds directory at all + + let output = Command::new(witryna_bin()) + .args([ + "switch", + "--config", + config_path.to_str().unwrap(), + "my-site", + "20260126-100000-000001", + ]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await + .unwrap(); + + assert!(!output.status.success(), "should exit non-zero"); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("no builds found"), + "should mention no builds, got: {stderr}" + ); +} |
