#![cfg(feature = "include-package")]
use hocon::Parser;
const PKG_LIB_REFERENCE: &str = r#"
host = "example.com"
port = 8080
app.name = "lib"
"#;
const PKG_IPK03_A: &str = r#"
version = "1.0.0"
source = "package-A"
"#;
const PKG_IPK03_B: &str = r#"
version = "2.0.0"
source = "package-B"
"#;
const PKG_FOO_BAR_X: &str = r#"
registered = true
"#;
const PKG_LIB_REFERENCE_UPPER: &str = r#"
registered = true
"#;
const PKG_EMPTY: &str = "";
const PKG_SELF: &str = r#"include package("foo", "self.conf")"#;
const PKG_CYCLE_A: &str = r#"include package("foo", "b.conf")"#;
const PKG_CYCLE_B: &str = r#"include package("foo", "a.conf")"#;
fn parser_ipk01() -> Parser {
Parser::new().register_package(
"github.com/example/lib",
"reference.conf",
PKG_LIB_REFERENCE,
)
}
#[test]
fn ipk01_basic_success() {
let input = r#"include package("github.com/example/lib", "reference.conf")"#;
let cfg = parser_ipk01()
.parse(input)
.expect("ipk01: should parse successfully");
assert_eq!(cfg.get_string("host").unwrap(), "example.com");
assert_eq!(cfg.get_i64("port").unwrap(), 8080);
assert_eq!(cfg.get_string("app.name").unwrap(), "lib");
}
#[test]
fn ipk02_one_arg_rejected() {
let input = r#"include package("github.com/example/lib/reference.conf")"#;
let result = Parser::new().parse(input);
assert!(
result.is_err(),
"ipk02: one-arg package(...) must be a parse error (E11 decision 2)"
);
}
#[test]
#[should_panic(expected = "conflicting content")]
fn ipk03_collision_panics() {
let parser =
Parser::new().register_package("github.com/example/lib", "reference.conf", PKG_IPK03_A);
let _parser = parser.register_package("github.com/example/lib", "reference.conf", PKG_IPK03_B);
}
#[test]
fn ipk03_idempotent_registration_ok() {
let _parser = Parser::new()
.register_package("github.com/example/lib", "reference.conf", PKG_IPK03_A)
.register_package("github.com/example/lib", "reference.conf", PKG_IPK03_A);
}
#[test]
fn ipk04_lookup_miss() {
let input = r#"include package("github.com/example/missing", "x.conf")"#;
let result = Parser::new().parse(input);
assert!(
result.is_err(),
"ipk04: registry miss must be an error (E11 decision 4)"
);
}
#[test]
fn ipk05_required_miss() {
let input = r#"include required(package("github.com/example/missing", "x.conf"))"#;
let result = Parser::new().parse(input);
assert!(
result.is_err(),
"ipk05: required(package(...)) with registry miss must be an error (E11 decision 7)"
);
}
#[test]
fn ipk06_case_sensitive_id() {
let input = r#"include package("foo/bar", "x.conf")"#;
let result = Parser::new()
.register_package("Foo/Bar", "x.conf", PKG_FOO_BAR_X)
.parse(input);
assert!(
result.is_err(),
"ipk06: identifier is case-sensitive; 'foo/bar' != 'Foo/Bar' (E11 decision 5)"
);
}
#[test]
fn ipk07_case_sensitive_file() {
let input = r#"include package("github.com/example/lib", "reference.conf")"#;
let result = Parser::new()
.register_package(
"github.com/example/lib",
"Reference.conf",
PKG_LIB_REFERENCE_UPPER,
)
.parse(input);
assert!(
result.is_err(),
"ipk07: file argument is case-sensitive; 'reference.conf' != 'Reference.conf' (E11 decision 5)"
);
}
#[test]
fn ipk08_empty_content_succeeds() {
let input = r#"
app = host
include package("github.com/example/lib", "empty.conf")
"#;
let cfg = Parser::new()
.register_package("github.com/example/lib", "empty.conf", PKG_EMPTY)
.parse(input)
.expect("ipk08: empty registered content should succeed (E11 decision 4 note)");
assert_eq!(
cfg.get_string("app").unwrap(),
"host",
"ipk08: 'app' key from outer conf must survive empty-content include"
);
}
#[test]
fn ipk09_file_arg_empty() {
let input = r#"include package("foo", "")"#;
let result = Parser::new().parse(input);
assert!(
result.is_err(),
"ipk09: empty file argument must be a parse error (E11 decision 6)"
);
}
#[test]
fn ipk10_file_arg_absolute() {
let input = r#"include package("foo", "/etc/passwd")"#;
let result = Parser::new().parse(input);
assert!(
result.is_err(),
"ipk10: absolute path file argument must be a parse error (E11 decision 6)"
);
}
#[test]
fn ipk11_file_arg_traversal() {
let input = r#"include package("foo", "../escape.conf")"#;
let result = Parser::new().parse(input);
assert!(
result.is_err(),
"ipk11: '..' traversal in file argument must be a parse error (E11 decision 6)"
);
}
#[test]
fn ipk12_file_arg_backslash() {
let input = r#"include package("foo", "x\\y.conf")"#;
let result = Parser::new().parse(input);
assert!(
result.is_err(),
"ipk12: backslash in file argument (after unescape) must be a parse error (E11 decision 6)"
);
}
#[test]
fn ipk13_cycle_self() {
let input = r#"include package("foo", "self.conf")"#;
let result = Parser::new()
.register_package("foo", "self.conf", PKG_SELF)
.parse(input);
assert!(
result.is_err(),
"ipk13: self-include cycle must be an error (E11 decision 8)"
);
if let Err(e) = result {
let msg = format!("{}", e);
assert!(
msg.to_lowercase().contains("cycle")
|| msg.to_lowercase().contains("circular")
|| msg.to_lowercase().contains("recursive"),
"ipk13: error message should mention cycle/circular (got: {})",
msg
);
}
}
#[test]
fn ipk14_cycle_mutual() {
let input = r#"include package("foo", "a.conf")"#;
let result = Parser::new()
.register_package("foo", "a.conf", PKG_CYCLE_A)
.register_package("foo", "b.conf", PKG_CYCLE_B)
.parse(input);
assert!(
result.is_err(),
"ipk14: mutual include cycle must be an error (E11 decision 8)"
);
}
#[test]
fn file_arg_dot_segment_rejected() {
let input = r#"include package("foo", "./x.conf")"#;
assert!(Parser::new().parse(input).is_err());
}
#[test]
fn file_arg_consecutive_slash_rejected() {
let input = r#"include package("foo", "a//b.conf")"#;
assert!(Parser::new().parse(input).is_err());
}
#[test]
fn file_arg_valid_nested_accepted_with_registration() {
let input = r#"include package("foo", "conf/reference.conf")"#;
let result = Parser::new().parse(input);
assert!(result.is_err());
match result {
Err(hocon::HoconError::Resolve(_)) => { }
Err(hocon::HoconError::Parse(e)) => {
panic!(
"file_arg_valid_nested_accepted: got ParseError for valid file arg '{}': {}",
"conf/reference.conf", e
);
}
Err(e) => panic!("unexpected error type: {}", e),
Ok(_) => panic!("expected error for unregistered package"),
}
}
#[test]
fn empty_identifier_rejected() {
let input = r#"include package("", "x.conf")"#;
let result = Parser::new().parse(input);
assert!(
result.is_err(),
"empty identifier must be a parse error (E11 decision 1)"
);
}
#[test]
fn missing_closing_paren_is_parse_error() {
let input = r#"include package("foo", "x.conf""#;
let result = Parser::new().parse(input);
assert!(
result.is_err(),
"missing closing ')' must be a parse error (review fix for Codex finding)"
);
}
#[test]
fn required_missing_closing_paren_is_parse_error() {
let input = r#"include required(package("foo", "x.conf")"#;
let result = Parser::new().parse(input);
assert!(
result.is_err(),
"missing outer closing ')' for required(package(...)) must be a parse error"
);
}
#[test]
fn spaced_package_qualifier_accepted() {
let input = r#"include package ("github.com/example/lib", "reference.conf")"#;
let result = Parser::new()
.register_package(
"github.com/example/lib",
"reference.conf",
PKG_LIB_REFERENCE,
)
.parse(input);
assert!(
result.is_ok(),
"spaced `include package (...)` must parse successfully (Copilot review fix): {:?}",
result.err()
);
let config = result.unwrap();
assert_eq!(config.get_string("host").unwrap(), "example.com");
}
#[test]
fn spaced_package_qualifier_lookup_miss() {
let input = r#"include package ("github.com/example/lib", "reference.conf")"#;
let result = Parser::new().parse(input);
assert!(
result.is_err(),
"unregistered spaced package must be an error"
);
match result {
Err(hocon::HoconError::Resolve(_)) => { }
Err(hocon::HoconError::Parse(e)) => {
panic!(
"spaced package form: got ParseError instead of ResolveError: {}",
e
);
}
Err(e) => panic!("unexpected error type: {}", e),
Ok(_) => panic!("expected error for unregistered package"),
}
}