use crate::moon::*;
use chrono::{TimeZone, Utc};
use crate::julian_date;
#[test]
fn test_moon_position_range() {
let dates = [
Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2024, 4, 15, 12, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2024, 7, 30, 18, 0, 0).unwrap(),
Utc.with_ymd_and_hms(2024, 11, 11, 6, 0, 0).unwrap(),
];
for dt in dates {
let (lon, lat) = moon_position(dt);
assert!((0.0..360.0).contains(&lon), "Longitude out of range: {}", lon);
assert!((-6.0..=6.0).contains(&lat), "Latitude out of range: {}", lat);
}
}
#[test]
fn test_moon_phases_cycle() {
let start = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
let mut prev_phase = moon_phase_angle(start);
for hours in 1..24 {
let dt = start + chrono::Duration::hours(hours);
let phase = moon_phase_angle(dt);
if (phase - prev_phase).abs() < 300.0 { assert!(phase > prev_phase, "Phase not increasing: {} -> {}", prev_phase, phase);
}
prev_phase = phase;
}
}
#[test]
fn test_moon_distance_extremes() {
let mut min_dist = f64::MAX;
let mut max_dist = f64::MIN;
let start = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
for half_days in 0..60 {
let dt = start + chrono::Duration::hours(half_days * 12);
let dist = moon_distance(dt);
min_dist = min_dist.min(dist);
max_dist = max_dist.max(dist);
}
assert!(min_dist > 350000.0 && min_dist < 365000.0);
assert!(max_dist > 395000.0 && max_dist < 410000.0);
}
#[test]
fn test_phase_illumination_consistency() {
let dates = [
(0.0, 0.0), (90.0, 50.0), (180.0, 100.0), (270.0, 50.0), ];
for (expected_phase, expected_illum) in dates {
let base = Utc.with_ymd_and_hms(2024, 1, 11, 12, 0, 0).unwrap(); let offset_days = (expected_phase / 360.0 * 29.53) as i64; let dt = base + chrono::Duration::days(offset_days);
let illum = moon_illumination(dt);
if expected_illum == 0.0 {
assert!(illum < 10.0);
} else if expected_illum == 100.0 {
assert!(illum > 90.0);
} else {
assert!(illum > 30.0 && illum < 70.0);
}
}
}
#[test]
fn test_moon_edge_cases() {
let dt = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
let phase = moon_phase_angle(dt);
assert!((0.0..360.0).contains(&phase));
}
#[test]
fn test_moon_distance_formula() {
let dt = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
let dist = moon_distance(dt);
assert!(dist > 350000.0); assert!(dist < 410000.0); }
#[test]
fn test_moon_negative_normalizations() {
let dt = Utc.with_ymd_and_hms(1900, 1, 1, 0, 0, 0).unwrap();
let (lon, _) = moon_position(dt);
assert!((0.0..360.0).contains(&lon));
let phase = moon_phase_angle(dt);
assert!((0.0..360.0).contains(&phase));
let (ra, _) = moon_equatorial(dt);
assert!((0.0..360.0).contains(&ra));
}
#[test]
fn test_moon_phase_edge_cases() {
let base_dt = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
for i in 0..30 {
let dt = base_dt + chrono::Duration::days(i);
let name = moon_phase_name(dt);
assert!(["New Moon", "Waxing Crescent", "First Quarter", "Waxing Gibbous",
"Full Moon", "Waning Gibbous", "Last Quarter", "Waning Crescent"]
.contains(&name));
}
}
#[test]
fn test_moon_known_phases_erfa() {
let new_moon = Utc.with_ymd_and_hms(2024, 1, 11, 11, 57, 0).unwrap();
let phase = moon_phase_angle(new_moon);
let illum = moon_illumination(new_moon);
assert!(!(5.0..=355.0).contains(&phase), "New moon phase angle: {}", phase);
assert!(illum < 2.0, "New moon illumination: {}", illum);
let full_moon = Utc.with_ymd_and_hms(2024, 1, 25, 17, 54, 0).unwrap();
let phase = moon_phase_angle(full_moon);
let illum = moon_illumination(full_moon);
assert!(phase > 175.0 && phase < 185.0, "Full moon phase angle: {}", phase);
assert!(illum > 98.0, "Full moon illumination: {}", illum);
let first_quarter = Utc.with_ymd_and_hms(2024, 1, 18, 3, 53, 0).unwrap();
let phase = moon_phase_angle(first_quarter);
let illum = moon_illumination(first_quarter);
assert!(phase > 85.0 && phase < 95.0, "First quarter phase angle: {}", phase);
assert!(illum > 45.0 && illum < 55.0, "First quarter illumination: {}", illum);
}
#[test]
fn test_moon_perigee_apogee_erfa() {
let perigee = Utc.with_ymd_and_hms(2024, 1, 13, 10, 0, 0).unwrap();
let dist_perigee = moon_distance(perigee);
assert!(dist_perigee > 360000.0 && dist_perigee < 365000.0,
"Perigee distance: {}", dist_perigee);
let apogee = Utc.with_ymd_and_hms(2024, 1, 29, 8, 0, 0).unwrap();
let dist_apogee = moon_distance(apogee);
assert!(dist_apogee > 404000.0 && dist_apogee < 407000.0,
"Apogee distance: {}", dist_apogee);
}
#[test]
fn test_moon_libration_range() {
let mut min_lat: f64 = 90.0;
let mut max_lat: f64 = -90.0;
let start = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
for hours in (0..720).step_by(6) {
let dt = start + chrono::Duration::hours(hours);
let (_, lat) = moon_position(dt);
min_lat = min_lat.min(lat);
max_lat = max_lat.max(lat);
}
assert!(min_lat > -6.0 && min_lat < -4.0, "Min latitude: {}", min_lat);
assert!(max_lat > 4.0 && max_lat < 6.0, "Max latitude: {}", max_lat);
}
#[test]
fn test_moon_synodic_period() {
let start_new_moon = Utc.with_ymd_and_hms(2024, 1, 11, 11, 57, 0).unwrap();
let next_new_moon = Utc.with_ymd_and_hms(2024, 2, 9, 23, 0, 0).unwrap();
let start_phase = moon_phase_angle(start_new_moon);
let end_phase = moon_phase_angle(next_new_moon);
assert!(!(5.0..=355.0).contains(&start_phase));
assert!(!(5.0..=355.0).contains(&end_phase));
let period_days = julian_date(next_new_moon) - julian_date(start_new_moon);
assert!(period_days > 29.0 && period_days < 30.0, "Synodic period: {} days", period_days);
}
#[test]
fn test_moon_equatorial_precision() {
let dt = Utc.with_ymd_and_hms(2024, 1, 15, 0, 0, 0).unwrap();
let (ra, dec) = moon_equatorial(dt);
assert!((0.0..360.0).contains(&ra), "RA: {}", ra);
assert!((-30.0..=30.0).contains(&dec), "Dec: {}", dec);
}
#[test]
fn test_moon_velocity_continuity() {
let start = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
let mut prev_lon = moon_position(start).0;
for minutes in 1..60 {
let dt = start + chrono::Duration::minutes(minutes * 10);
let (lon, _) = moon_position(dt);
let mut diff = lon - prev_lon;
if diff < -180.0 {
diff += 360.0;
} else if diff > 180.0 {
diff -= 360.0;
}
assert!(diff > 0.05 && diff < 0.15,
"Unexpected motion: {} degrees in 10 minutes", diff);
prev_lon = lon;
}
}
#[test]
fn test_moon_ecliptic_inclination() {
let mut count_within_orbit = 0;
let total_samples = 100;
let start = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
for i in 0..total_samples {
let dt = start + chrono::Duration::hours(i * 7); let (_, lat) = moon_position(dt);
if lat.abs() <= 5.2 {
count_within_orbit += 1;
}
}
assert!(count_within_orbit > total_samples * 7 / 10,
"Only {} of {} samples within orbital plane", count_within_orbit, total_samples);
}