mod common;
use std::io::Cursor;
use common::kitchen_sink;
use pretty_assertions::assert_eq;
use rdml_qpcr::*;
#[test]
fn zip_round_trip_preserves_document_and_members() {
let mut file = RdmlFile::new(kitchen_sink());
file.insert_member("rapidx_thermals.bin", vec![0u8, 1, 2, 3, 255]);
let mut table = PartitionTable::new();
table
.push_column(
"HBV".parse().unwrap(),
vec![
PartitionPoint {
fluor: 3412.32,
score: PartitionScore::Positive,
},
PartitionPoint {
fluor: 239.23,
score: PartitionScore::Negative,
},
],
)
.unwrap();
table
.push_column(
"GAPDH".parse().unwrap(),
vec![
PartitionPoint {
fluor: 121.89,
score: PartitionScore::Negative,
},
PartitionPoint {
fluor: 3459.27,
score: PartitionScore::Undefined,
},
],
)
.unwrap();
file.insert_partition_table("plate1_well96.tsv", &table);
let (bytes, report) = file.to_bytes(RdmlVersion::V1_4).unwrap();
assert!(report.is_lossless());
let back = RdmlFile::from_slice(&bytes).unwrap();
assert_eq!(back.document, file.document);
assert_eq!(back.source_version(), Some(RdmlVersion::V1_4));
assert_eq!(back.read_notes(), &[]);
assert_eq!(
back.member("rapidx_thermals.bin"),
Some(&[0u8, 1, 2, 3, 255][..])
);
let end_pt_table = back
.document
.reactions()
.find_map(|ctx| ctx.react.partitions.as_ref())
.and_then(|p| p.end_pt_table.clone())
.unwrap();
assert_eq!(back.partition_table(&end_pt_table).unwrap(), table);
let (bytes2, _) = back.to_bytes(RdmlVersion::V1_4).unwrap();
let third = RdmlFile::from_slice(&bytes2).unwrap();
assert_eq!(third, back);
}
#[test]
fn bare_xml_is_accepted() {
let (xml, _) = kitchen_sink().to_xml(RdmlVersion::V1_4).unwrap();
let file = RdmlFile::from_slice(xml.as_bytes()).unwrap();
assert_eq!(file.document, kitchen_sink());
assert_eq!(file.source_version(), Some(RdmlVersion::V1_4));
}
#[test]
fn save_and_open_via_filesystem() {
let dir = std::env::temp_dir().join(format!("rdml-qpcr-io-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("out.rdml");
let mut file = RdmlFile::new(kitchen_sink());
file.insert_member(
"partitions/plate1_well96.tsv",
b"HBV\tHBV\n1.0\tp\n".to_vec(),
);
let report = file.save(&path).unwrap(); assert!(!report.is_lossless());
let back = RdmlFile::open(&path).unwrap();
assert_eq!(back.source_version(), Some(RdmlVersion::V1_3));
assert!(back.document.sample("liver 1").is_some());
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn validation_gates_saving() {
let mut doc = kitchen_sink();
doc.samples.clear(); let file = RdmlFile::new(doc);
match file.to_bytes(RdmlVersion::V1_3) {
Err(Error::Validation(report)) => assert!(report.errors().count() > 0),
other => panic!("expected validation error, got {other:?}"),
}
let mut cursor = Cursor::new(Vec::new());
file.write_unchecked(&mut cursor, RdmlVersion::V1_3)
.unwrap();
assert!(!cursor.into_inner().is_empty());
}
#[test]
fn container_validation_checks_partition_table_references() {
let file = RdmlFile::new(kitchen_sink());
let report = file.validate().unwrap_err();
assert!(
report
.errors()
.any(|f| f.path.contains("endPtTable") && f.message.contains("plate1_well96.tsv")),
"{report}"
);
let mut file = file;
file.insert_member(
"partitions/plate1_well96.tsv",
b"HBV\tHBV\n1.0\tp\n".to_vec(),
);
assert!(file.validate().is_ok());
}
#[test]
fn nonstandard_document_member_name_is_tolerated_with_note() {
use std::io::Write as _;
use zip::write::SimpleFileOptions;
let mut zip = zip::ZipWriter::new(Cursor::new(Vec::new()));
let (xml, _) = kitchen_sink().to_xml(RdmlVersion::V1_3).unwrap();
zip.start_file("run_export.xml", SimpleFileOptions::default())
.unwrap();
zip.write_all(xml.as_bytes()).unwrap();
let bytes = zip.finish().unwrap().into_inner();
let file = RdmlFile::from_slice(&bytes).unwrap();
assert!(file.document.sample("liver 1").is_some());
assert!(
file.read_notes()
.iter()
.any(|n| n.message.contains("non-standard member name")),
"{:?}",
file.read_notes()
);
}
#[test]
fn archive_without_document_is_rejected() {
use std::io::Write as _;
use zip::write::SimpleFileOptions;
let mut zip = zip::ZipWriter::new(Cursor::new(Vec::new()));
zip.start_file("readme.txt", SimpleFileOptions::default())
.unwrap();
zip.write_all(b"nothing here").unwrap();
let bytes = zip.finish().unwrap().into_inner();
let err = RdmlFile::from_slice(&bytes).unwrap_err();
assert!(err.to_string().contains("rdml_data.xml"), "{err}");
}