use bevy::prelude::*;
use bevy_persistence_database::bevy::components::Guid;
use bevy_persistence_database::bevy::plugins::persistence_plugin::CommitStatus;
use bevy_persistence_database::core::db::{
BEVY_PERSISTENCE_DATABASE_BEVY_TYPE_FIELD, BEVY_PERSISTENCE_DATABASE_METADATA_FIELD,
BEVY_PERSISTENCE_DATABASE_VERSION_FIELD,
};
use bevy_persistence_database::core::persist::Persist;
use bevy_persistence_database::core::session::commit_sync;
use crate::common::*;
use bevy_persistence_database_derive::db_matrix_test;
#[db_matrix_test]
fn test_create_new_entity() {
let (db, _container) = setup();
let mut app = setup_test_app(db.clone(), None);
let health_val = Health { value: 100 };
let pos_val = Position { x: 1.0, y: 2.0 };
let entity_id = app.world_mut().spawn((health_val, pos_val)).id();
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("Commit failed during test execution");
let guid = app
.world()
.get::<Guid>(entity_id)
.expect("Entity should have a Guid after commit");
assert!(!guid.id().is_empty(), "Guid should not be empty");
let health_json = run_async(db.fetch_component(TEST_STORE, guid.id(), Health::name()))
.expect("Failed to fetch component from DB")
.expect("Component should exist in DB");
let fetched_health: Health =
serde_json::from_value(health_json).expect("Failed to deserialize Health component");
assert_eq!(
fetched_health.value, 100,
"The health value in the database is incorrect"
);
}
#[db_matrix_test]
fn test_create_new_resource() {
let (db, _container) = setup();
let mut app = setup_test_app(db.clone(), None);
let settings = GameSettings {
difficulty: 0.8,
map_name: "level_1".into(),
};
app.insert_resource(settings);
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("Commit failed during test execution");
let resource_name = GameSettings::name();
let (resource_json, _) = run_async(db.fetch_resource(TEST_STORE, resource_name))
.expect("Failed to fetch resource from DB")
.expect("Resource should exist in DB");
let fetched_settings: GameSettings =
serde_json::from_value(resource_json).expect("Failed to deserialize resource");
assert_eq!(fetched_settings.difficulty, 0.8);
assert_eq!(fetched_settings.map_name, "level_1");
}
#[db_matrix_test]
fn test_update_existing_entity() {
let (db, _container) = setup();
let mut app = setup_test_app(db.clone(), None);
let entity_id = app.world_mut().spawn(Health { value: 100 }).id();
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("Initial commit failed");
let guid = app.world().get::<Guid>(entity_id).unwrap().id().to_string();
let health_json_before = run_async(db.fetch_component(TEST_STORE, &guid, Health::name()))
.expect("Fetch before update failed")
.expect("Component not found before update");
let fetched_health_before: Health =
serde_json::from_value(health_json_before).expect("Deserialization before update failed");
assert_eq!(fetched_health_before.value, 100);
let mut health = app.world_mut().get_mut::<Health>(entity_id).unwrap();
health.value = 50;
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("Second commit failed");
let health_json_after = run_async(db.fetch_component(TEST_STORE, &guid, Health::name()))
.expect("Failed to fetch component from DB")
.expect("Component should exist in DB");
let fetched_health_after: Health =
serde_json::from_value(health_json_after).expect("Failed to deserialize Health component");
assert_eq!(
fetched_health_after.value, 50,
"The health value in the database was not updated correctly"
);
}
#[db_matrix_test]
fn test_update_existing_resource() {
let (db, _container) = setup();
let mut app = setup_test_app(db.clone(), None);
let initial_settings = GameSettings {
difficulty: 0.8,
map_name: "level_1".into(),
};
app.insert_resource(initial_settings);
app.update(); commit_sync(&mut app, db.clone(), TEST_STORE).expect("Initial commit failed");
let mut settings = app.world_mut().resource_mut::<GameSettings>();
settings.difficulty = 0.5;
settings.map_name = "level_2".into();
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("Second commit failed");
let resource_name = GameSettings::name();
let (resource_json_after, _) = run_async(db.fetch_resource(TEST_STORE, resource_name))
.expect("Failed to fetch resource from DB")
.expect("Resource should exist in DB");
let fetched_settings_after: GameSettings =
serde_json::from_value(resource_json_after).expect("Failed to deserialize resource");
assert_eq!(fetched_settings_after.difficulty, 0.5);
assert_eq!(fetched_settings_after.map_name, "level_2");
}
#[db_matrix_test]
fn test_delete_persisted_entity() {
let (db, _container) = setup();
let mut app = setup_test_app(db.clone(), None);
let entity_id = app.world_mut().spawn(Health { value: 100 }).id();
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("Initial commit failed");
let guid = app.world().get::<Guid>(entity_id).unwrap().id().to_string();
let component = run_async(db.fetch_component(TEST_STORE, &guid, Health::name()))
.expect("Fetch should not fail")
.expect("Component should exist after first commit");
assert_eq!(component.get("value").unwrap().as_i64().unwrap(), 100);
app.world_mut().entity_mut(entity_id).despawn();
app.update(); commit_sync(&mut app, db.clone(), TEST_STORE).expect("Delete commit failed");
let component_after_delete = run_async(db.fetch_component(TEST_STORE, &guid, Health::name()))
.expect("Fetch should not fail");
assert!(
component_after_delete.is_none(),
"Component should be gone after delete commit"
);
}
#[db_matrix_test]
fn test_commit_with_no_changes() {
let (db, _container) = setup();
let mut app = setup_test_app(db.clone(), None);
app.world_mut().spawn(Health { value: 100 });
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("Initial commit failed");
commit_sync(&mut app, db.clone(), TEST_STORE).expect("Second commit with no changes failed");
let status = app.world().resource::<CommitStatus>();
assert_eq!(*status, CommitStatus::Idle);
}
#[db_matrix_test]
fn test_add_new_component_to_existing_entity() {
let (db, _container) = setup();
let mut app = setup_test_app(db.clone(), None);
let entity_id = app.world_mut().spawn(Health { value: 100 }).id();
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("Initial commit failed");
let guid = app.world().get::<Guid>(entity_id).unwrap().id().to_string();
let health_before = run_async(db.fetch_component(TEST_STORE, &guid, Health::name()))
.expect("Fetch before update failed");
assert!(health_before.is_some());
let position_before = run_async(db.fetch_component(TEST_STORE, &guid, Position::name()))
.expect("Fetch before update failed");
assert!(position_before.is_none());
app.world_mut()
.entity_mut(entity_id)
.insert(Position { x: 10.0, y: 20.0 });
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("Second commit failed");
let health_after_json = run_async(db.fetch_component(TEST_STORE, &guid, Health::name()))
.expect("Fetch after update failed")
.expect("Health component not found after update");
let health_after: Health =
serde_json::from_value(health_after_json).expect("Health deserialization failed");
assert_eq!(health_after.value, 100, "Health data was not retained");
let position_after_json = run_async(db.fetch_component(TEST_STORE, &guid, Position::name()))
.expect("Fetch after update failed")
.expect("Position component not found after update");
let position_after: Position =
serde_json::from_value(position_after_json).expect("Position deserialization failed");
assert_eq!(position_after.x, 10.0, "Position.x was not added correctly");
assert_eq!(position_after.y, 20.0, "Position.y was not added correctly");
}
#[derive(bevy::prelude::Component)]
struct NonPersisted {
_ignored: bool,
}
#[db_matrix_test]
fn test_commit_entity_with_non_persisted_component() {
let (db, _container) = setup();
let mut app = setup_test_app(db.clone(), None);
let entity_id = app
.world_mut()
.spawn((Health { value: 50 }, NonPersisted { _ignored: true }))
.id();
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("Commit failed");
let guid = app
.world()
.get::<Guid>(entity_id)
.expect("Entity should get a Guid because it has a persisted component")
.id();
let (doc, _) = run_async(db.fetch_document(TEST_STORE, guid))
.expect("Document fetch failed")
.expect("Document should exist in the database");
let obj = doc.as_object().expect("Document value is not an object");
let component_fields: Vec<_> = obj
.keys()
.filter(|k| {
!k.starts_with('_')
&& *k != BEVY_PERSISTENCE_DATABASE_METADATA_FIELD
&& *k != BEVY_PERSISTENCE_DATABASE_VERSION_FIELD
&& *k != BEVY_PERSISTENCE_DATABASE_BEVY_TYPE_FIELD
})
.collect();
assert_eq!(
component_fields.len(),
1,
"Document should only have one persisted component, but it had {}. Document: {:?}",
component_fields.len(),
obj
);
assert!(
obj.contains_key(Health::name()),
"The only persisted component should be Health"
);
let health_val = obj.get(Health::name()).unwrap();
let fetched_health: Health = serde_json::from_value(health_val.clone()).unwrap();
assert_eq!(fetched_health.value, 50);
}
#[db_matrix_test]
fn test_persist_component_with_empty_vec() {
let (db, _container) = setup();
let mut app = setup_test_app(db.clone(), None);
let inventory = Inventory { items: vec![] };
let entity_id = app.world_mut().spawn(inventory).id();
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("Commit failed");
let guid = app
.world()
.get::<Guid>(entity_id)
.expect("Entity should have a Guid after commit")
.id();
let inventory_json = run_async(db.fetch_component(TEST_STORE, guid, Inventory::name()))
.expect("Failed to fetch component from DB")
.expect("Component should exist in DB");
let fetched_inventory: Inventory =
serde_json::from_value(inventory_json).expect("Failed to deserialize Inventory component");
assert!(
fetched_inventory.items.is_empty(),
"The fetched inventory should have an empty items vec"
);
}
#[db_matrix_test]
fn test_persist_component_with_option_none() {
let (db, _container) = setup();
let mut app = setup_test_app(db.clone(), None);
let optional_data = OptionalData { data: None };
let entity_id = app.world_mut().spawn(optional_data).id();
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("Commit failed");
let guid = app
.world()
.get::<Guid>(entity_id)
.expect("Entity should have a Guid after commit")
.id();
let data_json = run_async(db.fetch_component(TEST_STORE, guid, OptionalData::name()))
.expect("Failed to fetch component from DB")
.expect("Component should exist in DB");
let fetched_data: OptionalData =
serde_json::from_value(data_json).expect("Failed to deserialize OptionalData component");
assert!(
fetched_data.data.is_none(),
"The fetched data should be None"
);
}
#[db_matrix_test]
fn test_entity_guid_auto_assigned_on_commit() {
let (db, _container) = setup();
let mut app = setup_test_app(db.clone(), None);
let entity = app.world_mut().spawn(Health { value: 42 }).id();
assert!(
app.world().get::<Guid>(entity).is_none(),
"Entity should not have a Guid before commit"
);
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("commit failed");
let guid = app
.world()
.get::<Guid>(entity)
.expect("library should auto-assign a Guid after commit")
.id();
uuid::Uuid::parse_str(guid).expect("auto-assigned Guid should be a valid UUID");
let stored = run_async(db.fetch_component(TEST_STORE, guid, Health::name()))
.expect("fetch failed")
.expect("component should exist in DB");
let health: Health = serde_json::from_value(stored).expect("deserialize failed");
assert_eq!(health.value, 42);
}
#[db_matrix_test]
fn test_preexisting_guid_is_preserved() {
let (db, _container) = setup();
let mut app = setup_test_app(db.clone(), None);
let custom_guid = "my-custom-id-12345";
let entity = app
.world_mut()
.spawn((Health { value: 77 }, Guid::new(custom_guid.to_string())))
.id();
app.update();
commit_sync(&mut app, db.clone(), TEST_STORE).expect("commit failed");
let guid = app
.world()
.get::<Guid>(entity)
.expect("entity should still have a Guid after commit");
assert_eq!(guid.id(), custom_guid, "pre-existing GUID should be preserved");
let health_json = run_async(db.fetch_component(TEST_STORE, custom_guid, Health::name()))
.expect("fetch failed")
.expect("component should exist under custom key");
let fetched: Health = serde_json::from_value(health_json).expect("deserialize failed");
assert_eq!(fetched.value, 77);
}