#![expect(
clippy::indexing_slicing,
clippy::uninlined_format_args,
clippy::unwrap_used,
reason = "pre-existing writer test debt moved into hl7v2; cleanup is split from topology collapse"
)]
use super::*;
use crate::model::{Atom, Batch, Comp, Delims, Field, FileBatch, Message, Rep, Segment};
#[test]
fn test_write_empty_message() {
let message = Message::new();
let bytes = write(&message);
assert!(bytes.is_empty() || bytes == b"\r" || bytes.starts_with(b"MSH"));
}
#[test]
fn test_write_minimal_msh() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"MSH",
fields: vec![Field::from_text("^~\\&")],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.starts_with("MSH|"));
assert!(result.contains("^~\\&"));
assert!(result.ends_with('\r'));
}
#[test]
fn test_write_msh_with_fields() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"MSH",
fields: vec![
Field::from_text("^~\\&"),
Field::from_text("SENDAPP"),
Field::from_text("SENDFAC"),
Field::from_text("RECVAPP"),
Field::from_text("RECVFAC"),
Field::from_text("20231225120000"),
Field::from_text(""),
Field::from_text("ADT^A01"),
Field::from_text("MSG00001"),
Field::from_text("P"),
Field::from_text("2.5"),
],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.starts_with("MSH|^~\\&"));
assert!(result.contains("SENDAPP"));
assert!(result.contains("SENDFAC"));
assert!(result.contains("ADT\\S\\A01"));
assert!(result.contains("MSG00001"));
assert!(result.ends_with('\r'));
}
#[test]
fn test_write_single_segment() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![
Field::from_text("1"),
Field::from_text("12345"),
Field::from_text("DOE^JOHN^A"),
],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.starts_with("PID|"));
assert!(result.contains("12345"));
assert!(result.contains("DOE\\S\\JOHN\\S\\A"));
assert!(result.ends_with('\r'));
}
#[test]
fn test_write_multiple_segments() {
let message = Message {
delims: Delims::default(),
segments: vec![
Segment {
id: *b"MSH",
fields: vec![
Field::from_text("^~\\&"),
Field::from_text("APP"),
Field::from_text("FAC"),
],
},
Segment {
id: *b"PID",
fields: vec![Field::from_text("1"), Field::from_text("12345")],
},
Segment {
id: *b"PV1",
fields: vec![Field::from_text("1"), Field::from_text("I")],
},
],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
let segments: Vec<&str> = result.trim_end_matches('\r').split('\r').collect();
assert_eq!(segments.len(), 3);
assert!(segments[0].starts_with("MSH|"));
assert!(segments[1].starts_with("PID|"));
assert!(segments[2].starts_with("PV1|"));
}
#[test]
fn test_write_segment_terminator() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"MSH",
fields: vec![Field::from_text("^~\\&")],
}],
charsets: vec![],
};
let bytes = write(&message);
assert!(bytes.ends_with(b"\r"));
}
#[test]
fn test_write_single_repetition() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![
Field::from_text("1"),
Field {
reps: vec![Rep::from_text("VALUE")],
},
],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("VALUE"));
assert!(!result.contains('~')); }
#[test]
fn test_write_multiple_repetitions() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![
Field::from_text("1"),
Field {
reps: vec![
Rep::from_text("FIRST"),
Rep::from_text("SECOND"),
Rep::from_text("THIRD"),
],
},
],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("FIRST~SECOND~THIRD"));
}
#[test]
fn test_write_empty_repetition() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![
Field::from_text("1"),
Field {
reps: vec![Rep::from_text(""), Rep::from_text("VALUE")],
},
],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("~VALUE"));
}
#[test]
fn test_write_single_component() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![
Field::from_text("1"),
Field {
reps: vec![Rep {
comps: vec![Comp::from_text("SINGLE")],
}],
},
],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("SINGLE"));
assert!(!result.contains('^')); }
#[test]
fn test_write_multiple_components() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![
Field::from_text("1"),
Field {
reps: vec![Rep {
comps: vec![
Comp::from_text("DOE"),
Comp::from_text("JOHN"),
Comp::from_text("A"),
],
}],
},
],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("DOE^JOHN^A"));
}
#[test]
fn test_write_components_with_repetitions() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![
Field::from_text("1"),
Field {
reps: vec![
Rep {
comps: vec![Comp::from_text("DOE"), Comp::from_text("JOHN")],
},
Rep {
comps: vec![Comp::from_text("SMITH"), Comp::from_text("JANE")],
},
],
},
],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("DOE^JOHN~SMITH^JANE"));
}
#[test]
fn test_write_single_subcomponent() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![
Field::from_text("1"),
Field {
reps: vec![Rep {
comps: vec![Comp {
subs: vec![Atom::text("SINGLE")],
}],
}],
},
],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("SINGLE"));
assert!(!result.contains('&')); }
#[test]
fn test_write_multiple_subcomponents() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"OBX",
fields: vec![
Field::from_text("1"),
Field::from_text("ST"),
Field::from_text("TEST"),
Field {
reps: vec![Rep {
comps: vec![Comp {
subs: vec![
Atom::text("FIRST"),
Atom::text("SECOND"),
Atom::text("THIRD"),
],
}],
}],
},
],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("FIRST&SECOND&THIRD"));
}
#[test]
fn test_write_escape_field_separator() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![Field::from_text("test|value")],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("test\\F\\value"));
assert!(!result.contains("test|value"));
}
#[test]
fn test_write_escape_component_separator() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![Field::from_text("test^value")],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("test\\S\\value"));
}
#[test]
fn test_write_escape_repetition_separator() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![Field::from_text("test~value")],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("test\\R\\value"));
}
#[test]
fn test_write_escape_subcomponent_separator() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![Field::from_text("test&value")],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("test\\T\\value"));
}
#[test]
fn test_write_escape_escape_character() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![Field::from_text("test\\value")],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("test\\E\\value"));
}
#[test]
fn test_write_escape_multiple_delimiters() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![Field::from_text("a|b^c~d&e\\f")],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("a\\F\\b\\S\\c\\R\\d\\T\\e\\E\\f"));
}
#[test]
fn test_write_no_escape_needed() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![Field::from_text("normal text 123")],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("normal text 123"));
assert!(!result.contains('\\'));
}
#[test]
fn test_write_mllp_basic() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"MSH",
fields: vec![Field::from_text("^~\\&")],
}],
charsets: vec![],
};
let framed = write_mllp(&message);
assert_eq!(framed[0], crate::transport::mllp::MLLP_START);
assert_eq!(framed[framed.len() - 2], crate::transport::mllp::MLLP_END_1);
assert_eq!(framed[framed.len() - 1], crate::transport::mllp::MLLP_END_2);
}
#[test]
fn test_write_mllp_contains_message() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"MSH",
fields: vec![Field::from_text("^~\\&"), Field::from_text("APP")],
}],
charsets: vec![],
};
let framed = write_mllp(&message);
let content = &framed[1..framed.len() - 2];
assert!(content.starts_with(b"MSH|"));
assert!(content.windows(3).any(|w| w == b"APP"));
}
#[test]
fn test_write_mllp_empty_message() {
let message = Message::new();
let framed = write_mllp(&message);
assert_eq!(framed[0], crate::transport::mllp::MLLP_START);
assert_eq!(framed[framed.len() - 2], crate::transport::mllp::MLLP_END_1);
assert_eq!(framed[framed.len() - 1], crate::transport::mllp::MLLP_END_2);
}
#[test]
fn test_write_batch_empty() {
let batch = Batch::default();
let bytes = write_batch(&batch);
assert!(bytes.is_empty());
}
#[test]
fn test_write_batch_single_message() {
let mut batch = Batch::default();
batch.messages.push(Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"MSH",
fields: vec![Field::from_text("^~\\&")],
}],
charsets: vec![],
});
let bytes = write_batch(&batch);
let result = String::from_utf8(bytes).unwrap();
assert!(result.starts_with("MSH|"));
}
#[test]
fn test_write_batch_multiple_messages() {
let mut batch = Batch::default();
for i in 0..3 {
batch.messages.push(Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"MSH",
fields: vec![
Field::from_text("^~\\&"),
Field::from_text(format!("MSG{}", i)),
],
}],
charsets: vec![],
});
}
let bytes = write_batch(&batch);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("MSG0"));
assert!(result.contains("MSG1"));
assert!(result.contains("MSG2"));
}
#[test]
fn test_write_batch_with_header() {
let mut batch = Batch {
header: Some(Segment {
id: *b"BHS",
fields: vec![
Field::from_text("^~\\&"),
Field::from_text("SENDAPP"),
Field::from_text("SENDFAC"),
],
}),
..Default::default()
};
batch.messages.push(Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"MSH",
fields: vec![Field::from_text("^~\\&")],
}],
charsets: vec![],
});
let bytes = write_batch(&batch);
let result = String::from_utf8(bytes).unwrap();
assert!(result.starts_with("BHS|"));
assert!(result.contains("MSH|"));
}
#[test]
fn test_write_batch_with_trailer() {
let mut batch = Batch::default();
batch.messages.push(Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"MSH",
fields: vec![Field::from_text("^~\\&")],
}],
charsets: vec![],
});
batch.trailer = Some(Segment {
id: *b"BTS",
fields: vec![Field::from_text("1")], });
let bytes = write_batch(&batch);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("MSH|"));
assert!(result.contains("BTS|"));
}
#[test]
fn test_write_file_batch_empty() {
let file_batch = FileBatch::default();
let bytes = write_file_batch(&file_batch);
assert!(bytes.is_empty());
}
#[test]
fn test_write_file_batch_with_header() {
let file_batch = FileBatch {
header: Some(Segment {
id: *b"FHS",
fields: vec![Field::from_text("^~\\&"), Field::from_text("SENDAPP")],
}),
..Default::default()
};
let bytes = write_file_batch(&file_batch);
let result = String::from_utf8(bytes).unwrap();
assert!(result.starts_with("FHS|"));
}
#[test]
fn test_write_file_batch_with_batches() {
let mut file_batch = FileBatch::default();
let mut batch1 = Batch::default();
batch1.messages.push(Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"MSH",
fields: vec![Field::from_text("^~\\&"), Field::from_text("BATCH1")],
}],
charsets: vec![],
});
let mut batch2 = Batch::default();
batch2.messages.push(Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"MSH",
fields: vec![Field::from_text("^~\\&"), Field::from_text("BATCH2")],
}],
charsets: vec![],
});
file_batch.batches.push(batch1);
file_batch.batches.push(batch2);
let bytes = write_file_batch(&file_batch);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("BATCH1"));
assert!(result.contains("BATCH2"));
}
#[test]
fn test_write_file_batch_full_structure() {
let mut file_batch = FileBatch {
header: Some(Segment {
id: *b"FHS",
fields: vec![Field::from_text("^~\\&"), Field::from_text("FILE")],
}),
..Default::default()
};
let batch = Batch {
header: Some(Segment {
id: *b"BHS",
fields: vec![Field::from_text("^~\\&"), Field::from_text("BATCH")],
}),
messages: vec![Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"MSH",
fields: vec![Field::from_text("^~\\&"), Field::from_text("MSG")],
}],
charsets: vec![],
}],
trailer: Some(Segment {
id: *b"BTS",
fields: vec![Field::from_text("1")],
}),
};
file_batch.batches.push(batch);
file_batch.trailer = Some(Segment {
id: *b"FTS",
fields: vec![Field::from_text("1")],
});
let bytes = write_file_batch(&file_batch);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("FHS|"));
assert!(result.contains("BHS|"));
assert!(result.contains("MSH|"));
assert!(result.contains("BTS|"));
assert!(result.contains("FTS|"));
}
#[test]
fn test_to_json_basic() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"MSH",
fields: vec![Field::from_text("^~\\&"), Field::from_text("APP")],
}],
charsets: vec![],
};
let json = to_json(&message);
assert!(json.is_object());
assert!(json.get("segments").is_some());
}
#[test]
fn test_to_json_string_basic() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"MSH",
fields: vec![Field::from_text("^~\\&")],
}],
charsets: vec![],
};
let json_str = to_json_string(&message);
assert!(json_str.contains("segments"));
assert!(!json_str.contains('\n')); }
#[test]
fn test_to_json_string_pretty() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"MSH",
fields: vec![Field::from_text("^~\\&")],
}],
charsets: vec![],
};
let json_str = to_json_string_pretty(&message);
assert!(json_str.contains("segments"));
assert!(json_str.contains('\n')); }
#[test]
fn test_write_empty_field() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![Field::from_text(""), Field::from_text("VALUE")],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("|VALUE"));
}
#[test]
fn test_write_null_atom() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![Field {
reps: vec![Rep {
comps: vec![Comp {
subs: vec![Atom::null()],
}],
}],
}],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("\"\""));
}
#[test]
fn test_write_unicode() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![Field::from_text("Patient 名前")],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("Patient 名前"));
}
#[test]
fn test_write_long_message() {
let mut segments = Vec::new();
for i in 0..100 {
segments.push(Segment {
id: *b"OBX",
fields: vec![
Field::from_text(format!("{}", i)),
Field::from_text("ST"),
Field::from_text(format!("OBS{}", i)),
Field::from_text(format!("Long observation value {}", "X".repeat(100))),
],
});
}
let message = Message {
delims: Delims::default(),
segments,
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert_eq!(result.matches("OBX|").count(), 100);
}
#[test]
fn test_write_special_characters() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![Field::from_text("Test <>&\"' chars")],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("Test <>&\"' chars") || result.contains("Test <>\\T\\\"' chars"));
}
#[test]
fn test_write_newlines_in_field() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![Field::from_text("Line1\nLine2\rLine3")],
}],
charsets: vec![],
};
let bytes = write(&message);
assert!(bytes.windows(5).any(|w| w == b"Line1"));
assert!(bytes.windows(5).any(|w| w == b"Line2"));
assert!(bytes.windows(5).any(|w| w == b"Line3"));
}
#[test]
fn test_write_custom_delimiters() {
let message = Message {
delims: Delims {
field: '|',
comp: '^',
rep: '~',
esc: '\\',
sub: '&',
},
segments: vec![Segment {
id: *b"PID",
fields: vec![
Field::from_text("1"),
Field {
reps: vec![Rep {
comps: vec![Comp::from_text("DOE"), Comp::from_text("JOHN")],
}],
},
],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("DOE^JOHN"));
}
#[test]
fn test_write_text_atom() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![Field {
reps: vec![Rep {
comps: vec![Comp {
subs: vec![Atom::text("TEXT_VALUE".to_string())],
}],
}],
}],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("TEXT_VALUE"));
}
#[test]
fn test_write_mixed_atoms() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"PID",
fields: vec![Field {
reps: vec![Rep {
comps: vec![Comp {
subs: vec![
Atom::text("TEXT".to_string()),
Atom::null(),
Atom::text("MORE".to_string()),
],
}],
}],
}],
}],
charsets: vec![],
};
let bytes = write(&message);
let result = String::from_utf8(bytes).unwrap();
assert!(result.contains("TEXT"));
assert!(result.contains("\"\""));
assert!(result.contains("MORE"));
}
#[test]
fn test_write_allocation_efficiency() {
let message = Message {
delims: Delims::default(),
segments: vec![Segment {
id: *b"MSH",
fields: vec![
Field::from_text("^~\\&"),
Field::from_text("APP"),
Field::from_text("FAC"),
],
}],
charsets: vec![],
};
let bytes = write(&message);
assert!(bytes.len() < 1000);
}