use jyotish::{JyotishError, Planet, PlanetaryPosition, calendar};
#[test]
fn error_type_exists() {
let err = JyotishError::InvalidParameter("test".into());
assert!(err.to_string().contains("test"));
}
#[test]
fn all_error_variants() {
let _ = JyotishError::InvalidParameter("a".into());
let _ = JyotishError::MathError("b".into());
let _ = JyotishError::DateError("c".into());
let _ = JyotishError::EphemerisError("d".into());
let _ = JyotishError::Io(std::io::Error::other("e"));
}
#[test]
fn planet_display_all() {
let planets = [
Planet::Sun,
Planet::Moon,
Planet::Mercury,
Planet::Venus,
Planet::Mars,
Planet::Jupiter,
Planet::Saturn,
Planet::Uranus,
Planet::Neptune,
Planet::Pluto,
];
for p in &planets {
let s = p.to_string();
assert!(!s.is_empty(), "{:?} has empty display", p);
}
}
#[test]
fn planet_serde_roundtrip() {
let planet = Planet::Saturn;
let json = serde_json::to_string(&planet).unwrap();
let restored: Planet = serde_json::from_str(&json).unwrap();
assert_eq!(restored, planet);
}
#[test]
fn position_serde_roundtrip() {
let pos = PlanetaryPosition::new(Planet::Venus, 267.5, -3.1, 0.723, 1711324800);
let json = serde_json::to_string(&pos).unwrap();
let restored: PlanetaryPosition = serde_json::from_str(&json).unwrap();
assert_eq!(restored.planet, Planet::Venus);
assert!((restored.longitude_deg - 267.5).abs() < f64::EPSILON);
assert!((restored.latitude_deg - (-3.1)).abs() < f64::EPSILON);
assert!((restored.distance_au - 0.723).abs() < f64::EPSILON);
assert_eq!(restored.timestamp, 1711324800);
}
#[test]
fn calendar_gregorian_jdn_known_dates() {
assert_eq!(calendar::gregorian_to_jdn(2000, 1, 1).unwrap(), 2_451_545);
assert_eq!(calendar::gregorian_to_jdn(1970, 1, 1).unwrap(), 2_440_588);
assert_eq!(calendar::gregorian_to_jdn(1858, 11, 17).unwrap(), 2_400_001); }
#[test]
fn calendar_unix_jd_consistency() {
let jd_from_unix = calendar::unix_to_jd(0);
let jd_from_greg = calendar::gregorian_to_jd(1970, 1, 1, 0, 0, 0.0).unwrap();
assert!((jd_from_unix - jd_from_greg).abs() < 1e-10);
}
#[test]
fn calendar_sidereal_time_range() {
for day_offset in 0..365 {
let jd = 2_451_545.0 + day_offset as f64;
let gmst = calendar::gmst_degrees(jd);
assert!(
(0.0..360.0).contains(&gmst),
"GMST {gmst} out of range at JD {jd}"
);
}
}
#[test]
fn calendar_jdn_roundtrip_wide_range() {
for year in (-1000..=3000).step_by(500) {
for month in [1, 6, 12] {
let jdn = calendar::gregorian_to_jdn(year, month, 1).unwrap();
let (y, m, d) = calendar::jdn_to_gregorian(jdn);
assert_eq!(
(y, m, d),
(year, month, 1),
"roundtrip failed for {year}-{month}-1"
);
}
}
}
mod apparent_pipeline {
use jyotish::{Planet, PositionType, apparent, delta_t};
const JD_TT: f64 = 2_451_545.0;
#[test]
fn apparent_vs_geometric_differs_for_planets() {
for planet in [Planet::Mars, Planet::Jupiter, Planet::Saturn] {
let geo = apparent::geometric_position(planet, JD_TT).unwrap();
let app = apparent::apparent_position(planet, JD_TT).unwrap();
assert_eq!(geo.position_type, PositionType::Geometric);
assert_eq!(app.position_type, PositionType::Apparent);
let diff = (geo.position.longitude_deg - app.position.longitude_deg).abs();
let diff = if diff > 180.0 { 360.0 - diff } else { diff };
assert!(
diff > 0.0001 && diff < 0.05,
"{planet}: apparent-geometric diff = {diff}°"
);
}
}
#[test]
fn apparent_sun_is_tagged() {
let pos = apparent::apparent_sun(JD_TT);
assert_eq!(pos.position_type, PositionType::Apparent);
assert_eq!(pos.position.planet, Planet::Sun);
}
#[test]
fn apparent_moon_has_nutation() {
let geo_lon = jyotish::moon::lunar_longitude(JD_TT);
let app = apparent::apparent_moon(JD_TT);
let diff = (app.position.longitude_deg - geo_lon).abs();
assert!(diff > 0.0001 && diff < 0.02, "moon nutation diff = {diff}°");
}
#[test]
fn delta_t_roundtrip_stability() {
let mut jd = JD_TT;
for _ in 0..10 {
jd = delta_t::ut1_to_tt(delta_t::tt_to_ut1(jd));
}
assert!(
(jd - JD_TT).abs() < 1e-6,
"10-cycle roundtrip drift = {} days",
(jd - JD_TT).abs()
);
}
#[test]
fn full_pipeline_lunar_parallax() {
let observer = jyotish::parallax::Observer::new(51.5, -0.1, 0.0);
let corrected = jyotish::parallax::correct_lunar_position(JD_TT, &observer).unwrap();
let geo_lon = jyotish::moon::lunar_longitude(JD_TT);
let diff = (corrected.longitude_deg - geo_lon).abs();
assert!(diff > 0.01 && diff < 2.0, "parallax shift = {diff}°");
}
}
mod edge_cases {
use jyotish::Planet;
const JD_J2000: f64 = 2_451_545.0;
#[test]
fn ecliptic_to_equatorial_near_poles() {
let (ra, dec) = jyotish::coords::ecliptic_to_equatorial(45.0, 89.999, 23.44);
assert!(ra.is_finite(), "RA is not finite at near-pole");
assert!(dec.is_finite(), "Dec is not finite at near-pole");
let (ra2, dec2) = jyotish::coords::ecliptic_to_equatorial(45.0, -89.999, 23.44);
assert!(ra2.is_finite());
assert!(dec2.is_finite());
}
#[test]
fn ecliptic_to_equatorial_at_exact_pole() {
let (ra, dec) = jyotish::coords::ecliptic_to_equatorial(0.0, 90.0, 23.44);
assert!(ra.is_finite());
assert!((dec - (90.0 - 23.44)).abs() < 0.01, "dec = {dec}");
}
#[test]
fn house_systems_at_various_latitudes() {
use jyotish::house::{HouseSystem, compute_houses};
let lst = 100.0; let eps = 23.44;
let h = compute_houses(HouseSystem::Placidus, lst, 0.0, eps).unwrap();
assert_eq!(h.cusps.len(), 12);
let h = compute_houses(HouseSystem::Placidus, lst, 45.0, eps).unwrap();
assert_eq!(h.cusps.len(), 12);
assert!(compute_houses(HouseSystem::Placidus, lst, 70.0, eps).is_err());
let h = compute_houses(HouseSystem::WholeSign, lst, 85.0, eps).unwrap();
assert_eq!(h.cusps.len(), 12);
}
#[test]
fn riseset_circumpolar_check() {
let rst = jyotish::riseset::rise_set_transit(Planet::Sun, 2000, 6, 21, 70.0, 25.0).unwrap();
assert!(
rst.rise.is_none() || rst.set.is_none() || rst.rise.is_some(),
"expected circumpolar or normal behavior at 70°N midsummer"
);
assert!(rst.transit.is_some(), "transit should always exist");
}
#[test]
fn all_vsop87_planets_in_range() {
for planet in [
Planet::Mercury,
Planet::Venus,
Planet::Mars,
Planet::Jupiter,
Planet::Saturn,
Planet::Uranus,
Planet::Neptune,
] {
let (lon, lat, r) = jyotish::vsop87::planet_heliocentric(planet, JD_J2000).unwrap();
assert!((0.0..360.0).contains(&lon), "{planet} lon = {lon}");
assert!(lat.abs() < 15.0, "{planet} lat = {lat}");
assert!(r > 0.3 && r < 35.0, "{planet} r = {r}");
}
}
#[test]
fn apparent_position_output_ranges() {
for planet in [Planet::Mars, Planet::Jupiter, Planet::Saturn] {
let pos = jyotish::apparent::apparent_position(planet, JD_J2000).unwrap();
assert!(
(0.0..360.0).contains(&pos.position.longitude_deg),
"{planet} apparent lon = {}",
pos.position.longitude_deg
);
assert!(
pos.position.latitude_deg.abs() < 15.0,
"{planet} apparent lat = {}",
pos.position.latitude_deg
);
}
}
#[test]
fn zodiac_sign_boundaries() {
use jyotish::zodiac::tropical_sign;
let sp = tropical_sign(0.0);
assert_eq!(format!("{}", sp.sign), "Aries");
assert!(sp.degrees_in_sign.abs() < 0.001);
let sp30 = tropical_sign(30.0);
assert_eq!(format!("{}", sp30.sign), "Taurus");
let sp359 = tropical_sign(359.99);
assert_eq!(format!("{}", sp359.sign), "Pisces");
assert!((sp359.degrees_in_sign - 29.99).abs() < 0.01);
}
#[test]
fn aspect_near_wrap_boundary() {
let sep = jyotish::aspect::angular_separation(359.0, 1.0);
assert!((sep - 2.0).abs() < 0.001);
let sep2 = jyotish::aspect::angular_separation(1.0, 359.0);
assert!((sep2 - 2.0).abs() < 0.001);
}
#[test]
fn event_search_all_seasons() {
use jyotish::event::{Season, next_season};
for season in [
Season::VernalEquinox,
Season::SummerSolstice,
Season::AutumnalEquinox,
Season::WinterSolstice,
] {
let jd = next_season(season, JD_J2000).unwrap();
assert!(jd > JD_J2000, "{season} should be after J2000");
assert!(jd < JD_J2000 + 400.0, "{season} should be within a year");
}
}
#[test]
fn nutation_at_distant_epochs() {
let far_past = JD_J2000 - 365_250.0;
let far_future = JD_J2000 + 365_250.0;
let (dpsi, deps) = jyotish::nutation::nutation_components(far_past);
assert!(dpsi.is_finite() && deps.is_finite());
assert!(dpsi.abs() < 25.0 && deps.abs() < 20.0);
let (dpsi2, deps2) = jyotish::nutation::nutation_components(far_future);
assert!(dpsi2.is_finite() && deps2.is_finite());
}
}