use hocon::{empty, parse_string_with_options, ParseOptions, ResolveOptions};
fn deferred_parse(input: &str) -> hocon::Config {
parse_string_with_options(
input,
ParseOptions::defaults().with_resolve_substitutions(false),
)
.expect("parse must succeed")
}
fn no_env_opts() -> ResolveOptions {
ResolveOptions::defaults().with_use_system_environment(false)
}
#[test]
fn fused_parse_is_resolved() {
let c = hocon::parse(r#"a = 1"#).unwrap();
assert!(
c.is_resolved(),
"fused parse must produce a resolved Config"
);
}
#[test]
fn deferred_parse_with_subst_is_not_resolved() {
let c = deferred_parse(r#"a = ${b}"#);
assert!(
!c.is_resolved(),
"deferred parse with substitution must be unresolved"
);
}
#[test]
fn deferred_parse_no_subst_is_resolved() {
let c = deferred_parse(r#"a = 1"#);
assert!(
c.is_resolved(),
"no substitutions -> is_resolved must be true even with deferred flag"
);
}
#[cfg(feature = "serde")]
#[test]
fn from_map_is_resolved() {
use serde_json::json;
let map = json!({"x": 1}).as_object().unwrap().clone();
let c = hocon::from_map(map, None).unwrap();
assert!(c.is_resolved(), "from_map must produce a resolved Config");
}
#[test]
fn empty_is_resolved() {
assert!(empty(None).is_resolved());
}
#[test]
fn empty_as_fallback_is_noop() {
let c = hocon::parse(r#"a = 1"#).unwrap();
let m = c.with_fallback(&empty(None));
assert_eq!(m.get_i64("a").unwrap(), 1);
}
#[test]
fn empty_resolve_is_noop() {
let r = empty(None).resolve(ResolveOptions::defaults()).unwrap();
assert!(r.is_resolved());
assert!(r.keys().is_empty());
}
#[test]
fn getter_on_unresolved_path_returns_error() {
let c = deferred_parse(r#"a = ${b}"#);
let err = c
.get_string("a")
.expect_err("must error on unresolved path");
let msg = format!("{}", err);
assert!(
msg.to_lowercase().contains("not resolved") || msg.to_lowercase().contains("unresolved"),
"error must indicate unresolved state; got: {}",
msg
);
}
#[test]
fn s13a_optional_self_ref_across_fallback_dr04() {
let r = deferred_parse(r#"a = ${?a} extra"#);
let f = deferred_parse(r#"a = base"#);
let resolved = r
.with_fallback(&f)
.resolve(no_env_opts())
.expect("Resolve must succeed");
assert_eq!(resolved.get_string("a").unwrap(), "base extra");
}
#[test]
fn s13a_required_self_ref_with_fallback_dr05() {
let r = deferred_parse(r#"a = ${a} extra"#);
let f = deferred_parse(r#"a = base"#);
let resolved = r
.with_fallback(&f)
.resolve(no_env_opts())
.expect("Resolve must succeed");
assert_eq!(resolved.get_string("a").unwrap(), "base extra");
}
#[test]
fn s13a_required_self_ref_no_fallback_dr06() {
let r = deferred_parse(r#"a = ${a} extra"#);
let result = r.resolve(no_env_opts());
assert!(
result.is_err(),
"required self-ref with no prior must error"
);
}
#[test]
fn transitive_cross_layer_dr21() {
let r = deferred_parse(r#"a = ${b}"#);
let f1 = deferred_parse(r#"b = ${c}"#);
let f2 = deferred_parse(r#"c = 1"#);
let resolved = r
.with_fallback(&f1)
.with_fallback(&f2)
.resolve(no_env_opts())
.unwrap();
assert_eq!(resolved.get_i64("a").unwrap(), 1);
}
#[test]
fn hidden_across_layers_dr23() {
let r = deferred_parse(r#"foo = 42"#);
let f = deferred_parse(r#"foo = ${nonexist}"#);
let resolved = r
.with_fallback(&f)
.resolve(no_env_opts())
.expect("hidden substitution must not cause error");
assert_eq!(resolved.get_i64("foo").unwrap(), 42);
}
#[test]
fn cross_layer_cycle_dr18() {
let r = deferred_parse(r#"a = ${b}"#);
let f = deferred_parse(r#"b = ${a}"#);
let result = r.with_fallback(&f).resolve(no_env_opts());
assert!(
result.is_err(),
"cross-layer cycle must return ResolveError"
);
}
#[test]
fn optional_undef_materialisation_standalone_dr24() {
let r = deferred_parse(r#"a = ${?x}"#);
let resolved = r.resolve(no_env_opts()).unwrap();
assert!(
!resolved.has("a"),
"standalone optional undefined -> field must be omitted"
);
}
#[test]
fn optional_undef_materialisation_concat_dr25() {
let r = deferred_parse(r#"a = ${?x} "tail""#);
let resolved = r.resolve(no_env_opts()).unwrap();
let val = resolved.get_string("a").unwrap_or_default();
assert!(
val.contains("tail"),
"concat with undefined optional must produce remaining pieces; got {:?}",
val
);
}
#[test]
fn composition_barrier_dr10() {
let r = deferred_parse(r#"a { x = 1 }"#);
let fb1 = deferred_parse(r#"a = "scalar""#);
let fb2 = deferred_parse(r#"a { y = 2 }"#);
let m = r.with_fallback(&fb1).with_fallback(&fb2);
let resolved = m.resolve(no_env_opts()).unwrap();
assert_eq!(resolved.get_i64("a.x").unwrap(), 1);
let a_cfg = resolved.get_config("a").unwrap();
assert!(
!a_cfg.has("y"),
"composition barrier: fb2's y must not contribute"
);
}
#[test]
fn double_resolve_is_idempotent_dr19() {
let c = deferred_parse(r#"a = 1"#);
let r1 = c.resolve(no_env_opts()).unwrap();
let r2 = r1.clone().resolve(no_env_opts()).unwrap();
assert_eq!(r1.get_i64("a").unwrap(), r2.get_i64("a").unwrap());
}
#[test]
fn not_resolved_error_is_config_error() {
let c = deferred_parse(r#"a = ${b}"#);
let result = c.get_string("a");
assert!(result.is_err());
let msg = format!("{}", result.unwrap_err());
assert!(
msg.to_lowercase().contains("not resolved") || msg.to_lowercase().contains("unresolved"),
"ConfigError message must mention resolution state; got: {}",
msg
);
}
#[test]
fn render_json_for_test_basic() {
use hocon::_render_json_for_test;
let c = hocon::parse(
r#"
a = 1
b = "hello"
c { x = true }
"#,
)
.unwrap();
let json = _render_json_for_test(&c);
assert!(
json.contains(r#""a":1"#) || json.contains(r#""a": 1"#),
"a must be 1; got {}",
json
);
assert!(
json.contains(r#""b":"hello""#) || json.contains(r#""b": "hello""#),
"b must be hello; got {}",
json
);
assert!(
json.contains(r#""x":true"#) || json.contains(r#""x": true"#),
"c.x must be true; got {}",
json
);
}