#[cfg(test)]
#[expect(
clippy::assertions_on_result_states,
clippy::expect_used,
clippy::indexing_slicing,
clippy::uninlined_format_args,
clippy::unwrap_used,
reason = "legacy CLI unit tests use static fixtures; cleanup is tracked in policy/clippy-debt.toml"
)]
mod cli_unit_tests {
use hl7v2_test_utils::fixtures::SampleMessages;
use std::fs;
use std::path::PathBuf;
use tempfile::TempDir;
fn create_temp_file(dir: &TempDir, filename: &str, content: &[u8]) -> PathBuf {
let path = dir.path().join(filename);
fs::write(&path, content).expect("Failed to write temp file");
path
}
fn create_temp_hl7_file(dir: &TempDir, filename: &str) -> PathBuf {
create_temp_file(dir, filename, SampleMessages::adt_a01().as_bytes())
}
mod argument_parsing {
use clap::CommandFactory;
#[test]
fn test_parse_command_requires_input() {
use crate::Cli;
let schema = Cli::command();
assert!(schema.get_subcommands().any(|c| c.get_name() == "parse"));
}
#[test]
fn test_validate_command_requires_profile() {
use crate::Cli;
let schema = Cli::command();
assert!(schema.get_subcommands().any(|c| c.get_name() == "val"));
}
#[test]
fn test_ack_command_mode_options() {
let modes = vec!["original", "enhanced"];
for mode in modes {
assert!(mode == "original" || mode == "enhanced");
}
}
#[test]
fn test_ack_command_code_options() {
let codes = vec!["AA", "AE", "AR", "CA", "CE", "CR"];
for code in codes {
assert!(!code.is_empty());
}
}
}
mod parse_command {
use super::*;
use hl7v2::{parse, to_json};
#[test]
fn test_parse_valid_hl7_message() {
let content = SampleMessages::adt_a01();
let bytes = content.as_bytes();
let result = parse(bytes);
assert!(result.is_ok());
let message = result.unwrap();
assert!(!message.segments.is_empty());
assert!(message.segments[0].id_str() == "MSH");
}
#[test]
fn test_parse_outputs_valid_json() {
let content = SampleMessages::adt_a01();
let bytes = content.as_bytes();
let message = parse(bytes).expect("Parse should succeed");
let json_value = to_json(&message);
let json_string = serde_json::to_string(&json_value).expect("Should serialize to JSON");
assert!(json_string.contains("MSH"));
}
#[test]
fn test_parse_mllp_framed_message() {
let content = SampleMessages::adt_a01();
let mut mllp_bytes = vec![0x0B]; mllp_bytes.extend_from_slice(content.as_bytes());
mllp_bytes.push(0x1C); mllp_bytes.push(0x0D);
let result = hl7v2::parse_mllp(&mllp_bytes);
assert!(result.is_ok());
let message = result.unwrap();
assert!(message.segments[0].id_str() == "MSH");
}
#[test]
fn test_parse_detects_segment_count() {
let content = SampleMessages::adt_a01();
let bytes = content.as_bytes();
let message = parse(bytes).expect("Parse should succeed");
assert!(message.segments.len() >= 4);
}
#[test]
fn test_parse_extracts_delimiters() {
let content = SampleMessages::adt_a01();
let bytes = content.as_bytes();
let message = parse(bytes).expect("Parse should succeed");
assert_eq!(message.delims.field, '|');
assert_eq!(message.delims.comp, '^');
assert_eq!(message.delims.rep, '~');
assert_eq!(message.delims.esc, '\\');
assert_eq!(message.delims.sub, '&');
}
}
mod norm_command {
use super::*;
use hl7v2::{parse, write};
#[test]
fn test_normalize_roundtrip() {
let content = SampleMessages::adt_a01();
let bytes = content.as_bytes();
let message = parse(bytes).expect("Parse should succeed");
let normalized = write(&message);
let reparsed = parse(&normalized).expect("Reparse should succeed");
assert_eq!(message.segments.len(), reparsed.segments.len());
}
#[test]
fn test_normalize_mllp_output() {
let content = SampleMessages::adt_a01();
let bytes = content.as_bytes();
let message = parse(bytes).expect("Parse should succeed");
let normalized = write(&message);
let mllp_bytes = hl7v2::wrap_mllp(&normalized);
assert_eq!(mllp_bytes[0], 0x0B); assert_eq!(mllp_bytes[mllp_bytes.len() - 2], 0x1C); assert_eq!(mllp_bytes[mllp_bytes.len() - 1], 0x0D); }
}
mod validate_command {
use super::*;
#[test]
fn test_validate_with_valid_profile() {
let profile_yaml = r#"
message_structure: ADT_A01
version: "2.5.1"
segments:
- id: MSH
constraints:
- path: MSH.1
required: true
- path: MSH.2
required: true
"#;
let result = hl7v2::load_profile(profile_yaml);
assert!(result.is_ok(), "Failed to load profile: {:?}", result.err());
}
#[test]
fn test_validate_detects_missing_required_segment() {
let profile_yaml = r#"
message_structure: ADT_A01
version: "2.5.1"
segments:
- id: MSH
- id: EVN
- id: PID
- id: ZZ1
constraints:
- path: MSH
required: true
- path: EVN
required: true
- path: PID
required: true
- path: ZZ1
required: true
"#;
let profile = hl7v2::load_profile(profile_yaml).expect("Profile should load");
let message =
hl7v2::parse(SampleMessages::adt_a01().as_bytes()).expect("Parse should succeed");
let results = hl7v2::validate(&message, &profile);
assert!(
!results.is_empty(),
"Should have validation errors for missing ZZ1 segment"
);
}
}
mod ack_command {
use super::*;
use hl7v2::{AckCode, ack};
#[test]
fn test_generate_ack_aa() {
let content = SampleMessages::adt_a01();
let message = hl7v2::parse(content.as_bytes()).expect("Parse should succeed");
let ack_result = ack(&message, AckCode::AA);
assert!(ack_result.is_ok());
let ack_message = ack_result.unwrap();
assert!(ack_message.segments.iter().any(|s| s.id_str() == "MSH"));
assert!(ack_message.segments.iter().any(|s| s.id_str() == "MSA"));
}
#[test]
fn test_generate_ack_ae() {
let content = SampleMessages::adt_a01();
let message = hl7v2::parse(content.as_bytes()).expect("Parse should succeed");
let ack_result = ack(&message, AckCode::AE);
assert!(ack_result.is_ok());
}
#[test]
fn test_generate_ack_ar() {
let content = SampleMessages::adt_a01();
let message = hl7v2::parse(content.as_bytes()).expect("Parse should succeed");
let ack_result = ack(&message, AckCode::AR);
assert!(ack_result.is_ok());
}
#[test]
fn test_ack_contains_original_message_control_id() {
let content = SampleMessages::adt_a01();
let message = hl7v2::parse(content.as_bytes()).expect("Parse should succeed");
let ack_message = ack(&message, AckCode::AA).expect("ACK should generate");
let msa = ack_message.segments.iter().find(|s| s.id_str() == "MSA");
assert!(msa.is_some());
}
}
mod gen_command {
#[test]
fn test_parse_template_yaml() {
let template_yaml = r#"
name: ADT_A01
delims: "^~\\&"
segments:
- "MSH|^~\\&|TestApp|TestFac|ReceivingApp|ReceivingFac|20250128152312||ADT^A01^ADT_A01|ABC123|P|2.5.1"
- "PID|1||123456^^^HOSP^MR||Doe^John"
values: {}
"#;
let result: Result<hl7v2::synthetic::generate::Template, _> =
serde_yaml::from_str(template_yaml);
assert!(
result.is_ok(),
"Failed to parse template YAML: {:?}",
result.err()
);
}
}
mod error_handling {
use super::*;
#[test]
fn test_parse_empty_input_returns_error() {
let result = hl7v2::parse(b"");
assert!(result.is_err());
}
#[test]
fn test_parse_invalid_input_returns_error() {
let result = hl7v2::parse(b"This is not HL7");
assert!(result.is_err());
}
#[test]
fn test_parse_truncated_message_returns_error() {
let result = hl7v2::parse(b"MSH");
assert!(result.is_err());
}
#[test]
fn test_missing_file_error() {
let non_existent = PathBuf::from("/nonexistent/path/file.hl7");
let result = fs::read_to_string(&non_existent);
assert!(result.is_err());
}
#[test]
fn test_invalid_profile_yaml_returns_error() {
let invalid_yaml = "this is not: valid: yaml:::";
let _result = hl7v2::load_profile(invalid_yaml);
}
}
mod mllp_handling {
use super::*;
#[test]
fn test_mllp_wrap() {
let data = b"MSH|^~\\&|Test\r";
let wrapped = hl7v2::wrap_mllp(data);
assert_eq!(wrapped[0], 0x0B); assert!(wrapped[..].ends_with(&[0x1C, 0x0D])); }
#[test]
fn test_mllp_parse_and_unwrap() {
let content = SampleMessages::adt_a01();
let mut mllp_bytes = vec![0x0B];
mllp_bytes.extend_from_slice(content.as_bytes());
mllp_bytes.push(0x1C);
mllp_bytes.push(0x0D);
let message = hl7v2::parse_mllp(&mllp_bytes).expect("Should parse MLLP");
assert!(!message.segments.is_empty());
}
#[test]
fn test_mllp_write() {
let content = SampleMessages::adt_a01();
let message = hl7v2::parse(content.as_bytes()).expect("Parse should succeed");
let mllp_bytes = hl7v2::write_mllp(&message);
assert_eq!(mllp_bytes[0], 0x0B);
assert!(mllp_bytes[..].ends_with(&[0x1C, 0x0D]));
}
}
mod interactive_mode {
#[test]
fn test_interactive_help_command() {
let commands = ["parse", "norm", "val", "ack", "gen", "help", "exit"];
for cmd in commands {
assert!(!cmd.is_empty());
}
}
#[test]
fn test_interactive_parse_command_parsing() {
let input = "parse test.hl7 --json --summary";
let parts: Vec<&str> = input.split_whitespace().collect();
assert_eq!(parts[0], "parse");
assert_eq!(parts[1], "test.hl7");
assert!(parts.contains(&"--json"));
assert!(parts.contains(&"--summary"));
}
}
mod output_formatting {
use super::*;
#[test]
fn test_json_pretty_format() {
let content = SampleMessages::adt_a01();
let message = hl7v2::parse(content.as_bytes()).expect("Parse should succeed");
let json_value = hl7v2::to_json(&message);
let pretty = serde_json::to_string_pretty(&json_value).expect("Should serialize");
assert!(pretty.contains('\n')); }
#[test]
fn test_json_compact_format() {
let content = SampleMessages::adt_a01();
let message = hl7v2::parse(content.as_bytes()).expect("Parse should succeed");
let json_value = hl7v2::to_json(&message);
let compact = serde_json::to_string(&json_value).expect("Should serialize");
assert!(!compact.contains("\n ")); }
}
mod new_flags {
use super::*;
use hl7v2::{normalize, parse, write};
#[test]
fn test_canonical_delimiters_normalization() {
let content = "MSH|@#$\\&|SendingApp|SendingFac|ReceivingApp|ReceivingFac|20250128152312||ADT^A01|ABC123|P|2.5.1\rPID|1||12345^^^HOSP^MR||Doe^John\r";
let message = parse(content.as_bytes()).expect("Parse should succeed");
let original_bytes = write(&message);
let normalized = normalize(&original_bytes, true).expect("Normalize should succeed");
let normalized_str = String::from_utf8_lossy(&normalized);
assert!(normalized_str.starts_with("MSH|^~\\&|"));
}
#[test]
fn test_canonical_delimiters_preserves_content() {
let content = SampleMessages::adt_a01();
let message = parse(content.as_bytes()).expect("Parse should succeed");
let original_bytes = write(&message);
let normalized = normalize(&original_bytes, true).expect("Normalize should succeed");
let reparsed = parse(&normalized).expect("Reparse should succeed");
assert_eq!(message.segments.len(), reparsed.segments.len());
}
#[test]
fn test_envelope_mllp_wrap() {
let content = SampleMessages::adt_a01();
let message = parse(content.as_bytes()).expect("Parse should succeed");
let output_bytes = write(&message);
let mllp_bytes = hl7v2::wrap_mllp(&output_bytes);
assert_eq!(mllp_bytes[0], 0x0B); assert!(mllp_bytes[..].ends_with(&[0x1C, 0x0D])); }
#[test]
fn test_envelope_and_canonical_combined() {
let content = SampleMessages::adt_a01();
let message = parse(content.as_bytes()).expect("Parse should succeed");
let original_bytes = write(&message);
let normalized = normalize(&original_bytes, true).expect("Normalize should succeed");
let mllp_bytes = hl7v2::wrap_mllp(&normalized);
assert_eq!(mllp_bytes[0], 0x0B);
let content = &mllp_bytes[1..mllp_bytes.len() - 2];
let content_str = String::from_utf8_lossy(content);
assert!(content_str.starts_with("MSH|^~\\&|"));
}
#[test]
fn test_validation_report_json_format() {
let profile_yaml = r#"
message_structure: ADT_A01
version: "2.5.1"
segments:
- id: MSH
constraints:
- path: MSH.1
required: true
"#;
let profile = hl7v2::load_profile(profile_yaml).expect("Profile should load");
let message =
parse(SampleMessages::adt_a01().as_bytes()).expect("Parse should succeed");
let results = hl7v2::validate(&message, &profile);
let report = crate::ValidationReport {
input_file: "test.hl7".to_string(),
profile_file: "profile.yaml".to_string(),
file_size: 100,
segment_count: message.segments.len(),
is_valid: results.is_empty(),
issue_count: results.len(),
issues: results.iter().map(|r| format!("{:?}", r)).collect(),
};
let json = serde_json::to_string_pretty(&report).expect("Should serialize to JSON");
assert!(json.contains("input_file"));
assert!(json.contains("is_valid"));
}
#[test]
fn test_validation_report_yaml_format() {
let profile_yaml = r#"
message_structure: ADT_A01
version: "2.5.1"
segments:
- id: MSH
constraints:
- path: MSH.1
required: true
"#;
let profile = hl7v2::load_profile(profile_yaml).expect("Profile should load");
let message =
parse(SampleMessages::adt_a01().as_bytes()).expect("Parse should succeed");
let results = hl7v2::validate(&message, &profile);
let report = crate::ValidationReport {
input_file: "test.hl7".to_string(),
profile_file: "profile.yaml".to_string(),
file_size: 100,
segment_count: message.segments.len(),
is_valid: results.is_empty(),
issue_count: results.len(),
issues: results.iter().map(|r| format!("{:?}", r)).collect(),
};
let yaml = serde_yaml::to_string(&report).expect("Should serialize to YAML");
assert!(yaml.contains("input_file"));
assert!(yaml.contains("is_valid"));
}
#[test]
fn test_collect_stats_basic() {
use hl7v2::parse;
let content = SampleMessages::adt_a01();
let message = parse(content.as_bytes()).expect("Parse should succeed");
let stats = crate::collect_stats(&message, false);
assert_eq!(stats.segment_count, message.segments.len());
assert!(stats.field_distributions.is_none());
assert!(stats.segments.iter().any(|s| s.segment_id == "MSH"));
assert!(stats.segments.iter().any(|s| s.segment_id == "PID"));
}
#[test]
fn test_collect_stats_with_distributions() {
use hl7v2::parse;
let content = SampleMessages::adt_a01();
let message = parse(content.as_bytes()).expect("Parse should succeed");
let stats = crate::collect_stats(&message, true);
assert!(stats.field_distributions.is_some());
let dists = stats.field_distributions.unwrap();
assert!(!dists.is_empty());
assert!(dists.iter().any(|d| d.path == "MSH.3"));
}
#[test]
fn test_format_stats_report_text() {
let segments = vec![
crate::SegmentStats {
segment_id: "MSH".to_string(),
count: 1,
},
crate::SegmentStats {
segment_id: "PID".to_string(),
count: 1,
},
];
let report = crate::StatsReport {
input_file: "test.hl7".to_string(),
file_size: 100,
segment_count: 2,
segments,
field_distributions: None,
};
let output = crate::format_stats_report(&report, &crate::ReportFormat::Text)
.expect("Should format as text");
assert!(output.contains("Message Statistics:"));
assert!(output.contains("Input file: test.hl7"));
assert!(output.contains("Total segments: 2"));
assert!(output.contains("MSH: 1 occurrence(s)"));
assert!(output.contains("PID: 1 occurrence(s)"));
}
#[test]
fn test_format_stats_report_yaml() {
let report = crate::StatsReport {
input_file: "test.hl7".to_string(),
file_size: 100,
segment_count: 1,
segments: vec![crate::SegmentStats {
segment_id: "MSH".to_string(),
count: 1,
}],
field_distributions: None,
};
let output = crate::format_stats_report(&report, &crate::ReportFormat::Yaml)
.expect("Should format as YAML");
assert!(output.contains("input_file: test.hl7"));
assert!(output.contains("segment_count: 1"));
}
#[test]
fn test_stats_command_execution() {
use crate::ReportFormat;
use crate::stats_command;
let dir = TempDir::new().expect("Failed to create temp dir");
let file_path = create_temp_hl7_file(&dir, "test.hl7");
let result = stats_command(&file_path, false, true, &ReportFormat::Text);
assert!(result.is_ok());
}
#[test]
fn test_command_paths_accept_mllp_inputs_through_facade() {
let dir = TempDir::new().expect("Failed to create temp dir");
let framed = hl7v2::wrap_mllp(SampleMessages::adt_a01().as_bytes());
let mllp_path = create_temp_file(&dir, "message.mllp", &framed);
let output_path = dir.path().join("normalized.hl7");
let profile_path = create_temp_file(
&dir,
"profile.yaml",
br#"
message_structure: ADT_A01
version: "2.5.1"
segments:
- id: MSH
constraints:
- path: MSH.9
required: true
"#,
);
assert!(
crate::parse_command(&mllp_path, false, true, true, true, false, false).is_ok()
);
assert!(
crate::norm_command(&mllp_path, true, &Some(output_path), true, true, false)
.is_ok()
);
assert!(
crate::val_command(
&mllp_path,
&profile_path,
true,
false,
&crate::ReportFormat::Text,
false,
)
.is_ok()
);
assert!(
crate::stats_command(&mllp_path, true, false, &crate::ReportFormat::Text).is_ok()
);
assert!(
crate::ack_command(
&mllp_path,
&crate::AckMode::Original,
&crate::AckCode::AA,
true,
true,
false,
)
.is_ok()
);
}
#[test]
fn test_streaming_mode_flag() {
use crate::Cli;
use clap::CommandFactory;
let schema = Cli::command();
let parse_cmd = schema.get_subcommands().find(|c| c.get_name() == "parse");
assert!(parse_cmd.is_some());
let parse_cmd = parse_cmd.unwrap();
let streaming_arg = parse_cmd
.get_arguments()
.find(|a| a.get_id() == "streaming");
assert!(streaming_arg.is_some());
}
#[test]
fn test_report_format_values() {
use crate::ReportFormat;
let _text = ReportFormat::Text;
let _json = ReportFormat::Json;
let _yaml = ReportFormat::Yaml;
assert_eq!(ReportFormat::default(), ReportFormat::Text);
}
#[test]
fn test_parse_command_has_canonical_delims_flag() {
use crate::Cli;
use clap::CommandFactory;
let schema = Cli::command();
let parse_cmd = schema.get_subcommands().find(|c| c.get_name() == "parse");
assert!(parse_cmd.is_some());
let parse_cmd = parse_cmd.unwrap();
let canonical_arg = parse_cmd
.get_arguments()
.find(|a| a.get_id() == "canonical_delims");
assert!(canonical_arg.is_some());
}
#[test]
fn test_parse_command_has_envelope_flag() {
use crate::Cli;
use clap::CommandFactory;
let schema = Cli::command();
let parse_cmd = schema.get_subcommands().find(|c| c.get_name() == "parse");
assert!(parse_cmd.is_some());
let parse_cmd = parse_cmd.unwrap();
let envelope_arg = parse_cmd.get_arguments().find(|a| a.get_id() == "envelope");
assert!(envelope_arg.is_some());
}
#[test]
fn test_val_command_has_report_flag() {
use crate::Cli;
use clap::CommandFactory;
let schema = Cli::command();
let val_cmd = schema.get_subcommands().find(|c| c.get_name() == "val");
assert!(val_cmd.is_some());
let val_cmd = val_cmd.unwrap();
let report_arg = val_cmd.get_arguments().find(|a| a.get_id() == "report");
assert!(report_arg.is_some());
}
#[test]
fn test_stats_command_exists() {
use crate::Cli;
use clap::CommandFactory;
let schema = Cli::command();
let stats_cmd = schema.get_subcommands().find(|c| c.get_name() == "stats");
assert!(stats_cmd.is_some());
}
#[test]
fn test_stats_command_has_distributions_flag() {
use crate::Cli;
use clap::CommandFactory;
let schema = Cli::command();
let stats_cmd = schema.get_subcommands().find(|c| c.get_name() == "stats");
assert!(stats_cmd.is_some());
let stats_cmd = stats_cmd.unwrap();
let distributions_arg = stats_cmd
.get_arguments()
.find(|a| a.get_id() == "distributions");
assert!(distributions_arg.is_some());
}
}
}