use rstest::{fixture, rstest};
use tempfile::TempDir;
mod common;
use common::run_rivets_in_dir;
#[fixture]
fn temp_dir() -> TempDir {
TempDir::new().expect("Failed to create temp directory")
}
#[rstest]
fn test_init_creates_rivets_directory(temp_dir: TempDir) {
let output = run_rivets_in_dir(temp_dir.path(), &["init", "--quiet"]);
assert!(output.status.success(), "Init command should succeed");
let rivets_dir = temp_dir.path().join(".rivets");
assert!(rivets_dir.exists(), ".rivets directory should exist");
assert!(rivets_dir.is_dir(), ".rivets should be a directory");
}
#[rstest]
fn test_init_creates_config_file(temp_dir: TempDir) {
let output = run_rivets_in_dir(temp_dir.path(), &["init", "--quiet"]);
assert!(output.status.success());
let config_path = temp_dir.path().join(".rivets/config.yaml");
assert!(config_path.exists(), "config.yaml should exist");
let content = std::fs::read_to_string(&config_path).unwrap();
assert!(
content.contains("issue-prefix:"),
"Config should contain issue-prefix"
);
assert!(
content.contains("backend: jsonl"),
"Config should specify jsonl backend"
);
assert!(
content.contains("data_file:"),
"Config should specify data_file"
);
}
#[rstest]
fn test_init_creates_issues_file(temp_dir: TempDir) {
let output = run_rivets_in_dir(temp_dir.path(), &["init", "--quiet"]);
assert!(output.status.success());
let issues_path = temp_dir.path().join(".rivets/issues.jsonl");
assert!(issues_path.exists(), "issues.jsonl should exist");
let content = std::fs::read_to_string(&issues_path).unwrap();
assert!(content.is_empty(), "issues.jsonl should be empty initially");
}
#[rstest]
fn test_init_creates_gitignore(temp_dir: TempDir) {
let output = run_rivets_in_dir(temp_dir.path(), &["init", "--quiet"]);
assert!(output.status.success());
let gitignore_path = temp_dir.path().join(".rivets/.gitignore");
assert!(gitignore_path.exists(), ".gitignore should exist");
}
#[rstest]
fn test_init_with_custom_prefix(temp_dir: TempDir) {
let output = run_rivets_in_dir(temp_dir.path(), &["init", "--prefix", "myproj", "--quiet"]);
assert!(output.status.success());
let config_path = temp_dir.path().join(".rivets/config.yaml");
let content = std::fs::read_to_string(&config_path).unwrap();
assert!(
content.contains("issue-prefix: myproj"),
"Config should contain custom prefix 'myproj'"
);
}
#[rstest]
fn test_init_with_default_prefix(temp_dir: TempDir) {
let output = run_rivets_in_dir(temp_dir.path(), &["init", "--quiet"]);
assert!(output.status.success());
let config_path = temp_dir.path().join(".rivets/config.yaml");
let content = std::fs::read_to_string(&config_path).unwrap();
assert!(
content.contains("issue-prefix: proj"),
"Config should contain default prefix 'proj'"
);
}
#[rstest]
fn test_init_fails_if_already_initialized(temp_dir: TempDir) {
let output1 = run_rivets_in_dir(temp_dir.path(), &["init", "--quiet"]);
assert!(output1.status.success(), "First init should succeed");
let output2 = run_rivets_in_dir(temp_dir.path(), &["init", "--quiet"]);
assert!(
!output2.status.success(),
"Second init should fail because already initialized"
);
let stderr = String::from_utf8_lossy(&output2.stderr);
assert!(
stderr.to_lowercase().contains("already initialized")
|| stderr.to_lowercase().contains("already")
|| stderr.to_lowercase().contains("exists"),
"Error message should indicate already initialized. Got: {}",
stderr
);
}
#[rstest]
fn test_init_fails_with_invalid_prefix_too_short(temp_dir: TempDir) {
let output = run_rivets_in_dir(temp_dir.path(), &["init", "--prefix", "a"]);
assert!(
!output.status.success(),
"Init should fail with prefix too short"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.to_lowercase().contains("at least 2")
|| stderr.to_lowercase().contains("characters"),
"Error should mention minimum characters. Got: {}",
stderr
);
}
#[rstest]
fn test_init_fails_with_invalid_prefix_special_chars(temp_dir: TempDir) {
let output = run_rivets_in_dir(temp_dir.path(), &["init", "--prefix", "proj-test"]);
assert!(
!output.status.success(),
"Init should fail with hyphen in prefix"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.to_lowercase().contains("alphanumeric") || stderr.to_lowercase().contains("invalid"),
"Error should mention alphanumeric requirement. Got: {}",
stderr
);
}
#[rstest]
fn test_init_output_without_quiet_flag(temp_dir: TempDir) {
let output = run_rivets_in_dir(temp_dir.path(), &["init"]);
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("Initializing") || stdout.contains("rivets"),
"Should show initialization message. Got: {}",
stdout
);
assert!(
stdout.contains(".rivets") || stdout.contains("Initialized"),
"Should mention .rivets directory. Got: {}",
stdout
);
}
#[rstest]
fn test_init_quiet_flag_suppresses_output(temp_dir: TempDir) {
let output = run_rivets_in_dir(temp_dir.path(), &["init", "-q"]);
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.is_empty(),
"Quiet mode should suppress output. Got: {}",
stdout
);
assert!(temp_dir.path().join(".rivets").exists());
}
#[rstest]
fn test_init_with_long_quiet_flag(temp_dir: TempDir) {
let output = run_rivets_in_dir(temp_dir.path(), &["init", "--quiet"]);
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.is_empty(), "Long quiet flag should also work");
}
#[rstest]
fn test_init_complete_directory_structure(temp_dir: TempDir) {
let output = run_rivets_in_dir(temp_dir.path(), &["init", "--prefix", "test", "--quiet"]);
assert!(output.status.success());
let rivets_dir = temp_dir.path().join(".rivets");
assert!(rivets_dir.exists(), ".rivets/ should exist");
assert!(
rivets_dir.join("config.yaml").exists(),
"config.yaml should exist"
);
assert!(
rivets_dir.join("issues.jsonl").exists(),
"issues.jsonl should exist"
);
assert!(
rivets_dir.join(".gitignore").exists(),
".gitignore should exist"
);
let entries: Vec<_> = std::fs::read_dir(&rivets_dir)
.unwrap()
.filter_map(|e| e.ok())
.collect();
assert_eq!(
entries.len(),
3,
"Should have exactly 3 files: config.yaml, issues.jsonl, .gitignore. Found: {:?}",
entries.iter().map(|e| e.file_name()).collect::<Vec<_>>()
);
}
#[rstest]
fn test_init_prefix_validation_boundary_2_chars(temp_dir: TempDir) {
let output = run_rivets_in_dir(temp_dir.path(), &["init", "--prefix", "ab", "--quiet"]);
assert!(output.status.success(), "2-char prefix should be valid");
let config_path = temp_dir.path().join(".rivets/config.yaml");
let content = std::fs::read_to_string(&config_path).unwrap();
assert!(content.contains("issue-prefix: ab"));
}
#[rstest]
fn test_init_prefix_validation_boundary_20_chars(temp_dir: TempDir) {
let prefix = "a1b2c3d4e5f6g7h8i9j0"; let output = run_rivets_in_dir(temp_dir.path(), &["init", "--prefix", prefix, "--quiet"]);
assert!(output.status.success(), "20-char prefix should be valid");
let config_path = temp_dir.path().join(".rivets/config.yaml");
let content = std::fs::read_to_string(&config_path).unwrap();
assert!(content.contains(&format!("issue-prefix: {}", prefix)));
}