use std::panic::AssertUnwindSafe;
use std::time::Duration;
use astrodyn_bevy::prelude::*;
use astrodyn_bevy::recipes::{earth, orbital_elements, vehicle};
use bevy::prelude::*;
fn build_app() -> (App, Entity) {
let mut app = App::new();
app.add_plugins(MinimalPlugins)
.insert_resource(Time::<Fixed>::from_seconds(10.0))
.insert_resource(IntegrationDtR(10.0))
.add_plugins(AstrodynPlugin);
let earth_recipe = earth::point_mass();
let earth_mu = earth_recipe.source.mu;
let earth = app
.world_mut()
.spawn((
Name::new("Earth"),
GravitySourceC(earth_recipe.source),
SourceInertialPositionC::default(),
TranslationalStateC::<Earth>::default(),
))
.id();
let cfg = VehicleBuilder::new()
.from_orbital_elements(orbital_elements::iss(), earth_mu.m3_per_s2())
.three_dof_point_mass(vehicle::iss_mass())
.rk4()
.gravity(GravityControl::new_spherical(
0_usize,
GravityGradient::Skip,
))
.build();
let mut commands_state = bevy::ecs::system::SystemState::<Commands>::new(app.world_mut());
let mut commands = commands_state.get_mut(app.world_mut());
let _ = cfg.spawn_bevy::<astrodyn::Earth>(&mut commands, &[earth]);
commands_state.apply(app.world_mut());
app.update();
for _ in 0..2 {
app.world_mut()
.resource_mut::<Time<Fixed>>()
.advance_by(Duration::from_secs_f64(10.0));
app.world_mut().run_schedule(FixedUpdate);
}
(app, earth)
}
#[test]
fn validation_fires_for_body_added_after_startup() {
let (mut app, earth) = build_app();
let earth_mu = earth::point_mass().source.mu;
let bogus_cfg = VehicleBuilder::new()
.from_orbital_elements(orbital_elements::iss(), earth_mu.m3_per_s2())
.three_dof_point_mass(vehicle::iss_mass())
.rk4()
.gravity(GravityControl::new_nonspherical(
0_usize,
4,
4,
GravityGradient::Skip,
))
.build();
let mut commands_state = bevy::ecs::system::SystemState::<Commands>::new(app.world_mut());
let mut commands = commands_state.get_mut(app.world_mut());
let _ = bogus_cfg.spawn_bevy::<astrodyn::Earth>(&mut commands, &[earth]);
commands_state.apply(app.world_mut());
let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
app.world_mut()
.resource_mut::<Time<Fixed>>()
.advance_by(Duration::from_secs_f64(10.0));
app.world_mut().run_schedule(FixedUpdate);
}));
let panic = result.expect_err(
"expected validation to panic on the late-added body's bad GravityControl, \
but the FixedUpdate schedule completed without error — \
the Added<GravityControlsC> trigger is not firing for late additions",
);
let msg = panic
.downcast_ref::<String>()
.map(String::as_str)
.or_else(|| panic.downcast_ref::<&'static str>().copied())
.unwrap_or("<non-string panic payload>");
assert!(
msg.contains("Non-spherical gravity")
|| msg.contains("PointMass")
|| msg.contains("SphericalHarmonics"),
"panic message did not mention gravity validation: {msg}"
);
}