use cadmpeg_test_support::wire;
use crate::report::decode::{DecodeReport, DecodeTransfer, TransferLedger};
use cadmpeg_core::dialect::DialectLayers;
use std::collections::BTreeMap;
use crate::report::decode::{TransferDisposition, TransferOutcome, TransferRecord};
#[test]
fn hex_coverage_records_the_formatted_name() {
use crate::report::decode::{Coverage, HexByteCoverageKey};
let key = HexByteCoverageKey::new("type_", "_count");
let mut coverage = Coverage::default();
coverage.record_hex_byte(key, 0x0a, 7);
assert_eq!(coverage.get("type_0a_count"), Some(&7));
}
#[test]
fn indexed_coverage_records_the_decimal_name() {
use crate::report::decode::{Coverage, IndexedCoverageKey};
let key = IndexedCoverageKey::decimal("type_", "_count");
let mut coverage = Coverage::default();
coverage.record_indexed(key, 4, 7);
assert_eq!(coverage.get("type_4_count"), Some(&7));
}
#[test]
fn owned_coverage_record_preserves_declared_key() {
let key = crate::report::decode::CoverageKey::new("decoded_entities");
let mut coverage = crate::report::decode::Coverage::default();
assert!(coverage
.record_owned(key, "other_entities".to_owned(), 3)
.is_err());
assert!(coverage.is_empty());
coverage
.record_owned(key, "decoded_entities".to_owned(), 3)
.expect("declared key is admitted");
assert_eq!(coverage.get("decoded_entities"), Some(&3));
}
#[cfg(feature = "schema")]
#[test]
fn current_decode_report_schema_requires_its_format_identity() {
let schema = serde_json::to_value(schemars::schema_for!(crate::report::decode::DecodeReport))
.expect("decode report schema serializes");
let required = schema["required"]
.as_array()
.expect("decode report schema has required fields");
assert!(
required.iter().any(|field| field == "identity"),
"{schema:#}"
);
}
#[test]
fn transfer_record_keeps_the_nested_outcome_wire_shape() {
let record = TransferRecord {
source: "D1".into(),
outcome: TransferOutcome::Retained {
target: "iges:entity:directory#1".into(),
note: Some("retained".into()),
},
};
let wire = serde_json::to_value(&record).expect("transfer record serializes");
assert_eq!(
wire,
serde_json::json!({
"source": "D1",
"outcome": {
"disposition": "retained",
"target": "iges:entity:directory#1",
"note": "retained"
}
})
);
assert_eq!(
serde_json::from_value::<TransferRecord>(wire).expect("transfer record deserializes"),
record
);
}
#[test]
fn transfer_record_wire_rejects_disposition_target_disagreement() {
for wire in [
serde_json::json!({
"source": "D1",
"outcome": {"disposition": "retained"}
}),
serde_json::json!({
"source": "D1",
"outcome": {"disposition": "omitted", "target": "point:1"}
}),
serde_json::json!({
"source": "D1",
"outcome": {"disposition": "emitted", "target": "point:1", "note": "n"}
}),
serde_json::json!({
"source": "D1",
"outcome": {"disposition": "emitted"}
}),
] {
assert!(serde_json::from_value::<TransferRecord>(wire).is_err());
}
let omitted: TransferRecord = serde_json::from_value(serde_json::json!({
"source": "D2",
"outcome": {"disposition": "omitted", "note": "unsupported"}
}))
.expect("omitted record has no target");
assert_eq!(omitted.target(), None);
assert_eq!(
wire::field::<crate::report::decode::TransferDisposition>(
&(omitted.outcome),
"disposition"
),
TransferDisposition::Omitted
);
assert_eq!(
wire::field_or_default::<Option<String>>(&(omitted.outcome), "note").as_deref(),
Some("unsupported")
);
}
#[test]
fn classified_report_wire_requires_its_primary_format() {
let report = DecodeReport::classified(
DialectLayers::of(cadmpeg_core::dialect::DialectMatch::admitted(
cadmpeg_core::dialect_id!("rhino:archive-80"),
)),
DecodeTransfer::full(true),
BTreeMap::new(),
Vec::new(),
Vec::new(),
TransferLedger::default(),
);
let golden = serde_json::to_string(&report).unwrap();
assert_eq!(
golden,
r#"{"identity":{"classification":"classified","dialects":{"primary":{"dialect":"rhino:archive-80","admission":"admitted"},"extra":[]}},"transfer":{"transfer":"full","geometry_transferred":true},"losses":[],"notes":[]}"#
);
assert_eq!(
serde_json::from_str::<DecodeReport>(&golden).unwrap(),
report
);
let contradictory = golden.replacen("\"transfer\":\"full\"", "\"transfer\":\"container\"", 1);
let error = serde_json::from_str::<DecodeReport>(&contradictory)
.expect_err("a container-only report has no geometry outcome to state");
assert!(
error.to_string().contains("geometry_transferred"),
"{error}"
);
let restated = golden.replacen(
"\"classification\":\"classified\"",
"\"classification\":\"classified\",\"format\":\"step\"",
1,
);
let error = serde_json::from_str::<DecodeReport>(&restated)
.expect_err("a classified identity carries no second format");
assert!(error.to_string().contains("format"), "{error}");
}
#[test]
fn container_only_report_wire_preserves_the_coherent_transfer_state() {
let report = DecodeReport::unclassified(
"test",
DecodeTransfer::ContainerOnly {},
BTreeMap::new(),
Vec::new(),
Vec::new(),
TransferLedger::default(),
);
let rendered = serde_json::to_string(&report).unwrap();
assert!(
rendered.contains("\"transfer\":\"container\""),
"{rendered}"
);
assert!(!rendered.contains("geometry_transferred"), "{rendered}");
assert_eq!(
serde_json::from_str::<DecodeReport>(&rendered).unwrap(),
report
);
}
#[test]
fn a_decode_transfer_states_its_scope_and_carries_only_its_own_keys() {
let base = serde_json::json!({
"identity": {"classification": "unclassified", "format": "rhino"},
"transfer": {"transfer": "full", "geometry_transferred": true},
"losses": [],
"notes": [],
});
let report = serde_json::from_value::<DecodeReport>(base.clone()).expect("a full decode");
assert_eq!(report.transfer, DecodeTransfer::full(true));
assert_eq!(
serde_json::to_value(&report).unwrap()["transfer"]["transfer"],
"full"
);
let mut container_only = base.clone();
container_only["transfer"]["transfer"] = serde_json::json!("container");
let error = serde_json::from_value::<DecodeReport>(container_only.clone())
.expect_err("a container-only report has no geometry outcome");
assert!(
error.to_string().contains("geometry_transferred"),
"{error}"
);
container_only["transfer"]
.as_object_mut()
.expect("map")
.remove("geometry_transferred");
let report =
serde_json::from_value::<DecodeReport>(container_only.clone()).expect("container only");
assert_eq!(report.transfer, DecodeTransfer::ContainerOnly {});
let wire = serde_json::to_value(&report).unwrap();
assert_eq!(wire["transfer"]["transfer"], "container");
assert_eq!(
wire["transfer"]["transfer"],
serde_json::to_value(cadmpeg_core::decode::DecodeDepth::Container).unwrap(),
"the transfer tag spells the depth it delivered"
);
container_only["transfer"]["transfer"] = serde_json::json!("container_only");
serde_json::from_value::<DecodeReport>(container_only)
.expect_err("the depth token is `container`");
assert!(wire["transfer"].get("geometry_transferred").is_none());
let mut without_outcome = base;
without_outcome["transfer"]
.as_object_mut()
.expect("map")
.remove("geometry_transferred");
let error = serde_json::from_value::<DecodeReport>(without_outcome)
.expect_err("a full decode states its geometry outcome");
assert!(
error.to_string().contains("geometry_transferred"),
"{error}"
);
}
#[test]
fn coverage_entry_replaces_its_count() {
use crate::report::decode::{Coverage, CoverageKey};
let key = CoverageKey::new("one_count");
let mut coverage = Coverage::default();
coverage.record(key, 7);
coverage.record(key, 8);
assert_eq!(coverage.get(key.as_str()), Some(&8));
}