This commit is contained in:
2026-02-28 19:53:34 -08:00
parent 59cb886adb
commit db593886a3
7 changed files with 716 additions and 17 deletions

129
PM/PMCrud.cs Normal file
View File

@@ -0,0 +1,129 @@
using System;
using Xunit;
using Newtonsoft.Json.Linq;
using FluentAssertions;
namespace raven_integration
{
public class PMCrud
{
/// <summary>
/// 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)
/// </summary>
[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>();
long PMSerial = a.ObjectResponse["data"]["serial"].Value<long>();
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<long>().Should().Be(PMId);
a.ObjectResponse["data"]["repeatUnit"].Value<int>().Should().Be(6, "should be Months");
a.ObjectResponse["data"]["excludeDaysOfWeek"].Value<int>().Should().Be(96, "should be Sat+Sun flags");
a.ObjectResponse["data"]["internalReferenceNumber"].Value<string>().Should().Be("PM-INT-001");
var headerConcurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
// -------------------------------------------------------------------
// ID-FROM-NUMBER LOOKUP
// -------------------------------------------------------------------
a = await Util.GetAsync($"pm/id-from-number/{PMSerial}", token);
Util.ValidateDataReturnResponseOk(a);
a.ObjectResponse["data"].Value<long>().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<long>();
PMItemId.Should().BeGreaterThan(0);
// GET PM ITEM
a = await Util.GetAsync($"pm/items/{PMItemId}", token);
Util.ValidateDataReturnResponseOk(a);
a.ObjectResponse["data"]["notes"].Value<string>().Should().Be("Test PM item summary");
var itemConcurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
// -------------------------------------------------------------------
// 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<uint>();
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<string>().Should().Be("PM-INT-002");
a.ObjectResponse["data"]["repeatUnit"].Value<int>().Should().Be(4, "should now be Days");
var newHeaderConcurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
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