using Domain; using static TestData.ClientBuilder; using static TestData.OrderBuilder; namespace Db.Tests; /// The fake stores data-only records, never domain objects; Save maps state into rows (insert or refresh), the graph flattened to ids. public class AfterPersistenceTests { [Fact] public void Save_maps_domain_state_into_data_records() { var db = new DbContext(); var order = ValidOrder().WithDescription("one").WithTotal(150_00).Build(); var client = ValidClient().WithName("Acme").WithOrder(order).Build(); db.Save(client); db.Save(order); var clientRow = Assert.Single(db.Clients); Assert.Equal(client.Id, clientRow.Id); Assert.Equal("Acme", clientRow.Name); var orderRow = Assert.Single(db.Orders); Assert.Equal(order.Id, orderRow.Id); Assert.Equal("one", orderRow.Description); Assert.Equal(150_00, orderRow.TotalInCents); Assert.Equal(client.Id, orderRow.ClientId); // the graph is flattened to a key } [Fact] public void An_order_without_client_stores_an_empty_client_id() { var db = new DbContext(); var order = ValidOrder().WithDescription("orphan").Build(); db.Save(order); Assert.Equal(Guid.Empty, Assert.Single(db.Orders).ClientId); } [Fact] public void Saving_again_refreshes_the_row_instead_of_duplicating_it() { var db = new DbContext(); var order = ValidOrder().WithDescription("one").WithTotal(150_00).Build(); var client = ValidClient().WithName("Acme").WithOrder(order).Build(); db.Save(client); db.Save(order); order.Description = "one (edited)"; order.TotalInCents = 99_00; client.Name = "Acme GmbH"; db.Save(order); db.Save(client); // One row per saved object, refreshed with the *current* state: var orderRow = Assert.Single(db.Orders); Assert.Equal(order.Id, orderRow.Id); Assert.Equal("one (edited)", orderRow.Description); Assert.Equal(99_00, orderRow.TotalInCents); Assert.Equal(client.Id, orderRow.ClientId); var clientRow = Assert.Single(db.Clients); Assert.Equal("Acme GmbH", clientRow.Name); } [Fact] public void Rows_are_reachable_through_Set_and_Find() { var db = new DbContext(); var order = ValidOrder().WithDescription("one").WithTotal(150_00).Build(); var client = ValidClient().WithName("Acme").WithOrder(order).Build(); db.Save(client); db.Save(order); Assert.Single(db.Set()); Assert.Single(db.Set()); var found = db.Find(row => row.Description == "one"); Assert.Equal(order.Id, found!.Id); Assert.Null(db.Find(row => row.Description == "no such order")); } // Read-back: LoadOrder / LoadClient reassemble domain objects from the stored rows. [Fact] public void Loading_an_order_restores_its_state_and_its_client() { var db = new DbContext(); var order = ValidOrder().WithDescription("one").WithTotal(150_00).Build(); var client = ValidClient().WithName("Acme").WithOrder(order).Build(); db.Save(client); db.Save(order); var loaded = db.LoadOrder(order.Id); Assert.NotNull(loaded); Assert.Equal(order.Id, loaded!.Id); Assert.Equal("one", loaded.Description); Assert.Equal(150_00, loaded.TotalInCents); // The graph is reassembled: the loaded order knows its client again, // and the client's collection holds the loaded order. Assert.NotNull(loaded.Client); Assert.Equal("Acme", loaded.Client!.Name); Assert.Same(loaded, Assert.Single(loaded.Client.Orders)); } [Fact] public void Loading_a_client_restores_it_with_all_of_its_orders() { var db = new DbContext(); var first = ValidOrder().WithDescription("one").WithTotal(150_00).Build(); var second = ValidOrder().WithDescription("two").WithTotal(250_00).Build(); var client = ValidClient().WithName("Acme").WithOrder(first).WithOrder(second).Build(); db.Save(client); db.Save(first); db.Save(second); var loaded = db.LoadClient(client.Id); Assert.NotNull(loaded); Assert.Equal("Acme", loaded!.Name); Assert.Equal(2, loaded.Orders.Count); Assert.All(loaded.Orders, o => Assert.Same(loaded, o.Client)); } [Fact] public void Loading_an_unknown_id_returns_null() { var db = new DbContext(); Assert.Null(db.LoadOrder(Guid.NewGuid())); Assert.Null(db.LoadClient(Guid.NewGuid())); } // Tracking: explicit Save, distinct ids; a saved object keeps no reference to the context. [Fact] public void DbContext_saves_plain_domain_objects_and_tracks_them() { var db = new DbContext(); var order = ValidOrder().WithDescription("one").Build(); var client = ValidClient().WithName("Acme").WithOrder(order).Build(); Assert.Equal(Guid.Empty, client.Id); db.Save(client); db.Save(order); Assert.NotEqual(Guid.Empty, client.Id); Assert.NotEqual(Guid.Empty, order.Id); Assert.NotEqual(client.Id, order.Id); Assert.True(db.IsTracked(client)); Assert.True(db.IsTracked(order)); Assert.Equal(2, db.Tracked.Count); } [Fact] public void Saving_twice_does_not_reassign_the_id() { var db = new DbContext(); var client = ValidClient().WithName("Acme").Build(); db.Save(client); var id = client.Id; db.Save(client); Assert.Equal(id, client.Id); Assert.Single(db.Tracked); } [Fact] public void A_saved_domain_object_keeps_no_reference_to_the_context() { var db = new DbContext(); var client = ValidClient().WithName("Acme").Build(); db.Save(client); // Saved (the id was assigned by the context) ... Assert.NotEqual(Guid.Empty, client.Id); Assert.True(db.IsTracked(client)); // No way back to the context through the domain object (see DomainStaysPlainTests): the only "receipt" is the plain Id value. Assert.Equal(db.Tracked.Single(t => t.Id == client.Id).Id, client.Id); } }