using System; using Xunit; using Newtonsoft.Json.Linq; using FluentAssertions; namespace raven_integration { public class PMCrud { /// /// Full CRUD for a Preventive Maintenance header + PMItem. /// Also tests concurrency violation and id-from-number lookup. /// PM is structurally parallel to WorkOrder (header → items → sub-types) /// so failures here flag regressions if the refactor consolidates those patterns. /// /// Key enum values used: /// PMTimeUnit: Minutes=2, Hours=3, Days=4, Months=6, Years=7 /// AyaDaysOfWeek (flags): Monday=1, Tuesday=2, Wednesday=4, Thursday=8, /// Friday=16, Saturday=32, Sunday=64 /// excludeDaysOfWeek=96 means Saturday(32)|Sunday(64) /// [Fact] public async Task CRUD() { var token = await Util.GetTokenAsync("BizAdmin"); // nextServiceDate must be a future UTC timestamp var isoNextService = DateTime.UtcNow.AddDays(7).ToString("o"); // ------------------------------------------------------------------- // CREATE PM HEADER // customerId=1 is a seeded customer; serial=0 means server assigns it. // ------------------------------------------------------------------- var payload = $$""" {"id":0,"concurrency":0,"serial":0,"notes":"Test PM — the quick brown fox jumped over the six lazy dogs!","wiki":null,"customFields":"{}","tags":[],"copyWiki":false,"copyAttachments":true,"stopGeneratingDate":null,"excludeDaysOfWeek":96,"active":true,"nextServiceDate":"{{isoNextService}}","repeatUnit":6,"generateBeforeUnit":4,"repeatInterval":1,"generateBeforeInterval":3,"customerId":1,"projectId":null,"internalReferenceNumber":"PM-INT-001","customerReferenceNumber":null,"customerContactName":null,"onsite":true,"contractId":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("pm", token, payload); Util.ValidateDataReturnResponseOk(a); long PMId = a.ObjectResponse["data"]["id"].Value(); long PMSerial = a.ObjectResponse["data"]["serial"].Value(); PMId.Should().BeGreaterThan(0); PMSerial.Should().BeGreaterThan(0, "server should have assigned a non-zero serial"); // ------------------------------------------------------------------- // GET PM // ------------------------------------------------------------------- a = await Util.GetAsync($"pm/{PMId}", token); Util.ValidateDataReturnResponseOk(a); a.ObjectResponse["data"]["id"].Value().Should().Be(PMId); a.ObjectResponse["data"]["repeatUnit"].Value().Should().Be(6, "should be Months"); a.ObjectResponse["data"]["excludeDaysOfWeek"].Value().Should().Be(96, "should be Sat+Sun flags"); a.ObjectResponse["data"]["internalReferenceNumber"].Value().Should().Be("PM-INT-001"); var headerConcurrency = a.ObjectResponse["data"]["concurrency"].Value(); // ------------------------------------------------------------------- // ID-FROM-NUMBER LOOKUP // ------------------------------------------------------------------- a = await Util.GetAsync($"pm/id-from-number/{PMSerial}", token); Util.ValidateDataReturnResponseOk(a); a.ObjectResponse["data"].Value().Should().Be(PMId); // ------------------------------------------------------------------- // CREATE PM ITEM // ------------------------------------------------------------------- payload = $$""" {"id":0,"concurrency":0,"notes":"Test PM item summary","wiki":null,"customFields":"{}","tags":[],"pmId":{{PMId}},"techNotes":"Tech notes for PM item","workOrderItemStatusId":null,"workOrderItemPriorityId":null,"requestDate":null,"warrantyService":false,"sequence":1} """; a = await Util.PostAsync("pm/items", token, payload); Util.ValidateDataReturnResponseOk(a); long PMItemId = a.ObjectResponse["data"]["id"].Value(); PMItemId.Should().BeGreaterThan(0); // GET PM ITEM a = await Util.GetAsync($"pm/items/{PMItemId}", token); Util.ValidateDataReturnResponseOk(a); a.ObjectResponse["data"]["notes"].Value().Should().Be("Test PM item summary"); var itemConcurrency = a.ObjectResponse["data"]["concurrency"].Value(); // ------------------------------------------------------------------- // UPDATE PM ITEM // ------------------------------------------------------------------- payload = $$""" {"id":{{PMItemId}},"concurrency":{{itemConcurrency}},"notes":"Updated PM item notes","wiki":null,"customFields":"{}","tags":[],"pmId":{{PMId}},"techNotes":"Updated tech notes","workOrderItemStatusId":null,"workOrderItemPriorityId":null,"requestDate":null,"warrantyService":false,"sequence":1} """; a = await Util.PutAsync("pm/items/", token, payload); Util.ValidateHTTPStatusCode(a, 200); var newItemConcurrency = a.ObjectResponse["data"]["concurrency"].Value(); newItemConcurrency.Should().NotBe(itemConcurrency, "concurrency should increment on update"); // CONCURRENCY VIOLATION on item a = await Util.PutAsync("pm/items/", token, payload); // stale concurrency Util.ValidateConcurrencyError(a); // ------------------------------------------------------------------- // UPDATE PM HEADER // ------------------------------------------------------------------- var isoNewNextService = DateTime.UtcNow.AddDays(14).ToString("o"); payload = $$""" {"id":{{PMId}},"concurrency":{{headerConcurrency}},"serial":{{PMSerial}},"notes":"Updated PM notes","wiki":null,"customFields":"{}","tags":[],"copyWiki":true,"copyAttachments":true,"stopGeneratingDate":null,"excludeDaysOfWeek":0,"active":true,"nextServiceDate":"{{isoNewNextService}}","repeatUnit":4,"generateBeforeUnit":4,"repeatInterval":2,"generateBeforeInterval":5,"customerId":1,"projectId":null,"internalReferenceNumber":"PM-INT-002","customerReferenceNumber":null,"customerContactName":null,"onsite":false,"contractId":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("pm", token, payload); Util.ValidateHTTPStatusCode(a, 200); a.ObjectResponse["data"]["internalReferenceNumber"].Value().Should().Be("PM-INT-002"); a.ObjectResponse["data"]["repeatUnit"].Value().Should().Be(4, "should now be Days"); var newHeaderConcurrency = a.ObjectResponse["data"]["concurrency"].Value(); newHeaderConcurrency.Should().NotBe(headerConcurrency); // CONCURRENCY VIOLATION on header a = await Util.PutAsync("pm", token, payload); // stale concurrency Util.ValidateConcurrencyError(a); // ------------------------------------------------------------------- // DELETE in bottom-up order: item → header // ------------------------------------------------------------------- a = await Util.DeleteAsync($"pm/items/{PMItemId}", token); Util.ValidateHTTPStatusCode(a, 204); a = await Util.DeleteAsync($"pm/{PMId}", token); Util.ValidateHTTPStatusCode(a, 204); // Confirm header is gone a = await Util.GetAsync($"pm/{PMId}", token); Util.ValidateResponseNotFound(a); } }//eoc }//eons