use start_command::failure_handler::{
can_create_issue, handle_failure, is_gh_authenticated, is_gh_upload_log_available,
parse_git_url, Config,
};
mod parse_git_url_tests {
use super::*;
#[test]
fn should_parse_https_url() {
let info = parse_git_url("https://github.com/owner/repo").unwrap();
assert_eq!(info.owner, "owner");
assert_eq!(info.repo, "repo");
assert_eq!(info.url, "https://github.com/owner/repo");
}
#[test]
fn should_parse_ssh_url() {
let info = parse_git_url("git@github.com:owner/repo.git").unwrap();
assert_eq!(info.owner, "owner");
assert_eq!(info.repo, "repo");
}
#[test]
fn should_parse_git_at_format() {
let info = parse_git_url("git@github.com:myorg/myrepo").unwrap();
assert_eq!(info.owner, "myorg");
assert_eq!(info.repo, "myrepo");
}
#[test]
fn should_return_none_for_empty_string() {
assert!(parse_git_url("").is_none());
}
#[test]
fn should_return_none_for_invalid_url() {
assert!(parse_git_url("not a url").is_none());
}
#[test]
fn should_return_none_for_non_github_url() {
assert!(parse_git_url("https://gitlab.com/owner/repo").is_none());
}
#[test]
fn should_strip_git_suffix_from_repo() {
let info = parse_git_url("https://github.com/owner/repo.git").unwrap();
assert_eq!(info.repo, "repo");
assert!(!info.repo.ends_with(".git"));
}
#[test]
fn should_normalize_url_to_https() {
let info = parse_git_url("git@github.com:owner/myrepo.git").unwrap();
assert!(info.url.starts_with("https://"));
assert!(info.url.contains("github.com"));
}
#[test]
fn should_parse_npm_style_git_url() {
let info = parse_git_url("git+https://github.com/link-foundation/start.git");
if let Some(i) = info {
assert_eq!(i.owner, "link-foundation");
assert_eq!(i.repo, "start");
}
}
}
mod config_tests {
use super::*;
#[test]
fn should_have_correct_default_values() {
let config = Config::default();
assert!(!config.disable_auto_issue);
assert!(!config.disable_log_upload);
assert!(!config.verbose);
}
#[test]
fn should_allow_setting_disable_auto_issue() {
let config = Config {
disable_auto_issue: true,
..Config::default()
};
assert!(config.disable_auto_issue);
}
#[test]
fn should_allow_setting_disable_log_upload() {
let config = Config {
disable_log_upload: true,
..Config::default()
};
assert!(config.disable_log_upload);
}
#[test]
fn should_allow_setting_verbose() {
let config = Config {
verbose: true,
..Config::default()
};
assert!(config.verbose);
}
}
mod handle_failure_tests {
use super::*;
#[test]
fn should_not_panic_when_disable_auto_issue_is_true() {
let config = Config {
disable_auto_issue: true,
..Config::default()
};
handle_failure(&config, "test-cmd", "test-cmd arg1", 1, "/tmp/test.log");
}
#[test]
fn should_not_panic_when_verbose_and_disable_auto_issue() {
let config = Config {
disable_auto_issue: true,
verbose: true,
..Config::default()
};
handle_failure(&config, "test-cmd", "test-cmd --flag", 42, "/tmp/test.log");
}
}
mod is_gh_authenticated_tests {
use super::*;
#[test]
fn should_return_a_bool() {
let result = is_gh_authenticated();
let _ = result;
}
#[test]
fn return_value_is_bool_type() {
let result: bool = is_gh_authenticated();
let _ = result;
}
}
mod is_gh_upload_log_available_tests {
use super::*;
#[test]
fn should_return_a_bool() {
let result = is_gh_upload_log_available();
let _ = result;
}
#[test]
fn return_value_is_bool_type() {
let result: bool = is_gh_upload_log_available();
let _ = result;
}
}
mod can_create_issue_tests {
use super::*;
#[test]
fn should_return_false_for_invalid_repo() {
let result = can_create_issue("invalid-owner-xyz-99999", "invalid-repo-xyz-99999");
assert!(!result);
}
#[test]
fn should_return_bool() {
let result: bool = can_create_issue("some-owner", "some-repo");
let _ = result;
}
}
#[cfg(unix)]
mod create_issue_tests {
use start_command::failure_handler::{create_issue, RepoInfo};
use std::os::unix::fs::PermissionsExt;
use std::process::Command;
use tempfile::TempDir;
const FAILING_COMMAND: &str = "echo \"quoted\" $(id) `hostname`";
const HELPER_TEST: &str = "create_issue_tests::helper_process_calls_create_issue";
const URL_PREFIX: &str = "created-issue-url:";
#[test]
#[ignore = "helper process, driven by create_issue_with_fake_gh"]
fn helper_process_calls_create_issue() {
let repo_info = RepoInfo {
owner: "owner".to_string(),
repo: "repo".to_string(),
url: "https://github.com/owner/repo".to_string(),
};
let full_command =
std::env::var("TEST_FAILING_COMMAND").unwrap_or_else(|_| FAILING_COMMAND.to_string());
let url = create_issue(&repo_info, &full_command, 1, None).unwrap_or_default();
println!("{}{}", URL_PREFIX, url);
}
fn create_issue_with_fake_gh() -> (Option<String>, Vec<String>) {
create_issue_with_fake_gh_command(FAILING_COMMAND)
}
fn create_issue_with_fake_gh_command(full_command: &str) -> (Option<String>, Vec<String>) {
let dir = TempDir::new().unwrap();
let argv_file = dir.path().join("argv");
let gh_path = dir.path().join("gh");
std::fs::write(
&gh_path,
format!(
"#!/bin/sh\nprintf '%s\\0' \"$@\" > \"{}\"\necho https://github.com/owner/repo/issues/1\n",
argv_file.display()
),
)
.unwrap();
let mut permissions = std::fs::metadata(&gh_path).unwrap().permissions();
permissions.set_mode(0o755);
std::fs::set_permissions(&gh_path, permissions).unwrap();
let mut paths = vec![dir.path().to_path_buf()];
if let Some(existing) = std::env::var_os("PATH") {
paths.extend(std::env::split_paths(&existing));
}
let output = Command::new(std::env::current_exe().unwrap())
.args(["--exact", HELPER_TEST, "--ignored", "--nocapture"])
.env("PATH", std::env::join_paths(paths).unwrap())
.env("TEST_FAILING_COMMAND", full_command)
.output()
.unwrap();
assert!(
output.status.success(),
"helper process failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let url = stdout
.lines()
.find_map(|line| line.strip_prefix(URL_PREFIX))
.filter(|url| !url.is_empty())
.map(str::to_string);
let recorded = std::fs::read_to_string(&argv_file).unwrap();
let mut argv = recorded.split('\0').map(str::to_string).collect::<Vec<_>>();
argv.pop();
(url, argv)
}
#[test]
fn passes_the_title_and_body_as_separate_arguments() {
let (url, argv) = create_issue_with_fake_gh();
assert_eq!(
url.as_deref(),
Some("https://github.com/owner/repo/issues/1")
);
assert_eq!(argv[0..4], ["issue", "create", "--repo", "owner/repo"]);
assert_eq!(argv[4], "--title");
assert_eq!(argv[6], "--body");
assert_eq!(argv.len(), 8);
}
#[test]
fn sanitizes_credentials_in_issue_metadata_and_supports_unicode_commands() {
let token = format!("gh{}{}", "p_", "a".repeat(36));
let (_, argv) =
create_issue_with_fake_gh_command(&format!("echo {} 😀{}", token, "é".repeat(40)));
assert!(!argv[5].contains(&token));
assert!(!argv[7].contains(&token));
assert!(argv[7].contains("[REDACTED]"));
}
#[test]
fn keeps_the_failing_command_verbatim_and_writes_real_newlines() {
let (_, argv) = create_issue_with_fake_gh();
let title = &argv[5];
let body = &argv[7];
assert!(
title.contains(FAILING_COMMAND),
"title must quote the command verbatim, got: {}",
title
);
assert!(
body.contains(FAILING_COMMAND),
"body must quote the command verbatim"
);
assert!(!title.contains("\\\""), "title must not escape quotes");
assert!(!body.contains("\\\""), "body must not escape quotes");
assert!(body.contains('\n'), "body must contain real newlines");
assert!(
!body.contains("\\n"),
"body must not contain literal backslash-n sequences"
);
}
}