using Before; namespace BeforeAfter.Tests; /// /// Tests for the Client, Order, DbBase, DbContext, and WebApp classes. /// public class BeforeTests { // Client and Order derive from DbBase. [Fact] public void Client_and_Order_derive_from_DbBase() { Assert.True(typeof(DbBase).IsAssignableFrom(typeof(Client))); Assert.True(typeof(DbBase).IsAssignableFrom(typeof(Order))); } // New entities have Id == Guid.Empty. [Theory] [InlineData(typeof(Client))] [InlineData(typeof(Order))] public void New_entities_have_an_empty_id(Type type) { var entity = (DbBase)Activator.CreateInstance(type)!; Assert.Equal(Guid.Empty, entity.Id); } // Save assigns fresh, distinct Guids to unsaved entities. [Fact] public void Save_assigns_fresh_distinct_ids() { var db = new DbContext(); var acme = new Client { Name = "Acme" }; var globex = new Client { Name = "Globex" }; db.Attach(acme); db.Attach(globex); Assert.Equal(2, db.Save()); Assert.NotEqual(Guid.Empty, acme.Id); Assert.NotEqual(Guid.Empty, globex.Id); Assert.NotEqual(acme.Id, globex.Id); } // A saved entity references its context and is tracked by it. [Fact] public void Saved_entity_is_registered_with_its_context() { var db = new DbContext(); var client = new Client { Name = "Acme" }; db.Attach(client); db.Save(); Assert.Same(db, client.DbContext); Assert.True(db.IsTracked(client)); } // Adding an order to client.Orders sets order.Client. [Fact] public void Adding_an_order_sets_the_client_back_reference() { var client = new Client { Name = "Acme" }; var order = new Order { Description = "first order" }; client.Orders.Add(order); Assert.Same(client, order.Client); Assert.Same(order, client.Orders[0]); Assert.Single(client.Orders); } // A client exposes the context it was attached to. [Fact] public void A_client_can_reach_its_DbContext() { var db = new DbContext(); var client = new Client { Name = "Acme" }; db.Attach(client); db.Save(); Assert.NotNull(client.DbContext); Assert.Same(db, client.DbContext); Assert.True(client.DbContext!.IsTracked(client)); } // WebApp works on domain classes directly. [Fact] public void WebApp_works_on_the_domain_classes_directly() { var app = new WebApp(); var db = new DbContext(); var client = new Client { Name = "Acme" }; var order = new Order { Description = "order one" }; client.Orders.Add(order); db.Attach(client); db.Attach(order); db.Save(); var view = app.ShowClient(client); Assert.Contains("Acme", view); Assert.Contains("order one", view); Assert.True(app.IsPersisted(client)); } // A bare client has no context until saved. [Fact] public void A_bare_client_has_no_context_until_saved() { var app = new WebApp(); var client = new Client { Name = "Nobody" }; Assert.Null(client.DbContext); Assert.False(app.IsPersisted(client)); } // DbBase exposes a static, shared Context reference. [Fact] public void DbBase_has_a_static_Context_singleton() { Assert.Null(DbBase.Context); var db = new DbContext(); DbBase.Context = db; Assert.Same(db, DbBase.Context); DbBase.Context = null; // cleanup } // DbBase.Find returns the first tracked entity of type T matching the predicate. [Fact] public void DbBase_Find_finds_matching_entity() { var db = new DbContext(); var acme = new Client { Name = "Acme" }; var globex = new Client { Name = "Globex" }; db.Attach(acme); db.Attach(globex); var result = DbBase.Find(c => c.Name == "Globex", db); Assert.NotNull(result); Assert.Same(globex, result); } // DbBase.Find returns null when there is no match. [Fact] public void DbBase_Find_returns_null_when_no_match() { var db = new DbContext(); db.Attach(new Client { Name = "Acme" }); var result = DbBase.Find(c => c.Name == "Nobody", db); Assert.Null(result); } // DbBase.Find only matches entities of type T. [Fact] public void DbBase_Find_ignores_non_matching_types() { var db = new DbContext(); db.Attach(new Order { Description = "test" }); var result = DbBase.Find(_ => true, db); Assert.Null(result); } // DbBase.Find returns the first match only. [Fact] public void DbBase_Find_returns_first_match() { var db = new DbContext(); var first = new Client { Name = "SameName" }; var second = new Client { Name = "SameName" }; db.Attach(first); db.Attach(second); var result = DbBase.Find(c => c.Name == "SameName", db); Assert.Same(first, result); // first one inserted wins Assert.NotSame(second, result); } // DbBase.Find returns null when no context is available. [Fact] public void DbBase_Find_with_null_context_returns_default() { var result = DbBase.Find(_ => true, null); Assert.Null(result); } // DbBase.Find falls back to DbBase.Context when db is null. [Fact] public void DbBase_Find_falls_back_to_Context_singleton_when_db_is_null() { var db = new DbContext(); var acme = new Client { Name = "SingletonCo" }; db.Attach(acme); DbBase.Context = db; try { var result = DbBase.Find(c => c.Name == "SingletonCo", null); Assert.NotNull(result); Assert.Same(acme, result); } finally { DbBase.Context = null; // cleanup } } // Client.FindByName delegates to DbBase.Find. [Fact] public void Client_FindByName_finds_by_name() { var db = new DbContext(); var acme = new Client { Name = "Acme Corp" }; db.Attach(acme); var result = Client.FindByName("Acme Corp", db); Assert.NotNull(result); Assert.Same(acme, result); } // Client.FindByName returns null when there is no match. [Fact] public void Client_FindByName_returns_null_when_not_found() { var db = new DbContext(); db.Attach(new Client { Name = "Acme Corp" }); var result = Client.FindByName("Nobody", db); Assert.Null(result); } // Client.FindByName uses DbBase.Context when db is omitted. [Fact] public void Client_FindByName_uses_Context_singleton_when_db_omitted() { var db = new DbContext(); var acme = new Client { Name = "Acme Corp" }; db.Attach(acme); DbBase.Context = db; try { var result = Client.FindByName("Acme Corp"); Assert.NotNull(result); Assert.Same(acme, result); } finally { DbBase.Context = null; // cleanup } } // DbBase.Context can be passed as the context argument to Find. [Fact] public void DbBase_Context_singleton_is_used_in_Find() { var db = new DbContext(); var targeted = new Client { Name = "TargetCo" }; db.Attach(targeted); DbBase.Context = db; var queryResult = DbBase.Find(c => c.Name == "TargetCo", DbBase.Context); Assert.NotNull(queryResult); Assert.Same(targeted, queryResult); DbBase.Context = null; // cleanup } // DbBase.FindRequired returns the matching entity. [Fact] public void DbBase_FindRequired_T_finds_matching_entity() { var db = new DbContext(); var acme = new Client { Name = "Acme" }; var globex = new Client { Name = "Globex" }; db.Attach(acme); db.Attach(globex); var result = DbBase.FindRequired(c => c.Name == "Globex", db, "name == Globex"); Assert.Same(globex, result); } // DbBase.FindRequired throws ObjectNotFoundException when there is no match. [Fact] public void DbBase_FindRequired_T_throws_when_no_match() { var db = new DbContext(); db.Attach(new Client { Name = "Acme" }); var ex = Assert.Throws(() => DbBase.FindRequired(c => c.Name == "Nobody", db, "name == Nobody")); Assert.Contains("Nobody", ex.Message); Assert.Contains("no matching Client found", ex.Message); } // DbBase.FindRequired throws InvalidOperationException when no context is // configured — InvalidOperationException, not ObjectNotFoundException. [Fact] public void DbBase_FindRequired_T_throws_when_no_context_configured() { DbBase.Context = null; var ex = Assert.Throws(() => DbBase.FindRequired(_ => true, null, "true")); Assert.Contains("no DbContext configured", ex.Message); Assert.Contains("DbBase.Context", ex.Message); } // Client.FindByNameRequired returns the matching client. [Fact] public void Client_FindByNameRequired_finds_by_name() { var db = new DbContext(); var acme = new Client { Name = "Acme Corp" }; db.Attach(acme); var result = Client.FindByNameRequired("Acme Corp", db); Assert.Same(acme, result); } // Client.FindByNameRequired throws ObjectNotFoundException when there is no // match; the message includes the name. [Fact] public void Client_FindByNameRequired_throws_with_name_when_not_found() { var db = new DbContext(); db.Attach(new Client { Name = "Acme Corp" }); var ex = Assert.Throws(() => Client.FindByNameRequired("Nobody", db)); Assert.Contains("Nobody", ex.Message); Assert.Contains("no matching Client found", ex.Message); } // Client.FindByNameRequired uses DbBase.Context when no context is passed. [Fact] public void Client_FindByNameRequired_uses_Context_singleton() { var db = new DbContext(); var acme = new Client { Name = "SingletonCo" }; db.Attach(acme); DbBase.Context = db; try { var result = Client.FindByNameRequired("SingletonCo"); Assert.Same(acme, result); } finally { DbBase.Context = null; } } // Client.FindByNameRequired throws InvalidOperationException when no // context is configured. [Fact] public void Client_FindByNameRequired_throws_with_context_hint_when_singleton_null() { DbBase.Context = null; var ex = Assert.Throws(() => Client.FindByNameRequired("SomeBody")); Assert.Contains("no DbContext configured", ex.Message); Assert.Contains("DbBase.Context", ex.Message); } }