using ZB.MOM.WW.CBDD.Bson; using ZB.MOM.WW.CBDD.Shared; using System.Linq; namespace ZB.MOM.WW.CBDD.Tests; /// /// Tests for Source Generator enhancements: /// 1. Property inheritance from base classes (including Id) /// 2. Exclusion of computed getter-only properties /// 3. Recognition of advanced collection types (HashSet, ISet, LinkedList, etc.) /// public class SourceGeneratorFeaturesTests : IDisposable { private readonly string _dbPath; private readonly string _walPath; private readonly Shared.TestDbContext _db; /// /// Initializes a new instance of the class. /// public SourceGeneratorFeaturesTests() { _dbPath = Path.Combine(Path.GetTempPath(), $"test_sg_features_{Guid.NewGuid()}.db"); _walPath = Path.Combine(Path.GetTempPath(), $"test_sg_features_{Guid.NewGuid()}.wal"); _db = new Shared.TestDbContext(_dbPath); } #region Inheritance Tests /// /// Tests derived entity inherits id from base class. /// [Fact] public void DerivedEntity_InheritsId_FromBaseClass() { // Arrange var entity = new DerivedEntity { Name = "Test Entity", Description = "Testing inheritance", CreatedAt = DateTime.UtcNow }; // Act var id = _db.DerivedEntities.Insert(entity); _db.SaveChanges(); var retrieved = _db.DerivedEntities.FindById(id); // Assert retrieved.ShouldNotBeNull(); retrieved.Id.ShouldBe(id); // Id from base class should work retrieved.Name.ShouldBe("Test Entity"); retrieved.Description.ShouldBe("Testing inheritance"); retrieved.CreatedAt.Date.ShouldBe(entity.CreatedAt.Date); // Compare just date part } /// /// Tests derived entity update works with inherited id. /// [Fact] public void DerivedEntity_Update_WorksWithInheritedId() { // Arrange var entity = new DerivedEntity { Name = "Original", Description = "Original Description", CreatedAt = DateTime.UtcNow }; var id = _db.DerivedEntities.Insert(entity); _db.SaveChanges(); // Act var retrieved = _db.DerivedEntities.FindById(id); retrieved.ShouldNotBeNull(); retrieved.Name = "Updated"; retrieved.Description = "Updated Description"; _db.DerivedEntities.Update(retrieved); _db.SaveChanges(); var updated = _db.DerivedEntities.FindById(id); // Assert updated.ShouldNotBeNull(); updated.Id.ShouldBe(id); updated.Name.ShouldBe("Updated"); updated.Description.ShouldBe("Updated Description"); } /// /// Tests derived entity query works with inherited properties. /// [Fact] public void DerivedEntity_Query_WorksWithInheritedProperties() { // Arrange var now = DateTime.UtcNow; var entity1 = new DerivedEntity { Name = "Entity1", CreatedAt = now.AddDays(-2) }; var entity2 = new DerivedEntity { Name = "Entity2", CreatedAt = now.AddDays(-1) }; var entity3 = new DerivedEntity { Name = "Entity3", CreatedAt = now }; _db.DerivedEntities.Insert(entity1); _db.DerivedEntities.Insert(entity2); _db.DerivedEntities.Insert(entity3); _db.SaveChanges(); // Act - Query using inherited property var recent = _db.DerivedEntities.Find(e => e.CreatedAt >= now.AddDays(-1.5)).ToList(); // Assert recent.Count.ShouldBe(2); recent.ShouldContain(e => e.Name == "Entity2"); recent.ShouldContain(e => e.Name == "Entity3"); } #endregion #region Computed Properties Tests /// /// Tests computed properties are not serialized. /// [Fact] public void ComputedProperties_AreNotSerialized() { // Arrange var entity = new EntityWithComputedProperties { FirstName = "John", LastName = "Doe", BirthYear = 1990 }; // Act var id = _db.ComputedPropertyEntities.Insert(entity); _db.SaveChanges(); var retrieved = _db.ComputedPropertyEntities.FindById(id); // Assert retrieved.ShouldNotBeNull(); retrieved.FirstName.ShouldBe("John"); retrieved.LastName.ShouldBe("Doe"); retrieved.BirthYear.ShouldBe(1990); // Computed properties should still work after deserialization retrieved.FullName.ShouldBe("John Doe"); (retrieved.Age >= 34).ShouldBeTrue(); // Born in 1990, so at least 34 in 2024+ retrieved.DisplayInfo.ShouldContain("John Doe"); } /// /// Tests computed properties update does not break. /// [Fact] public void ComputedProperties_UpdateDoesNotBreak() { // Arrange var entity = new EntityWithComputedProperties { FirstName = "Jane", LastName = "Smith", BirthYear = 1985 }; var id = _db.ComputedPropertyEntities.Insert(entity); _db.SaveChanges(); // Act - Update stored properties var retrieved = _db.ComputedPropertyEntities.FindById(id); retrieved.ShouldNotBeNull(); retrieved.FirstName = "Janet"; retrieved.BirthYear = 1986; _db.ComputedPropertyEntities.Update(retrieved); _db.SaveChanges(); var updated = _db.ComputedPropertyEntities.FindById(id); // Assert updated.ShouldNotBeNull(); updated.FirstName.ShouldBe("Janet"); updated.LastName.ShouldBe("Smith"); updated.BirthYear.ShouldBe(1986); updated.FullName.ShouldBe("Janet Smith"); // Computed property reflects new data } #endregion #region Advanced Collections Tests /// /// Tests hash set serializes and deserializes. /// [Fact] public void HashSet_SerializesAndDeserializes() { // Arrange var entity = new EntityWithAdvancedCollections { Name = "Test HashSet" }; entity.Tags.Add("tag1"); entity.Tags.Add("tag2"); entity.Tags.Add("tag3"); // Act var id = _db.AdvancedCollectionEntities.Insert(entity); _db.SaveChanges(); var retrieved = _db.AdvancedCollectionEntities.FindById(id); // Assert retrieved.ShouldNotBeNull(); retrieved.Tags.ShouldNotBeNull(); retrieved.Tags.ShouldBeOfType>(); retrieved.Tags.Count.ShouldBe(3); retrieved.Tags.ShouldContain("tag1"); retrieved.Tags.ShouldContain("tag2"); retrieved.Tags.ShouldContain("tag3"); } /// /// Tests iset serializes and deserializes. /// [Fact] public void ISet_SerializesAndDeserializes() { // Arrange var entity = new EntityWithAdvancedCollections { Name = "Test ISet" }; entity.Numbers.Add(10); entity.Numbers.Add(20); entity.Numbers.Add(30); entity.Numbers.Add(10); // Duplicate - should be ignored by set // Act var id = _db.AdvancedCollectionEntities.Insert(entity); _db.SaveChanges(); var retrieved = _db.AdvancedCollectionEntities.FindById(id); // Assert retrieved.ShouldNotBeNull(); retrieved.Numbers.ShouldNotBeNull(); retrieved.Numbers.ShouldBeAssignableTo>(); retrieved.Numbers.Count.ShouldBe(3); // Only unique values retrieved.Numbers.ShouldContain(10); retrieved.Numbers.ShouldContain(20); retrieved.Numbers.ShouldContain(30); } /// /// Tests linked list serializes and deserializes. /// [Fact] public void LinkedList_SerializesAndDeserializes() { // Arrange var entity = new EntityWithAdvancedCollections { Name = "Test LinkedList" }; entity.History.AddLast("first"); entity.History.AddLast("second"); entity.History.AddLast("third"); // Act var id = _db.AdvancedCollectionEntities.Insert(entity); _db.SaveChanges(); var retrieved = _db.AdvancedCollectionEntities.FindById(id); // Assert retrieved.ShouldNotBeNull(); retrieved.History.ShouldNotBeNull(); // LinkedList may be deserialized as List, then need conversion var historyList = retrieved.History.ToList(); historyList.Count.ShouldBe(3); historyList[0].ShouldBe("first"); historyList[1].ShouldBe("second"); historyList[2].ShouldBe("third"); } /// /// Tests queue serializes and deserializes. /// [Fact] public void Queue_SerializesAndDeserializes() { // Arrange var entity = new EntityWithAdvancedCollections { Name = "Test Queue" }; entity.PendingItems.Enqueue("item1"); entity.PendingItems.Enqueue("item2"); entity.PendingItems.Enqueue("item3"); // Act var id = _db.AdvancedCollectionEntities.Insert(entity); _db.SaveChanges(); var retrieved = _db.AdvancedCollectionEntities.FindById(id); // Assert retrieved.ShouldNotBeNull(); retrieved.PendingItems.ShouldNotBeNull(); retrieved.PendingItems.Count.ShouldBe(3); var items = retrieved.PendingItems.ToList(); items.ShouldContain("item1"); items.ShouldContain("item2"); items.ShouldContain("item3"); } /// /// Tests stack serializes and deserializes. /// [Fact] public void Stack_SerializesAndDeserializes() { // Arrange var entity = new EntityWithAdvancedCollections { Name = "Test Stack" }; entity.UndoStack.Push("action1"); entity.UndoStack.Push("action2"); entity.UndoStack.Push("action3"); // Act var id = _db.AdvancedCollectionEntities.Insert(entity); _db.SaveChanges(); var retrieved = _db.AdvancedCollectionEntities.FindById(id); // Assert retrieved.ShouldNotBeNull(); retrieved.UndoStack.ShouldNotBeNull(); retrieved.UndoStack.Count.ShouldBe(3); var items = retrieved.UndoStack.ToList(); items.ShouldContain("action1"); items.ShouldContain("action2"); items.ShouldContain("action3"); } /// /// Tests hash set with nested objects serializes and deserializes. /// [Fact] public void HashSet_WithNestedObjects_SerializesAndDeserializes() { // Arrange var entity = new EntityWithAdvancedCollections { Name = "Test Nested HashSet" }; entity.Addresses.Add(new Address { Street = "123 Main St", City = new City { Name = "NYC", ZipCode = "10001" } }); entity.Addresses.Add(new Address { Street = "456 Oak Ave", City = new City { Name = "LA", ZipCode = "90001" } }); // Act var id = _db.AdvancedCollectionEntities.Insert(entity); _db.SaveChanges(); var retrieved = _db.AdvancedCollectionEntities.FindById(id); // Assert retrieved.ShouldNotBeNull(); retrieved.Addresses.ShouldNotBeNull(); retrieved.Addresses.ShouldBeOfType>(); retrieved.Addresses.Count.ShouldBe(2); var addressList = retrieved.Addresses.ToList(); addressList.ShouldContain(a => a.Street == "123 Main St" && a.City.Name == "NYC"); addressList.ShouldContain(a => a.Street == "456 Oak Ave" && a.City.Name == "LA"); } /// /// Tests iset with nested objects serializes and deserializes. /// [Fact] public void ISet_WithNestedObjects_SerializesAndDeserializes() { // Arrange var entity = new EntityWithAdvancedCollections { Name = "Test Nested ISet" }; entity.FavoriteCities.Add(new City { Name = "Paris", ZipCode = "75001" }); entity.FavoriteCities.Add(new City { Name = "Tokyo", ZipCode = "100-0001" }); entity.FavoriteCities.Add(new City { Name = "London", ZipCode = "SW1A" }); // Act var id = _db.AdvancedCollectionEntities.Insert(entity); _db.SaveChanges(); var retrieved = _db.AdvancedCollectionEntities.FindById(id); // Assert retrieved.ShouldNotBeNull(); retrieved.FavoriteCities.ShouldNotBeNull(); retrieved.FavoriteCities.ShouldBeAssignableTo>(); retrieved.FavoriteCities.Count.ShouldBe(3); var cityNames = retrieved.FavoriteCities.Select(c => c.Name).ToList(); cityNames.ShouldContain("Paris"); cityNames.ShouldContain("Tokyo"); cityNames.ShouldContain("London"); } /// /// Tests advanced collections all types in single entity. /// [Fact] public void AdvancedCollections_AllTypesInSingleEntity() { // Arrange - Test all collection types at once var entity = new EntityWithAdvancedCollections { Name = "Complete Test" }; entity.Tags.Add("tag1"); entity.Tags.Add("tag2"); entity.Numbers.Add(1); entity.Numbers.Add(2); entity.History.AddLast("h1"); entity.History.AddLast("h2"); entity.PendingItems.Enqueue("p1"); entity.PendingItems.Enqueue("p2"); entity.UndoStack.Push("u1"); entity.UndoStack.Push("u2"); entity.Addresses.Add(new Address { Street = "Street1" }); entity.FavoriteCities.Add(new City { Name = "City1" }); // Act var id = _db.AdvancedCollectionEntities.Insert(entity); _db.SaveChanges(); var retrieved = _db.AdvancedCollectionEntities.FindById(id); // Assert retrieved.ShouldNotBeNull(); retrieved.Name.ShouldBe("Complete Test"); retrieved.Tags.Count.ShouldBe(2); retrieved.Numbers.Count.ShouldBe(2); retrieved.History.Count.ShouldBe(2); retrieved.PendingItems.Count.ShouldBe(2); retrieved.UndoStack.Count.ShouldBe(2); retrieved.Addresses.Count().ShouldBe(1); retrieved.FavoriteCities.Count().ShouldBe(1); } #endregion #region Private Setters Tests /// /// Tests entity with private setters can be deserialized. /// [Fact] public void EntityWithPrivateSetters_CanBeDeserialized() { // Arrange var entity = EntityWithPrivateSetters.Create("John Doe", 30); // Act var id = _db.PrivateSetterEntities.Insert(entity); _db.SaveChanges(); var retrieved = _db.PrivateSetterEntities.FindById(id); // Assert retrieved.ShouldNotBeNull(); retrieved.Id.ShouldBe(id); retrieved.Name.ShouldBe("John Doe"); retrieved.Age.ShouldBe(30); } /// /// Tests entity with private setters update works. /// [Fact] public void EntityWithPrivateSetters_Update_Works() { // Arrange var entity1 = EntityWithPrivateSetters.Create("Alice", 25); var id1 = _db.PrivateSetterEntities.Insert(entity1); var entity2 = EntityWithPrivateSetters.Create("Bob", 35); entity2.GetType().GetProperty("Id")!.SetValue(entity2, id1); // Force same Id _db.PrivateSetterEntities.Update(entity2); _db.SaveChanges(); // Act var retrieved = _db.PrivateSetterEntities.FindById(id1); // Assert retrieved.ShouldNotBeNull(); retrieved.Id.ShouldBe(id1); retrieved.Name.ShouldBe("Bob"); retrieved.Age.ShouldBe(35); } /// /// Tests entity with private setters query works. /// [Fact] public void EntityWithPrivateSetters_Query_Works() { // Arrange var entity1 = EntityWithPrivateSetters.Create("Charlie", 20); var entity2 = EntityWithPrivateSetters.Create("Diana", 30); var entity3 = EntityWithPrivateSetters.Create("Eve", 40); _db.PrivateSetterEntities.Insert(entity1); _db.PrivateSetterEntities.Insert(entity2); _db.PrivateSetterEntities.Insert(entity3); _db.SaveChanges(); // Act var adults = _db.PrivateSetterEntities.Find(e => e.Age >= 30).ToList(); // Assert adults.Count.ShouldBe(2); adults.ShouldContain(e => e.Name == "Diana"); adults.ShouldContain(e => e.Name == "Eve"); } #endregion #region Init-Only Setters Tests /// /// Tests entity with init setters can be deserialized. /// [Fact] public void EntityWithInitSetters_CanBeDeserialized() { // Arrange var entity = new EntityWithInitSetters { Id = ObjectId.NewObjectId(), Name = "Jane Doe", Age = 28, CreatedAt = DateTime.UtcNow }; // Act var id = _db.InitSetterEntities.Insert(entity); _db.SaveChanges(); var retrieved = _db.InitSetterEntities.FindById(id); // Assert retrieved.ShouldNotBeNull(); retrieved.Id.ShouldBe(id); retrieved.Name.ShouldBe("Jane Doe"); retrieved.Age.ShouldBe(28); } /// /// Tests entity with init setters query works. /// [Fact] public void EntityWithInitSetters_Query_Works() { // Arrange var entity1 = new EntityWithInitSetters { Id = ObjectId.NewObjectId(), Name = "Alpha", Age = 20, CreatedAt = DateTime.UtcNow }; var entity2 = new EntityWithInitSetters { Id = ObjectId.NewObjectId(), Name = "Beta", Age = 30, CreatedAt = DateTime.UtcNow }; var entity3 = new EntityWithInitSetters { Id = ObjectId.NewObjectId(), Name = "Gamma", Age = 40, CreatedAt = DateTime.UtcNow }; _db.InitSetterEntities.Insert(entity1); _db.InitSetterEntities.Insert(entity2); _db.InitSetterEntities.Insert(entity3); _db.SaveChanges(); // Act var results = _db.InitSetterEntities.Find(e => e.Age > 25).ToList(); // Assert results.Count.ShouldBe(2); results.ShouldContain(e => e.Name == "Beta"); results.ShouldContain(e => e.Name == "Gamma"); } #endregion /// /// Disposes the resources used by this instance. /// public void Dispose() { _db?.Dispose(); if (File.Exists(_dbPath)) File.Delete(_dbPath); if (File.Exists(_walPath)) File.Delete(_walPath); } }