use super::{CargoAllowError, CargoAllowErrorKind};
use std::path::Path;
#[test]
fn cargo_allow_error_fmt_return_value_observer() {
let err = CargoAllowError::new("policy validation failed");
assert_eq!(format!("{err}"), "policy validation failed");
assert_eq!(err.to_string(), "policy validation failed");
}
#[test]
fn new_defaults_to_unknown_kind() {
let err = CargoAllowError::new("something went wrong");
assert_eq!(err.kind(), CargoAllowErrorKind::Unknown);
assert_eq!(err.message(), "something went wrong");
}
#[test]
fn with_kind_sets_structured_kind() {
let err = CargoAllowError::with_kind(CargoAllowErrorKind::InvalidPolicy, "missing owner");
assert_eq!(err.kind(), CargoAllowErrorKind::InvalidPolicy);
assert_eq!(err.message(), "missing owner");
}
#[test]
fn kind_renders_as_stable_lowercase_str() {
assert_eq!(CargoAllowErrorKind::Usage.as_str(), "usage");
assert_eq!(
CargoAllowErrorKind::InvalidConfig.as_str(),
"invalid_config"
);
assert_eq!(
CargoAllowErrorKind::InvalidPolicy.as_str(),
"invalid_policy"
);
assert_eq!(
CargoAllowErrorKind::PolicyViolation.as_str(),
"policy_violation"
);
assert_eq!(CargoAllowErrorKind::Artifact.as_str(), "artifact");
assert_eq!(CargoAllowErrorKind::Unknown.as_str(), "unknown");
}
#[test]
fn every_error_kind_has_a_unique_stable_code() {
let codes = CargoAllowErrorKind::ALL
.iter()
.map(|kind| kind.code())
.collect::<std::collections::BTreeSet<_>>();
assert_eq!(codes.len(), CargoAllowErrorKind::ALL.len());
assert_eq!(CargoAllowErrorKind::Usage.code(), "E0001_USAGE");
assert_eq!(
CargoAllowErrorKind::InvalidPolicy.code(),
"E0003_INVALID_POLICY"
);
assert_eq!(CargoAllowErrorKind::Unknown.code(), "E0009_UNKNOWN");
}
#[test]
fn error_exposes_the_code_of_its_kind() {
let error = CargoAllowError::with_kind(CargoAllowErrorKind::Artifact, "write failed");
assert_eq!(error.code(), "E0007_ARTIFACT");
}
#[test]
fn error_exposes_structured_diagnostic_details() -> Result<(), String> {
let error = CargoAllowError::with_kind(CargoAllowErrorKind::InvalidPolicy, "missing owner")
.with_diagnostic(super::CargoAllowDiagnostic::error(
"E0003_INVALID_POLICY",
"policy_validation",
Some("allow-1"),
Some("owner"),
"allow-1 missing owner",
));
let diagnostic = error
.diagnostics()
.first()
.ok_or_else(|| "diagnostic should be present".to_string())?;
assert_eq!(diagnostic.code, "E0003_INVALID_POLICY");
assert_eq!(diagnostic.entry_id.as_deref(), Some("allow-1"));
assert_eq!(diagnostic.field.as_deref(), Some("owner"));
Ok(())
}
#[test]
fn toml_span_preserves_path_and_one_based_line_column() -> Result<(), String> {
let error = CargoAllowError::with_kind(CargoAllowErrorKind::InvalidPolicy, "invalid TOML")
.with_toml_span(
Some(Path::new("policy/allow.toml")),
"policy = \"cargo-allow\"\nowner = [",
Some(29..30),
);
let location = error
.location()
.ok_or_else(|| "TOML span should produce a structured location".to_string())?;
assert_eq!(location.path.as_deref(), Some("policy/allow.toml"));
assert_eq!(location.line, 2);
assert!(location.column > 0);
Ok(())
}
#[test]
fn display_includes_cause_chain() {
let inner = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
let err = CargoAllowError::with_kind(CargoAllowErrorKind::Artifact, "failed to write receipt")
.with_cause(&inner);
let rendered = format!("{err}");
assert!(rendered.contains("failed to write receipt"));
assert!(rendered.contains("caused by:"));
assert!(rendered.contains("file missing"));
}
#[test]
fn error_source_walks_attached_causes() {
use std::error::Error as _;
let inner = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
let mid = std::io::Error::other("git failed");
let err = CargoAllowError::with_kind(CargoAllowErrorKind::Inventory, "failed to read revision")
.with_cause(&mid)
.with_cause(&inner);
let first = err
.source()
.unwrap_or_else(|| std::panic::panic_any("expected first cause"));
assert_eq!(first.to_string(), "git failed");
let second = first
.source()
.unwrap_or_else(|| std::panic::panic_any("expected nested cause"));
assert_eq!(second.to_string(), "file missing");
assert!(second.source().is_none());
}
#[test]
fn from_io_error_exposes_source_without_duplicate_display_cause() {
use std::error::Error as _;
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "no such file");
let err: CargoAllowError = io_err.into();
assert_eq!(err.message(), "no such file");
assert!(err.causes().is_empty());
let source = err
.source()
.unwrap_or_else(|| std::panic::panic_any("expected io source"));
assert_eq!(source.to_string(), "no such file");
assert!(!format!("{err}").contains("caused by:"));
}
#[test]
fn partialeq_compares_kind_and_message_not_causes() {
let a = CargoAllowError::with_kind(CargoAllowErrorKind::Scan, "read error");
let b = CargoAllowError::with_kind(CargoAllowErrorKind::Scan, "read error");
let a_with_cause = a.with_cause(&std::io::Error::other("boom"));
assert_eq!(a_with_cause, b);
let c = CargoAllowError::with_kind(CargoAllowErrorKind::Unknown, "read error");
assert_ne!(b, c);
}
#[test]
fn from_io_error_preserves_kind_and_message() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "no such file");
let err: CargoAllowError = io_err.into();
assert_eq!(err.kind(), CargoAllowErrorKind::InvalidConfig);
assert!(err.message().contains("no such file"));
}
#[test]
fn from_io_permission_denied_maps_to_inventory_kind() {
let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied");
let err: CargoAllowError = io_err.into();
assert_eq!(err.kind(), CargoAllowErrorKind::Inventory);
}