using System; using Xunit; using Newtonsoft.Json.Linq; using FluentAssertions; namespace raven_integration { public class CustomerCrud { /// /// Full CRUD for a Customer, including concurrency violation and alert retrieval. /// [Fact] public async Task CRUD() { var token = await Util.GetTokenAsync("BizAdmin"); // CREATE var name = Util.Uniquify("Test Customer"); var payload = $$""" {"id":0,"concurrency":0,"name":"{{name}}","active":true,"notes":"The quick brown fox jumped over the six lazy dogs!","wiki":null,"customFields":"{}","tags":[],"webAddress":null,"alertNotes":"Test alert text for this customer","billHeadOffice":false,"headOfficeId":null,"techNotes":"Tech-only notes","accountNumber":"ACC-001","contractId":null,"contractExpires":null,"phone1":"555-1234","phone2":null,"phone3":null,"phone4":null,"phone5":null,"emailAddress":"test@example.com","postAddress":null,"postCity":null,"postRegion":null,"postCountry":null,"postCode":null,"address":null,"city":null,"region":null,"country":null,"addressPostal":null,"latitude":null,"longitude":null} """; ApiResponse a = await Util.PostAsync("customer", token, payload); Util.ValidateDataReturnResponseOk(a); long Id = a.ObjectResponse["data"]["id"].Value(); Id.Should().BeGreaterThan(0); a.ObjectResponse["data"]["name"].Value().Should().Be(name); // GET a = await Util.GetAsync($"customer/{Id}", token); Util.ValidateDataReturnResponseOk(a); a.ObjectResponse["data"]["name"].Value().Should().Be(name); a.ObjectResponse["data"]["phone1"].Value().Should().Be("555-1234"); var concurrency = a.ObjectResponse["data"]["concurrency"].Value(); // GET ALERT a = await Util.GetAsync($"customer/alert/{Id}", token); Util.ValidateDataReturnResponseOk(a); a.ObjectResponse["data"].Value().Should().Be("Test alert text for this customer"); // PUT (update name and phone) var updatedName = Util.Uniquify("Updated Customer"); var putPayload = $$""" {"id":{{Id}},"concurrency":{{concurrency}},"name":"{{updatedName}}","active":true,"notes":"Updated notes","wiki":null,"customFields":"{}","tags":[],"webAddress":null,"alertNotes":"Updated alert text","billHeadOffice":false,"headOfficeId":null,"techNotes":null,"accountNumber":null,"contractId":null,"contractExpires":null,"phone1":"555-9999","phone2":null,"phone3":null,"phone4":null,"phone5":null,"emailAddress":null,"postAddress":null,"postCity":null,"postRegion":null,"postCountry":null,"postCode":null,"address":null,"city":null,"region":null,"country":null,"addressPostal":null,"latitude":null,"longitude":null} """; a = await Util.PutAsync("customer", token, putPayload); Util.ValidateHTTPStatusCode(a, 200); var newConcurrency = a.ObjectResponse["data"]["concurrency"].Value(); newConcurrency.Should().NotBe(concurrency, "concurrency should have been bumped by the update"); // Verify the update was persisted a = await Util.GetAsync($"customer/{Id}", token); Util.ValidateDataReturnResponseOk(a); a.ObjectResponse["data"]["name"].Value().Should().Be(updatedName); a.ObjectResponse["data"]["phone1"].Value().Should().Be("555-9999"); // CONCURRENCY VIOLATION: PUT with stale concurrency should return 409 a = await Util.PutAsync("customer", token, putPayload); // putPayload still has old concurrency Util.ValidateConcurrencyError(a); // DELETE a = await Util.DeleteAsync($"customer/{Id}", token); Util.ValidateHTTPStatusCode(a, 204); // Confirm deleted a = await Util.GetAsync($"customer/{Id}", token); Util.ValidateResponseNotFound(a); } /// /// A customer that has at least one work order associated with it should not /// be deletable — referential integrity must be enforced. /// [Fact] public async Task CustomerWithWorkOrders_CannotBeDeleted() { var token = await Util.GetTokenAsync("BizAdmin"); // Create a dedicated customer so we control what is linked to it var name = Util.Uniquify("RefInt Customer"); var payload = $$""" {"id":0,"concurrency":0,"name":"{{name}}","active":true,"notes":null,"wiki":null,"customFields":"{}","tags":[],"webAddress":null,"alertNotes":null,"billHeadOffice":false,"headOfficeId":null,"techNotes":null,"accountNumber":null,"contractId":null,"contractExpires":null,"phone1":null,"phone2":null,"phone3":null,"phone4":null,"phone5":null,"emailAddress":null,"postAddress":null,"postCity":null,"postRegion":null,"postCountry":null,"postCode":null,"address":null,"city":null,"region":null,"country":null,"addressPostal":null,"latitude":null,"longitude":null} """; ApiResponse a = await Util.PostAsync("customer", token, payload); Util.ValidateDataReturnResponseOk(a); long customerId = a.ObjectResponse["data"]["id"].Value(); // Create a work order linked to this customer var isoNow = DateTime.UtcNow.ToString("o"); var woPayload = $$""" {"id":0,"concurrency":0,"serial":0,"notes":"RefInt test WO","wiki":null,"customFields":"{}","tags":[],"customerId":{{customerId}},"projectId":null,"contractId":null,"internalReferenceNumber":null,"customerReferenceNumber":null,"customerContactName":null,"fromQuoteId":null,"fromPMId":null,"serviceDate":"{{isoNow}}","completeByDate":null,"durationToCompleted":"00:00:00","invoiceNumber":null,"onsite":true,"customerSignature":null,"customerSignatureName":null,"customerSignatureCaptured":null,"techSignature":null,"techSignatureName":null,"techSignatureCaptured":null,"postAddress":null,"postCity":null,"postRegion":null,"postCountry":null,"postCode":null,"address":null,"city":null,"region":null,"country":null,"addressPostal":null,"latitude":null,"longitude":null,"isDirty":true,"isLockedAtServer":false} """; a = await Util.PostAsync("workorder", token, woPayload); Util.ValidateDataReturnResponseOk(a); long woId = a.ObjectResponse["data"]["id"].Value(); // Attempt to delete the customer — should be blocked by referential integrity a = await Util.DeleteAsync($"customer/{customerId}", token); Util.ValidateViolatesReferentialIntegrityError(a); // Clean up: delete the work order first, then the customer a = await Util.DeleteAsync($"workorder/{woId}", token); Util.ValidateHTTPStatusCode(a, 204); a = await Util.DeleteAsync($"customer/{customerId}", token); Util.ValidateHTTPStatusCode(a, 204); } }//eoc }//eons