mod common;
use common::starlight_test_provenance;
use nsb::components::starlight::StarlightMap;
use siderust::checksum::sha256;
const CSV: &str = include_str!("data/starlight_fixture_map.csv");
#[test]
fn compact_roundtrip_is_exact_and_deterministic_in_both_orderings() {
for ordering in ["ring", "nested", "RING", "NESTED"] {
let csv = CSV
.replace("ordering=ring", &format!("ordering={ordering}"))
.replace("coordinate_frame=galactic", "coordinate_frame=Galactic");
let original = StarlightMap::from_csv_str(&csv, starlight_test_provenance()).unwrap();
let first = StarlightMap::compact_csv(&csv, starlight_test_provenance()).unwrap();
assert_eq!(
first,
StarlightMap::compact_csv(&csv, starlight_test_provenance()).unwrap()
);
let decoded =
StarlightMap::from_compact_bytes(&first, starlight_test_provenance()).unwrap();
assert_eq!(original, decoded);
assert!(decoded
.pixels()
.iter()
.all(|p| !p.s10_diagnostics_provided && p.total_uncertainty.is_some()));
for (a, b) in original.pixels().iter().zip(decoded.pixels()) {
assert_eq!(
a.integrated.value().to_bits(),
b.integrated.value().to_bits()
);
}
}
}
#[test]
fn compact_rejects_corruption_truncation_unknown_version_and_wrong_lengths() {
let bytes = StarlightMap::compact_csv(CSV, starlight_test_provenance()).unwrap();
for length in [0, 7, 12, 43, bytes.len() - 1] {
assert!(
StarlightMap::from_compact_bytes(&bytes[..length], starlight_test_provenance())
.is_err()
);
}
for offset in [0, 7, 8, 12, bytes.len() / 2, bytes.len() - 1] {
let mut corrupt = bytes.clone();
corrupt[offset] ^= 1;
assert!(StarlightMap::from_compact_bytes(&corrupt, starlight_test_provenance()).is_err());
}
let mut invalid_length = bytes.clone();
invalid_length[8..12].copy_from_slice(&u32::MAX.to_le_bytes());
let end = invalid_length.len() - 32;
let digest = sha256(&invalid_length[..end]);
invalid_length[end..].copy_from_slice(&digest);
assert!(
StarlightMap::from_compact_bytes(&invalid_length, starlight_test_provenance()).is_err()
);
let mut missing_pixel = bytes[..bytes.len() - 64].to_vec();
let digest = sha256(&missing_pixel);
missing_pixel.extend_from_slice(&digest);
assert!(StarlightMap::from_compact_bytes(&missing_pixel, starlight_test_provenance()).is_err());
}
#[test]
fn approved_bundled_default_evaluates_all_four_components() {
use nsb::components::starlight::StarlightProduct;
use nsb::{ComponentMask, NsbEvaluator, PointQuery, Target, DEG};
assert_eq!(
ComponentMask::ALL.contains(ComponentMask::STARLIGHT),
StarlightProduct::bundled_production_available()
);
if !StarlightProduct::bundled_production_available() {
return;
}
let time = tempoch::Time::<tempoch::UTC>::from_chrono(
chrono::DateTime::parse_from_rfc3339("2023-09-04T01:48:00Z")
.unwrap()
.with_timezone(&chrono::Utc),
);
let query = PointQuery::new(
siderust::catalogs::observatories::EL_PARANAL.geodetic(),
time,
Target::new(266.41683 * DEG, -29.00781 * DEG),
);
let first = NsbEvaluator::new().unwrap().evaluate(&query).unwrap();
let second = NsbEvaluator::new().unwrap().evaluate(&query).unwrap();
assert_eq!(first.components.len(), 4);
assert_eq!(first.integrated, second.integrated);
assert_eq!(
first.integrated.value(),
first
.components
.iter()
.map(|c| c.integrated.value())
.sum::<f64>()
);
assert!(
first
.components
.iter()
.find(|c| c.name == "starlight")
.unwrap()
.integrated
.value()
> 0.0
);
}
#[test]
fn compact_rejects_semantic_corruption_even_with_valid_internal_checksum() {
fn resign(bytes: &mut [u8]) {
let end = bytes.len() - 32;
let digest = sha256(&bytes[..end]);
bytes[end..].copy_from_slice(&digest);
}
let original = StarlightMap::compact_csv(CSV, starlight_test_provenance()).unwrap();
let length = u32::from_le_bytes(original[8..12].try_into().unwrap()) as usize;
let header = std::str::from_utf8(&original[12..12 + length]).unwrap();
for (from, to) in [
(
"# coordinate_frame=galactic",
"# coordinate_frame=equatorial",
),
(
"# s10_diagnostics=not_provided",
"# s10_diagnostics=provided",
),
("# nside=1", "# nside=invalid"),
("# nside=1", "# nside=0"),
("# nside=1", "# nside=3"),
("# nside=1", "# nside=2"),
("# nside=1\n", ""),
("# ordering=ring", "# ordering=unknown"),
] {
let altered = header.replace(from, to);
assert_ne!(header, altered);
let mut bytes = original[..8].to_vec();
bytes.extend_from_slice(&(altered.len() as u32).to_le_bytes());
bytes.extend_from_slice(altered.as_bytes());
bytes.extend_from_slice(&original[12 + length..]);
resign(&mut bytes);
assert!(StarlightMap::from_compact_bytes(&bytes, starlight_test_provenance()).is_err());
}
for (field, value) in [
(0, f64::NAN),
(0, f64::INFINITY),
(0, -1.0),
(1, 999.0),
(2, -1.0),
] {
let mut bytes = original.clone();
let offset = 12 + length + field * 8;
bytes[offset..offset + 8].copy_from_slice(&value.to_le_bytes());
resign(&mut bytes);
assert!(StarlightMap::from_compact_bytes(&bytes, starlight_test_provenance()).is_err());
}
let mut invalid_utf8 = original;
invalid_utf8[12] = 255;
resign(&mut invalid_utf8);
assert!(StarlightMap::from_compact_bytes(&invalid_utf8, starlight_test_provenance()).is_err());
}