4648
This commit is contained in:
134
Quote/QuoteCrud.cs
Normal file
134
Quote/QuoteCrud.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace raven_integration
|
||||
{
|
||||
|
||||
public class QuoteCrud
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Full CRUD for a Quote header + QuoteItem + QuoteItemLabor sub-type.
|
||||
/// Also tests concurrency violation on the header and id-from-number lookup.
|
||||
/// Mirrors the WorkOrderCrud structure — if a refactor consolidates WO/Quote
|
||||
/// patterns, failures here flag the regression.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task CRUD()
|
||||
{
|
||||
var token = await Util.GetTokenAsync("BizAdmin");
|
||||
var isoNow = DateTime.UtcNow.ToString("o");
|
||||
var isoOneHourFromNow = DateTime.UtcNow.AddHours(1).ToString("o");
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// CREATE QUOTE HEADER
|
||||
// customerId=1 is a seeded customer; serial=0 means server assigns it.
|
||||
// -------------------------------------------------------------------
|
||||
var payload = $$"""
|
||||
{"id":0,"concurrency":0,"serial":0,"notes":"Test quote — the quick brown fox jumped over the six lazy dogs!","wiki":null,"customFields":"{}","tags":[],"preparedById":null,"introduction":"Test introduction","requested":null,"validUntil":null,"submitted":null,"approved":null,"customerId":1,"projectId":null,"internalReferenceNumber":"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("quote", token, payload);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
long QuoteId = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
long QuoteSerial = a.ObjectResponse["data"]["serial"].Value<long>();
|
||||
QuoteId.Should().BeGreaterThan(0);
|
||||
QuoteSerial.Should().BeGreaterThan(0, "server should have assigned a non-zero serial");
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// GET QUOTE
|
||||
// -------------------------------------------------------------------
|
||||
a = await Util.GetAsync($"quote/{QuoteId}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
a.ObjectResponse["data"]["id"].Value<long>().Should().Be(QuoteId);
|
||||
a.ObjectResponse["data"]["internalReferenceNumber"].Value<string>().Should().Be("INT-001");
|
||||
var headerConcurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// ID-FROM-NUMBER LOOKUP
|
||||
// -------------------------------------------------------------------
|
||||
a = await Util.GetAsync($"quote/id-from-number/{QuoteSerial}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
a.ObjectResponse["data"].Value<long>().Should().Be(QuoteId);
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// CREATE QUOTE ITEM
|
||||
// -------------------------------------------------------------------
|
||||
payload = $$"""
|
||||
{"id":0,"concurrency":0,"notes":"Test quote item summary","wiki":null,"customFields":"{}","tags":[],"quoteId":{{QuoteId}},"techNotes":"Tech notes for item","workOrderItemStatusId":null,"workOrderItemPriorityId":null,"requestDate":null,"warrantyService":false,"sequence":1}
|
||||
""";
|
||||
a = await Util.PostAsync("quote/items", token, payload);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
long QuoteItemId = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
QuoteItemId.Should().BeGreaterThan(0);
|
||||
|
||||
// GET QUOTE ITEM
|
||||
a = await Util.GetAsync($"quote/items/{QuoteItemId}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
a.ObjectResponse["data"]["notes"].Value<string>().Should().Be("Test quote item summary");
|
||||
var itemConcurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// CREATE QUOTE ITEM LABOR (nested sub-type)
|
||||
// -------------------------------------------------------------------
|
||||
payload = $$"""
|
||||
{"id":0,"concurrency":0,"userId":null,"serviceStartDate":"{{isoNow}}","serviceStopDate":"{{isoOneHourFromNow}}","serviceRateId":null,"serviceDetails":"Test labor service details","serviceRateQuantity":1,"noChargeQuantity":0,"taxCodeSaleId":null,"priceOverride":null,"quoteItemId":{{QuoteItemId}}}
|
||||
""";
|
||||
a = await Util.PostAsync("quote/items/labors", token, payload);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
long QuoteItemLaborId = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
QuoteItemLaborId.Should().BeGreaterThan(0);
|
||||
a.ObjectResponse["data"]["serviceDetails"].Value<string>().Should().Be("Test labor service details");
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// UPDATE QUOTE ITEM
|
||||
// -------------------------------------------------------------------
|
||||
payload = $$"""
|
||||
{"id":{{QuoteItemId}},"concurrency":{{itemConcurrency}},"notes":"Updated item notes","wiki":null,"customFields":"{}","tags":[],"quoteId":{{QuoteId}},"techNotes":"Updated tech notes","workOrderItemStatusId":null,"workOrderItemPriorityId":null,"requestDate":null,"warrantyService":false,"sequence":1}
|
||||
""";
|
||||
a = await Util.PutAsync("quote/items/", token, payload);
|
||||
Util.ValidateHTTPStatusCode(a, 200);
|
||||
var newItemConcurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
||||
newItemConcurrency.Should().NotBe(itemConcurrency);
|
||||
|
||||
// CONCURRENCY VIOLATION on item
|
||||
a = await Util.PutAsync("quote/items/", token, payload); // still has stale concurrency
|
||||
Util.ValidateConcurrencyError(a);
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// UPDATE QUOTE HEADER
|
||||
// -------------------------------------------------------------------
|
||||
payload = $$"""
|
||||
{"id":{{QuoteId}},"concurrency":{{headerConcurrency}},"serial":{{QuoteSerial}},"notes":"Updated quote notes","wiki":null,"customFields":"{}","tags":[],"preparedById":null,"introduction":"Updated introduction","requested":null,"validUntil":null,"submitted":null,"approved":null,"customerId":1,"projectId":null,"internalReferenceNumber":"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("quote", token, payload);
|
||||
Util.ValidateHTTPStatusCode(a, 200);
|
||||
a.ObjectResponse["data"]["internalReferenceNumber"].Value<string>().Should().Be("INT-002");
|
||||
var newHeaderConcurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
||||
newHeaderConcurrency.Should().NotBe(headerConcurrency);
|
||||
|
||||
// CONCURRENCY VIOLATION on header
|
||||
a = await Util.PutAsync("quote", token, payload); // stale concurrency
|
||||
Util.ValidateConcurrencyError(a);
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// DELETE in bottom-up order: labor → item → header
|
||||
// -------------------------------------------------------------------
|
||||
a = await Util.DeleteAsync($"quote/items/labors/{QuoteItemLaborId}", token);
|
||||
Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
a = await Util.DeleteAsync($"quote/items/{QuoteItemId}", token);
|
||||
Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
a = await Util.DeleteAsync($"quote/{QuoteId}", token);
|
||||
Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// Confirm header is gone
|
||||
a = await Util.GetAsync($"quote/{QuoteId}", token);
|
||||
Util.ValidateResponseNotFound(a);
|
||||
}
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
Reference in New Issue
Block a user