mod common;
use assert_cmd::cargo::cargo_bin_cmd;
use std::fs;
use std::path::PathBuf;
use std::process::Command;
use tempfile::TempDir;
fn make_spike(id: &str, page: &str, rating: Option<&str>, resolved: bool) -> String {
let rating_json = match rating {
Some(r) => format!("\"{}\"", r),
None => "null".to_string(),
};
let resolved_json = if resolved {
r#""resolved": true, "resolvedAt": "2024-01-02T00:00:00Z""#
} else {
r#""resolved": false"#
};
format!(
r#"{{"id":"{}","type":"page","projectKey":"test","page":"{}","url":"http://test/{}","reviewer":{{"id":"r1","name":"Test"}},"rating":{},"comments":"Test comment","timestamp":"2024-01-01T00:00:00Z",{}}}"#,
id, page, page, rating_json, resolved_json
)
}
fn setup_test_project() -> TempDir {
let temp_dir = tempfile::tempdir().unwrap();
fs::create_dir_all(temp_dir.path().join(".spikes")).unwrap();
fs::write(
temp_dir.path().join(".spikes/config.toml"),
"project_key = \"test\"\n",
)
.unwrap();
fs::write(temp_dir.path().join(".spikes/feedback.jsonl"), "").unwrap();
temp_dir
}
fn write_spikes(dir: &TempDir, spikes: &[String]) {
let content = spikes.join("\n");
fs::write(dir.path().join(".spikes/feedback.jsonl"), content).unwrap();
}
fn check_script_path() -> PathBuf {
let mut path = std::env::current_dir().unwrap();
path.pop(); path.push("action");
path.push("check.sh");
path
}
fn spikes_binary_path() -> PathBuf {
cargo_bin_cmd!("spikes")
.arg("--help")
.assert()
.success();
let _output = cargo_bin_cmd!("spikes")
.arg("version")
.assert()
.get_output()
.to_owned();
let mut path = std::env::current_exe().unwrap();
path.pop(); path.pop(); path.push("spikes");
if !path.exists() {
path.pop();
path.push("release");
path.push("spikes");
}
path
}
#[test]
fn test_gate_passes_with_positive_only() {
let temp_dir = setup_test_project();
write_spikes(
&temp_dir,
&[
make_spike("s1", "/index.html", Some("love"), false),
make_spike("s2", "/about.html", Some("like"), false),
],
);
let spikes_bin = spikes_binary_path();
let output = Command::new(check_script_path())
.args(["0", "", "false"])
.env("SPIKES_BIN", &spikes_bin)
.current_dir(temp_dir.path())
.output()
.expect("Failed to run check.sh");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"Should pass with positive only. stdout: {}, stderr: {}",
stdout,
stderr
);
assert!(stdout.contains("CLEAN SLATE") || stdout.contains("No blocking"));
}
#[test]
fn test_gate_fails_with_negative_spikes() {
let temp_dir = setup_test_project();
write_spikes(
&temp_dir,
&[
make_spike("s1", "/index.html", Some("no"), false),
make_spike("s2", "/about.html", Some("meh"), false),
],
);
let spikes_bin = spikes_binary_path();
let output = Command::new(check_script_path())
.args(["0", "", "false"])
.env("SPIKES_BIN", &spikes_bin)
.current_dir(temp_dir.path())
.output()
.expect("Failed to run check.sh");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!output.status.success(),
"Should fail with negative spikes. stdout: {}, stderr: {}",
stdout,
stderr
);
assert!(stdout.contains("BLOCKING FEEDBACK") || stderr.contains("VIBES ARE OFF"));
}
#[test]
fn test_gate_passes_within_threshold() {
let temp_dir = setup_test_project();
write_spikes(
&temp_dir,
&[
make_spike("s1", "/index.html", Some("no"), false),
make_spike("s2", "/about.html", Some("meh"), false),
],
);
let spikes_bin = spikes_binary_path();
let output = Command::new(check_script_path())
.args(["2", "", "false"])
.env("SPIKES_BIN", &spikes_bin)
.current_dir(temp_dir.path())
.output()
.expect("Failed to run check.sh");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"Should pass within threshold. stdout: {}",
stdout
);
assert!(stdout.contains("PASSED") || stdout.contains("acceptable"));
}
#[test]
fn test_gate_ignores_matching_paths() {
let temp_dir = setup_test_project();
write_spikes(
&temp_dir,
&[
make_spike("s1", "/docs/index.html", Some("no"), false),
make_spike("s2", "/about.html", Some("no"), false),
],
);
let spikes_bin = spikes_binary_path();
let ignore_paths = "/docs/**";
let output = Command::new(check_script_path())
.args(["0", ignore_paths, "false"])
.env("SPIKES_BIN", &spikes_bin)
.current_dir(temp_dir.path())
.output()
.expect("Failed to run check.sh");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
!output.status.success(),
"Should fail - /about.html not ignored. stdout: {}",
stdout
);
}
#[test]
fn test_gate_ignores_all_negative_with_wildcard() {
let temp_dir = setup_test_project();
write_spikes(
&temp_dir,
&[
make_spike("s1", "/docs/index.html", Some("no"), false),
make_spike("s2", "/docs/api.html", Some("meh"), false),
],
);
let spikes_bin = spikes_binary_path();
let ignore_paths = "/docs/**";
let output = Command::new(check_script_path())
.args(["0", ignore_paths, "false"])
.env("SPIKES_BIN", &spikes_bin)
.current_dir(temp_dir.path())
.output()
.expect("Failed to run check.sh");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"Should pass - all spikes ignored. stdout: {}",
stdout
);
}
#[test]
fn test_require_resolution_fails_on_unresolved_positive() {
let temp_dir = setup_test_project();
write_spikes(
&temp_dir,
&[
make_spike("s1", "/index.html", Some("love"), false),
make_spike("s2", "/about.html", Some("like"), false),
],
);
let spikes_bin = spikes_binary_path();
let output = Command::new(check_script_path())
.args(["0", "", "true"])
.env("SPIKES_BIN", &spikes_bin)
.current_dir(temp_dir.path())
.output()
.expect("Failed to run check.sh");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
!output.status.success(),
"Should fail with unresolved positive spikes. stdout: {}",
stdout
);
}
#[test]
fn test_require_resolution_passes_with_resolved() {
let temp_dir = setup_test_project();
write_spikes(
&temp_dir,
&[
make_spike("s1", "/index.html", Some("no"), true),
make_spike("s2", "/about.html", Some("meh"), true),
],
);
let spikes_bin = spikes_binary_path();
let output = Command::new(check_script_path())
.args(["0", "", "true"])
.env("SPIKES_BIN", &spikes_bin)
.current_dir(temp_dir.path())
.output()
.expect("Failed to run check.sh");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"Should pass with all resolved. stdout: {}",
stdout
);
}
#[test]
fn test_gate_passes_without_spikes_dir() {
let temp_dir = tempfile::tempdir().unwrap();
let spikes_bin = spikes_binary_path();
let output = Command::new(check_script_path())
.args(["0", "", "false"])
.env("SPIKES_BIN", &spikes_bin)
.current_dir(temp_dir.path())
.output()
.expect("Failed to run check.sh");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"Should pass without .spikes/ dir. stdout: {}, stderr: {}",
stdout,
stderr
);
assert!(
stdout.contains("Clean slate") || stderr.contains("Clean slate") || stdout.contains("warning"),
"Should show warning"
);
}
#[test]
fn test_gate_passes_with_empty_feedback() {
let temp_dir = setup_test_project();
let spikes_bin = spikes_binary_path();
let output = Command::new(check_script_path())
.args(["0", "", "false"])
.env("SPIKES_BIN", &spikes_bin)
.current_dir(temp_dir.path())
.output()
.expect("Failed to run check.sh");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"Should pass with empty feedback. stdout: {}",
stdout
);
}
#[test]
fn test_resolved_negative_not_blocking() {
let temp_dir = setup_test_project();
write_spikes(
&temp_dir,
&[
make_spike("s1", "/index.html", Some("no"), true),
make_spike("s2", "/about.html", Some("meh"), true),
],
);
let spikes_bin = spikes_binary_path();
let output = Command::new(check_script_path())
.args(["0", "", "false"])
.env("SPIKES_BIN", &spikes_bin)
.current_dir(temp_dir.path())
.output()
.expect("Failed to run check.sh");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"Should pass with resolved negative. stdout: {}",
stdout
);
}
#[test]
fn test_mixed_resolved_unresolved() {
let temp_dir = setup_test_project();
write_spikes(
&temp_dir,
&[
make_spike("s1", "/index.html", Some("no"), false), make_spike("s2", "/about.html", Some("meh"), true), make_spike("s3", "/contact.html", Some("no"), false), ],
);
let spikes_bin = spikes_binary_path();
let output = Command::new(check_script_path())
.args(["1", "", "false"])
.env("SPIKES_BIN", &spikes_bin)
.current_dir(temp_dir.path())
.output()
.expect("Failed to run check.sh");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
!output.status.success(),
"Should fail with 2 unresolved. stdout: {}",
stdout
);
}
#[test]
fn test_no_rating_not_blocking() {
let temp_dir = setup_test_project();
write_spikes(&temp_dir, &[make_spike("s1", "/index.html", None, false)]);
let spikes_bin = spikes_binary_path();
let output = Command::new(check_script_path())
.args(["0", "", "false"])
.env("SPIKES_BIN", &spikes_bin)
.current_dir(temp_dir.path())
.output()
.expect("Failed to run check.sh");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"Should pass with no rating. stdout: {}",
stdout
);
}