use serde_json::Value;
use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
fn repopilot_bin() -> &'static str {
env!("CARGO_BIN_EXE_repopilot")
}
fn create_demo_project() -> tempfile::TempDir {
let temp = tempfile::tempdir().expect("failed to create temp dir");
let root = temp.path();
fs::create_dir_all(root.join("src")).expect("failed to create src dir");
fs::write(
root.join("package.json"),
r#"{
"name": "demo-repopilot-project",
"private": true,
"dependencies": {
"react": "18.2.0"
},
"devDependencies": {
"typescript": "5.0.0"
}
}
"#,
)
.expect("failed to write package.json");
fs::write(
root.join("src").join("index.ts"),
r#"
export function main(input: string): string {
if (input.length > 10) {
return input.toUpperCase();
}
return input.trim();
}
"#,
)
.expect("failed to write index.ts");
fs::write(
root.join("src").join("config.ts"),
r#"
// Intentional fake token for static-analysis smoke coverage.
export const API_TOKEN = "sk_live_fake_repopilot_test_token_1234567890";
"#,
)
.expect("failed to write config.ts");
temp
}
fn run_ok<I, S>(cwd: &Path, args: I) -> Output
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
let output = Command::new(repopilot_bin())
.current_dir(cwd)
.args(args)
.output()
.expect("failed to run repopilot");
assert!(
output.status.success(),
"command failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}",
output.status.code(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
output
}
fn read_non_empty(path: &Path) -> String {
let content = fs::read_to_string(path)
.unwrap_or_else(|error| panic!("failed to read {}: {error}", path.display()));
assert!(
!content.trim().is_empty(),
"{} should not be empty",
path.display()
);
content
}
#[test]
fn cli_version_matches_package_version() {
let temp = tempfile::tempdir().expect("failed to create temp dir");
let output = run_ok(temp.path(), ["--version"]);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains(env!("CARGO_PKG_VERSION")),
"version output should contain package version {}\nstdout:\n{}",
env!("CARGO_PKG_VERSION"),
stdout
);
}
#[test]
fn scan_writes_valid_json_report() {
let project = create_demo_project();
let output_path = project.path().join("scan.json");
run_ok(
project.path(),
[
"scan",
".",
"--format",
"json",
"--output",
output_path.to_str().expect("non-utf8 output path"),
],
);
let content = read_non_empty(&output_path);
let json: Value = serde_json::from_str(&content).expect("scan output should be valid JSON");
assert_eq!(json["files_count"].as_u64().unwrap_or_default(), 3);
assert!(
json["findings"].is_array(),
"scan JSON should include findings array"
);
}
#[test]
fn vibe_writes_llm_ready_markdown() {
let project = create_demo_project();
let output_path = project.path().join("vibe.md");
run_ok(
project.path(),
[
"vibe",
".",
"--focus",
"security",
"--budget",
"2k",
"--output",
output_path.to_str().expect("non-utf8 output path"),
],
);
let content = read_non_empty(&output_path);
assert!(
content.contains("RepoPilot") || content.contains("Vibe"),
"vibe output should identify RepoPilot/vibe context\n{}",
content
);
assert!(
content.contains("security")
|| content.contains("Security")
|| content.contains("API_TOKEN")
|| content.contains("token"),
"vibe output should include security-focused context\n{}",
content
);
}
#[test]
fn harden_writes_prioritized_remediation_plan() {
let project = create_demo_project();
let output_path = project.path().join("harden.md");
run_ok(
project.path(),
[
"harden",
".",
"--focus",
"all",
"--budget",
"4k",
"--output",
output_path.to_str().expect("non-utf8 output path"),
],
);
let content = read_non_empty(&output_path);
assert!(
content.contains("P0")
|| content.contains("P1")
|| content.contains("Priority")
|| content.contains("Remediation"),
"harden output should look like a remediation plan\n{}",
content
);
}
#[test]
fn prompt_writes_ai_ready_prompt() {
let project = create_demo_project();
let output_path: PathBuf = project.path().join("prompt.md");
run_ok(
project.path(),
[
"prompt",
".",
"--focus",
"quality",
"--budget",
"4k",
"--output",
output_path.to_str().expect("non-utf8 output path"),
],
);
let content = read_non_empty(&output_path);
assert!(
content.contains("RepoPilot")
|| content.contains("prompt")
|| content.contains("fix")
|| content.contains("Findings"),
"prompt output should include AI remediation context\n{}",
content
);
}