4648
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -1,30 +0,0 @@
|
||||
// using Xunit;
|
||||
// using Newtonsoft.Json.Linq;
|
||||
// using FluentAssertions;
|
||||
|
||||
// namespace raven_integration
|
||||
// {
|
||||
|
||||
// public class DataListRights
|
||||
// {
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// ///
|
||||
// /// </summary>
|
||||
// [Fact]
|
||||
// public async Task InsufficentRightsShouldNotRetrieve()
|
||||
// {
|
||||
// //Get without rights
|
||||
// /*
|
||||
// "{\"error\":{\"code\":\"2004\",\"message\":\"User not authorized for this resource operation (insufficient rights)\"}}"
|
||||
// */
|
||||
// //ApiResponse a = await Util.GetAsync("data-list/list?DataListKey=TestWidgetDataList&Offset=0&Limit=3", await Util.GetTokenAsync("CustomerRestricted"));
|
||||
// ApiResponse a = await Util.PostAsync($"data-list", await Util.GetTokenAsync("CustomerRestricted"), Util.BuildDataListRequestEx());
|
||||
// Util.ValidateErrorCodeResponse(a, 2004, 403);
|
||||
// }
|
||||
|
||||
// //==================================================
|
||||
|
||||
// }//eoc
|
||||
// }//eons
|
||||
@@ -1,413 +0,0 @@
|
||||
// using System;
|
||||
// using Xunit;
|
||||
// using Newtonsoft.Json.Linq;
|
||||
// using FluentAssertions;
|
||||
// using System.Collections.Generic;
|
||||
// using System.Collections.Concurrent;
|
||||
|
||||
// namespace raven_integration
|
||||
// {
|
||||
|
||||
|
||||
// public class DataListSorting
|
||||
// {
|
||||
|
||||
// /// <summary>
|
||||
// ///
|
||||
// /// </summary>
|
||||
// [Fact]
|
||||
// public async Task DefaultSortByIdWorks()
|
||||
// {
|
||||
|
||||
// var WidgetNameStart = Util.Uniquify("DefaultSortByIdWorks");
|
||||
|
||||
// //CREATE 3 TEST WIDGETS TO TEST ORDER
|
||||
// long FirstInOrderWidgetId = 0;
|
||||
// long SecondInOrderWidgetId = 0;
|
||||
// long ThirdInOrderWidgetId = 0;
|
||||
|
||||
// dynamic w = new JObject();
|
||||
// w.name = Util.Uniquify(WidgetNameStart);
|
||||
// w.customFields = Util.WidgetRequiredCustomFieldsJsonString();
|
||||
// w.notes = "blah";
|
||||
// w.usertype = 1;
|
||||
// ApiResponse a = await Util.PostAsync("widget", await Util.GetTokenAsync("superuser", "l3tm3in"), w.ToString());
|
||||
// Util.ValidateDataReturnResponseOk(a);
|
||||
// ThirdInOrderWidgetId = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
|
||||
// w = new JObject();
|
||||
// w.name = Util.Uniquify(WidgetNameStart);
|
||||
// w.customFields = Util.WidgetRequiredCustomFieldsJsonString();
|
||||
// w.notes = "blah";
|
||||
// w.usertype = 1;
|
||||
// a = await Util.PostAsync("widget", await Util.GetTokenAsync("superuser", "l3tm3in"), w.ToString());
|
||||
// Util.ValidateDataReturnResponseOk(a);
|
||||
// SecondInOrderWidgetId = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
|
||||
// w = new JObject();
|
||||
// w.name = Util.Uniquify(WidgetNameStart);
|
||||
// w.customFields = Util.WidgetRequiredCustomFieldsJsonString();
|
||||
// w.notes = "blah";
|
||||
// w.usertype = 1;
|
||||
// a = await Util.PostAsync("widget", await Util.GetTokenAsync("superuser", "l3tm3in"), w.ToString());
|
||||
// Util.ValidateDataReturnResponseOk(a);
|
||||
// FirstInOrderWidgetId = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
|
||||
|
||||
// //CREATE FILTER
|
||||
|
||||
// dynamic dListView = new JArray();
|
||||
|
||||
|
||||
// //name starts with filter to constrict to widgets that this test block created only
|
||||
|
||||
// dListView.Add(Util.BuildSimpleFilterDataListViewColumn("widgetname", Util.OpStartsWith, WidgetNameStart));
|
||||
|
||||
// //NOW FETCH WIDGET LIST WITH FILTER
|
||||
// // a = await Util.GetAsync($"data-list/list?DataListKey=TestWidgetDataList&Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("superuser", "l3tm3in"));
|
||||
// a = await Util.PostAsync($"data-list", await Util.GetTokenAsync("superuser", "l3tm3in"), Util.BuildDataListRequestEx(dListView));
|
||||
// Util.ValidateDataReturnResponseOk(a);
|
||||
// Util.ValidateHTTPStatusCode(a, 200);
|
||||
|
||||
// //assert contains exactly 3 records
|
||||
// ((JArray)a.ObjectResponse["data"]).Count.Should().Be(3);
|
||||
|
||||
// //assert the order returned
|
||||
// a.ObjectResponse["data"][0][0]["i"].Value<long>().Should().Be(FirstInOrderWidgetId);
|
||||
// a.ObjectResponse["data"][1][0]["i"].Value<long>().Should().Be(SecondInOrderWidgetId);
|
||||
// a.ObjectResponse["data"][2][0]["i"].Value<long>().Should().Be(ThirdInOrderWidgetId);
|
||||
|
||||
|
||||
// a = await Util.DeleteAsync("widget/" + FirstInOrderWidgetId.ToString(), await Util.GetTokenAsync("superuser", "l3tm3in"));
|
||||
// Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// a = await Util.DeleteAsync("widget/" + SecondInOrderWidgetId.ToString(), await Util.GetTokenAsync("superuser", "l3tm3in"));
|
||||
// Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// a = await Util.DeleteAsync("widget/" + ThirdInOrderWidgetId.ToString(), await Util.GetTokenAsync("superuser", "l3tm3in"));
|
||||
// Util.ValidateHTTPStatusCode(a, 204);
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// ///
|
||||
// /// </summary>
|
||||
// [Fact]
|
||||
// public async Task SortByFieldAscendingWorks()
|
||||
// {
|
||||
|
||||
// var WidgetNameStart = Util.Uniquify("SortByFieldAscendingWorks");
|
||||
|
||||
// //CREATE 3 TEST WIDGETS TO TEST ORDER
|
||||
// long FirstInOrderWidgetId = 0;
|
||||
// long SecondInOrderWidgetId = 0;
|
||||
// long ThirdInOrderWidgetId = 0;
|
||||
|
||||
// dynamic w = new JObject();
|
||||
// w.name = Util.Uniquify(WidgetNameStart);
|
||||
// w.customFields = Util.WidgetRequiredCustomFieldsJsonString();
|
||||
// w.notes = "blah";
|
||||
// w.startDate = DateTime.Now;
|
||||
// w.endDate = DateTime.Now.AddHours(1);
|
||||
// w.usertype = 1;
|
||||
|
||||
// ApiResponse a = await Util.PostAsync("widget", await Util.GetTokenAsync("superuser", "l3tm3in"), w.ToString());
|
||||
// Util.ValidateDataReturnResponseOk(a);
|
||||
// FirstInOrderWidgetId = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
|
||||
// w = new JObject();
|
||||
// w.name = Util.Uniquify(WidgetNameStart);
|
||||
// w.customFields = Util.WidgetRequiredCustomFieldsJsonString();
|
||||
// w.notes = "blah";
|
||||
// w.startDate = DateTime.Now.AddHours(1);
|
||||
// w.endDate = DateTime.Now.AddHours(2);
|
||||
// w.usertype = 1;
|
||||
// a = await Util.PostAsync("widget", await Util.GetTokenAsync("superuser", "l3tm3in"), w.ToString());
|
||||
// Util.ValidateDataReturnResponseOk(a);
|
||||
// SecondInOrderWidgetId = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
|
||||
// w = new JObject();
|
||||
// w.name = Util.Uniquify(WidgetNameStart);
|
||||
// w.customFields = Util.WidgetRequiredCustomFieldsJsonString();
|
||||
// w.notes = "blah";
|
||||
// w.startDate = DateTime.Now.AddHours(2);
|
||||
// w.endDate = DateTime.Now.AddHours(3);
|
||||
// w.usertype = 1;
|
||||
// a = await Util.PostAsync("widget", await Util.GetTokenAsync("superuser", "l3tm3in"), w.ToString());
|
||||
// Util.ValidateDataReturnResponseOk(a);
|
||||
// ThirdInOrderWidgetId = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
|
||||
|
||||
// //CREATE FILTER
|
||||
// //FILTER IN BY NAME FOR TESTING THIS RUN ONLY
|
||||
// dynamic dListView = new JArray();
|
||||
// //name starts with filter to constrict to widgets that this test block created only
|
||||
|
||||
|
||||
// //SORT ORDER ###################
|
||||
// // dynamic dsortarray = new JArray();
|
||||
// // dynamic dsort = new JObject();
|
||||
// // dsort.fld = "widgetstartdate";
|
||||
// // dsort.dir = "+";
|
||||
// // dsortarray.Add(dsort);
|
||||
|
||||
// //both conditions filter and sort here
|
||||
// dListView.Add(Util.BuildSimpleFilterDataListViewColumn("widgetname", Util.OpStartsWith, WidgetNameStart, "+"));
|
||||
|
||||
|
||||
|
||||
// //NOW FETCH WIDGET LIST WITH FILTER
|
||||
// a = await Util.PostAsync($"data-list", await Util.GetTokenAsync("superuser", "l3tm3in"), Util.BuildDataListRequestEx(dListView));
|
||||
// Util.ValidateDataReturnResponseOk(a);
|
||||
// Util.ValidateHTTPStatusCode(a, 200);
|
||||
|
||||
// //assert contains exactly 3 records
|
||||
// ((JArray)a.ObjectResponse["data"]).Count.Should().Be(3);
|
||||
|
||||
// //assert the order returned
|
||||
// a.ObjectResponse["data"][0][0]["i"].Value<long>().Should().Be(FirstInOrderWidgetId);
|
||||
// a.ObjectResponse["data"][1][0]["i"].Value<long>().Should().Be(SecondInOrderWidgetId);
|
||||
// a.ObjectResponse["data"][2][0]["i"].Value<long>().Should().Be(ThirdInOrderWidgetId);
|
||||
|
||||
|
||||
// a = await Util.DeleteAsync("widget/" + FirstInOrderWidgetId.ToString(), await Util.GetTokenAsync("superuser", "l3tm3in"));
|
||||
// Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// a = await Util.DeleteAsync("widget/" + SecondInOrderWidgetId.ToString(), await Util.GetTokenAsync("superuser", "l3tm3in"));
|
||||
// Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// a = await Util.DeleteAsync("widget/" + ThirdInOrderWidgetId.ToString(), await Util.GetTokenAsync("superuser", "l3tm3in"));
|
||||
// Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// ///
|
||||
// /// </summary>
|
||||
// [Fact]
|
||||
// public async Task SortByFieldDescendingWorks()
|
||||
// {
|
||||
|
||||
// var WidgetNameStart = Util.Uniquify("SortByFieldDescendingWorks");
|
||||
|
||||
// //CREATE 3 TEST WIDGETS TO TEST ORDER
|
||||
// long FirstInOrderWidgetId = 0;
|
||||
// long SecondInOrderWidgetId = 0;
|
||||
// long ThirdInOrderWidgetId = 0;
|
||||
|
||||
// dynamic w = new JObject();
|
||||
// w.name = Util.Uniquify(WidgetNameStart);
|
||||
// w.customFields = Util.WidgetRequiredCustomFieldsJsonString();
|
||||
// w.notes = "blah";
|
||||
// w.count = 999;
|
||||
// w.usertype = 1;
|
||||
|
||||
// ApiResponse a = await Util.PostAsync("widget", await Util.GetTokenAsync("superuser", "l3tm3in"), w.ToString());
|
||||
// Util.ValidateDataReturnResponseOk(a);
|
||||
// FirstInOrderWidgetId = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
|
||||
// w = new JObject();
|
||||
// w.name = Util.Uniquify(WidgetNameStart);
|
||||
// w.customFields = Util.WidgetRequiredCustomFieldsJsonString();
|
||||
// w.notes = "blah";
|
||||
// w.count = 665;
|
||||
// w.usertype = 1;
|
||||
// a = await Util.PostAsync("widget", await Util.GetTokenAsync("superuser", "l3tm3in"), w.ToString());
|
||||
// Util.ValidateDataReturnResponseOk(a);
|
||||
// SecondInOrderWidgetId = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
|
||||
// w = new JObject();
|
||||
// w.name = Util.Uniquify(WidgetNameStart);
|
||||
// w.customFields = Util.WidgetRequiredCustomFieldsJsonString();
|
||||
// w.notes = "blah";
|
||||
// w.count = 333;
|
||||
// w.usertype = 1;
|
||||
// a = await Util.PostAsync("widget", await Util.GetTokenAsync("superuser", "l3tm3in"), w.ToString());
|
||||
// Util.ValidateDataReturnResponseOk(a);
|
||||
// ThirdInOrderWidgetId = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
|
||||
|
||||
// //CREATE FILTER
|
||||
|
||||
// //FILTER IN BY NAME FOR TESTING THIS RUN ONLY
|
||||
// dynamic dListView = new JArray();
|
||||
// //name starts with filter to constrict to widgets that this test block created only
|
||||
|
||||
// dListView.Add(Util.BuildSimpleFilterDataListViewColumn("widgetname", Util.OpStartsWith, WidgetNameStart));
|
||||
|
||||
|
||||
// //SORT ORDER ###################
|
||||
// // dynamic dsortarray = new JArray();
|
||||
// // dynamic dsort = new JObject();
|
||||
// // dsort.fld = "widgetcount";
|
||||
// // dsort.dir = "-";
|
||||
// dListView.Add(Util.BuildSimpleSortDataListViewColumn("widgetcount", "-"));
|
||||
|
||||
// //NOW FETCH WIDGET LIST WITH FILTER
|
||||
// a = await Util.PostAsync($"data-list", await Util.GetTokenAsync("superuser", "l3tm3in"), Util.BuildDataListRequestEx(dListView));
|
||||
// Util.ValidateDataReturnResponseOk(a);
|
||||
// Util.ValidateHTTPStatusCode(a, 200);
|
||||
|
||||
// //assert contains exactly 3 records
|
||||
// ((JArray)a.ObjectResponse["data"]).Count.Should().Be(3);
|
||||
|
||||
// //assert the order returned
|
||||
// a.ObjectResponse["data"][0][0]["i"].Value<long>().Should().Be(FirstInOrderWidgetId);
|
||||
// a.ObjectResponse["data"][1][0]["i"].Value<long>().Should().Be(SecondInOrderWidgetId);
|
||||
// a.ObjectResponse["data"][2][0]["i"].Value<long>().Should().Be(ThirdInOrderWidgetId);
|
||||
|
||||
|
||||
// a = await Util.DeleteAsync("widget/" + FirstInOrderWidgetId.ToString(), await Util.GetTokenAsync("superuser", "l3tm3in"));
|
||||
// Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// a = await Util.DeleteAsync("widget/" + SecondInOrderWidgetId.ToString(), await Util.GetTokenAsync("superuser", "l3tm3in"));
|
||||
// Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// a = await Util.DeleteAsync("widget/" + ThirdInOrderWidgetId.ToString(), await Util.GetTokenAsync("superuser", "l3tm3in"));
|
||||
// Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// ///
|
||||
// /// </summary>
|
||||
// [Fact]
|
||||
// public async Task SortByMultipleFieldsWorks()
|
||||
// {
|
||||
// /*
|
||||
|
||||
// Created order:
|
||||
// dollaramount, count
|
||||
// 2,1
|
||||
// 1,2
|
||||
// 2,2
|
||||
// 1,1
|
||||
|
||||
|
||||
// sorted order:
|
||||
// dollar asc, count desc
|
||||
// 1,2
|
||||
// 1,1
|
||||
// 2,2
|
||||
// 2,1
|
||||
|
||||
// */
|
||||
// var WidgetNameStart = Util.Uniquify("SortByMultipleFieldsWorks");
|
||||
|
||||
// //CREATE 4 TEST WIDGETS TO TEST ORDER
|
||||
// long FirstInOrderWidgetId = 0;
|
||||
// long SecondInOrderWidgetId = 0;
|
||||
// long ThirdInOrderWidgetId = 0;
|
||||
// long FourthInOrderWidgetId = 0;
|
||||
|
||||
// dynamic w = new JObject();
|
||||
// w.name = Util.Uniquify(WidgetNameStart);
|
||||
// w.customFields = Util.WidgetRequiredCustomFieldsJsonString();
|
||||
// w.notes = "blah";
|
||||
// w.dollaramount = 2.22;
|
||||
// w.count = 1;
|
||||
// w.usertype = 1;
|
||||
|
||||
// ApiResponse a = await Util.PostAsync("widget", await Util.GetTokenAsync("superuser", "l3tm3in"), w.ToString());
|
||||
// Util.ValidateDataReturnResponseOk(a);
|
||||
// FourthInOrderWidgetId = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
|
||||
// w = new JObject();
|
||||
// w.name = Util.Uniquify(WidgetNameStart);
|
||||
// w.customFields = Util.WidgetRequiredCustomFieldsJsonString();
|
||||
// w.notes = "blah";
|
||||
// w.dollaramount = 1.11;
|
||||
// w.count = 2;
|
||||
// w.usertype = 1;
|
||||
// a = await Util.PostAsync("widget", await Util.GetTokenAsync("superuser", "l3tm3in"), w.ToString());
|
||||
// Util.ValidateDataReturnResponseOk(a);
|
||||
// FirstInOrderWidgetId = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
|
||||
// w = new JObject();
|
||||
// w.name = Util.Uniquify(WidgetNameStart);
|
||||
// w.customFields = Util.WidgetRequiredCustomFieldsJsonString();
|
||||
// w.notes = "blah";
|
||||
// w.dollaramount = 1.11;
|
||||
// w.count = 1;
|
||||
// w.usertype = 1;
|
||||
// a = await Util.PostAsync("widget", await Util.GetTokenAsync("superuser", "l3tm3in"), w.ToString());
|
||||
// Util.ValidateDataReturnResponseOk(a);
|
||||
// SecondInOrderWidgetId = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
|
||||
// w = new JObject();
|
||||
// w.name = Util.Uniquify(WidgetNameStart);
|
||||
// w.customFields = Util.WidgetRequiredCustomFieldsJsonString();
|
||||
// w.notes = "blah";
|
||||
// w.dollaramount = 2.22;
|
||||
// w.count = 2;
|
||||
// w.usertype = 1;
|
||||
// a = await Util.PostAsync("widget", await Util.GetTokenAsync("superuser", "l3tm3in"), w.ToString());
|
||||
// Util.ValidateDataReturnResponseOk(a);
|
||||
// ThirdInOrderWidgetId = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
|
||||
|
||||
// //CREATE FILTER
|
||||
|
||||
// //FILTER IN BY NAME FOR TESTING THIS RUN ONLY
|
||||
// dynamic dListView = new JArray();
|
||||
// //name starts with filter to constrict to widgets that this test block created only
|
||||
// dListView.Add(Util.BuildSimpleFilterDataListViewColumn("widgetname", Util.OpStartsWith, WidgetNameStart));
|
||||
|
||||
// //SORT ORDER ###################
|
||||
// // dynamic dsortarray = new JArray();
|
||||
|
||||
// //First column
|
||||
// // dynamic dsort1 = new JObject();
|
||||
// // dsort1.fld = "widgetdollaramount";
|
||||
// // dsort1.dir = "+";
|
||||
// // dsortarray.Add(dsort1);
|
||||
// dListView.Add(Util.BuildSimpleSortDataListViewColumn("widgetdollaramount", "+"));
|
||||
|
||||
// //Second column
|
||||
// // dynamic dsort2 = new JObject();
|
||||
// // dsort2.fld = "widgetcount";
|
||||
// // dsort2.dir = "-";
|
||||
// // dsortarray.Add(dsort2);
|
||||
// dListView.Add(Util.BuildSimpleSortDataListViewColumn("widgetcount", "-"));
|
||||
|
||||
|
||||
// //NOW FETCH WIDGET LIST WITH FILTER
|
||||
// a = await Util.PostAsync($"data-list", await Util.GetTokenAsync("superuser", "l3tm3in"), Util.BuildDataListRequestEx(dListView));
|
||||
// Util.ValidateDataReturnResponseOk(a);
|
||||
// Util.ValidateHTTPStatusCode(a, 200);
|
||||
|
||||
// //assert contains exactly 3 records
|
||||
// ((JArray)a.ObjectResponse["data"]).Count.Should().Be(4);
|
||||
|
||||
// //assert the order returned
|
||||
// a.ObjectResponse["data"][0][0]["i"].Value<long>().Should().Be(FirstInOrderWidgetId);
|
||||
// a.ObjectResponse["data"][1][0]["i"].Value<long>().Should().Be(SecondInOrderWidgetId);
|
||||
// a.ObjectResponse["data"][2][0]["i"].Value<long>().Should().Be(ThirdInOrderWidgetId);
|
||||
// a.ObjectResponse["data"][3][0]["i"].Value<long>().Should().Be(FourthInOrderWidgetId);
|
||||
|
||||
|
||||
// a = await Util.DeleteAsync("widget/" + FirstInOrderWidgetId.ToString(), await Util.GetTokenAsync("superuser", "l3tm3in"));
|
||||
// Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// a = await Util.DeleteAsync("widget/" + SecondInOrderWidgetId.ToString(), await Util.GetTokenAsync("superuser", "l3tm3in"));
|
||||
// Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// a = await Util.DeleteAsync("widget/" + ThirdInOrderWidgetId.ToString(), await Util.GetTokenAsync("superuser", "l3tm3in"));
|
||||
// Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// a = await Util.DeleteAsync("widget/" + FourthInOrderWidgetId.ToString(), await Util.GetTokenAsync("superuser", "l3tm3in"));
|
||||
// Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// }
|
||||
|
||||
// //========================================================================
|
||||
|
||||
// }//eoc
|
||||
// }//eons
|
||||
@@ -1,202 +0,0 @@
|
||||
// using System;
|
||||
// using Xunit;
|
||||
// using Newtonsoft.Json.Linq;
|
||||
// using FluentAssertions;
|
||||
// using System.Collections.Generic;
|
||||
// using System.Collections.Concurrent;
|
||||
|
||||
// namespace raven_integration
|
||||
// {
|
||||
|
||||
// public class DataListFilterCrud
|
||||
// {
|
||||
|
||||
// /// <summary>
|
||||
// /// Test all CRUD routes
|
||||
// /// </summary>
|
||||
// [Fact]
|
||||
// public async Task CRUD()
|
||||
// {
|
||||
// //CREATE
|
||||
// dynamic d = new JObject();
|
||||
// d.name = Util.Uniquify("Test DataListView");
|
||||
|
||||
// d["public"] = true;
|
||||
// d.listKey="TestWidgetDataList";
|
||||
|
||||
// //"[{fld:"name",op:"!=",value:"Notequaltothis"},{fld:"tags",op:"Eq",value:"[23,456,54]"}]
|
||||
// dynamic dListView = new JArray();
|
||||
// // dynamic df = new JObject();
|
||||
// // df.fld = "widgetname";
|
||||
// // df.op = "%-";
|
||||
// // df.value = "Generic";//lots of seed widgets start with Generic
|
||||
// // dListView.Add(df);
|
||||
// // d.filter = dListView.ToString();//it expects it to be a json string, not actual json
|
||||
// dListView.Add(Util.BuildSimpleFilterDataListViewColumn("widgetname", Util.OpStartsWith, "Generic"));
|
||||
// d.listView=dListView.ToString(Newtonsoft.Json.Formatting.None);
|
||||
|
||||
// ApiResponse a = await Util.PostAsync("data-list-view", await Util.GetTokenAsync("BizAdmin"), d.ToString(Newtonsoft.Json.Formatting.None));
|
||||
// Util.ValidateDataReturnResponseOk(a);
|
||||
|
||||
// long Id = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
|
||||
|
||||
// //RETRIEVE
|
||||
// //Get one
|
||||
// a = await Util.GetAsync("data-list-view/" + Id.ToString(), await Util.GetTokenAsync("BizAdmin"));
|
||||
// Util.ValidateDataReturnResponseOk(a);
|
||||
// a.ObjectResponse["data"]["name"].Value<string>().Should().StartWith("Test DataListView");
|
||||
|
||||
// //Get as alternate user should work for public filter
|
||||
// a = await Util.GetAsync("data-list-view/" + Id.ToString(), await Util.GetTokenAsync("SubContractorRestricted"));
|
||||
// Util.ValidateDataReturnResponseOk(a);
|
||||
// a.ObjectResponse["data"]["name"].Value<string>().Should().StartWith("Test DataListView");
|
||||
|
||||
|
||||
// //UPDATE
|
||||
|
||||
// //PUT, make private
|
||||
// d["public"] = false;
|
||||
// d.name = Util.Uniquify("Put - Test DataListView (privatized)");
|
||||
// d.concurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
||||
// a = await Util.PutAsync("data-list-view/" + Id.ToString(), await Util.GetTokenAsync("BizAdmin"), d.ToString());
|
||||
// Util.ValidateHTTPStatusCode(a, 200);
|
||||
|
||||
// //check PUT worked
|
||||
// a = await Util.GetAsync("data-list-view/" + Id.ToString(), await Util.GetTokenAsync("BizAdmin"));
|
||||
// Util.ValidateNoErrorInResponse(a);
|
||||
// a.ObjectResponse["data"]["name"].Value<string>().Should().Be(d.name.ToString());
|
||||
|
||||
|
||||
// //FETCH DISALLOWED
|
||||
// //Get as alternate user should fail for private filter
|
||||
// a = await Util.GetAsync("data-list-view/" + Id.ToString(), await Util.GetTokenAsync("SubContractorRestricted"));
|
||||
// Util.ValidateResponseNotFound(a);
|
||||
|
||||
// // //DELETE
|
||||
// ApiResponse DELETETestResponse = await Util.DeleteAsync("data-list-view/" + Id.ToString(), await Util.GetTokenAsync("BizAdmin"));
|
||||
// Util.ValidateHTTPStatusCode(DELETETestResponse, 204);
|
||||
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// ///
|
||||
// /// </summary>
|
||||
// [Fact]
|
||||
// public async Task InvalidListKeyShouldFail()
|
||||
// {
|
||||
// //CREATE
|
||||
// dynamic d = new JObject();
|
||||
// d.name = Util.Uniquify("Test DataListView");
|
||||
|
||||
// d["public"] = true;
|
||||
// d.listKey = "nonexistant";
|
||||
|
||||
// //"[{fld:"name",op:"!=",value:"Notequaltothis"},{fld:"tags",op:"Eq",value:"[23,456,54]"}]
|
||||
// // dynamic dfilter = new JArray();
|
||||
// // dynamic df = new JObject();
|
||||
// // df.fld = "name";
|
||||
// // df.op = "%-";
|
||||
// // df.value = "Generic";//lots of seed widgets start with Generic
|
||||
// // dfilter.Add(df);
|
||||
|
||||
// // d.filter = dfilter.ToString();//it expects it to be a json string, not actual json
|
||||
|
||||
// // ApiResponse a = await Util.PostAsync("data-list-view", await Util.GetTokenAsync("BizAdmin"), d.ToString());
|
||||
|
||||
// dynamic dListView = new JArray();
|
||||
// // dynamic df = new JObject();
|
||||
// // df.fld = "widgetname";
|
||||
// // df.op = "%-";
|
||||
// // df.value = "Generic";//lots of seed widgets start with Generic
|
||||
// // dListView.Add(df);
|
||||
// // d.filter = dListView.ToString();//it expects it to be a json string, not actual json
|
||||
// dListView.Add(Util.BuildSimpleFilterDataListViewColumn("widgetname", Util.OpStartsWith, "Generic"));
|
||||
// d.listView=dListView.ToString(Newtonsoft.Json.Formatting.None);
|
||||
|
||||
// ApiResponse a = await Util.PostAsync("data-list-view", await Util.GetTokenAsync("BizAdmin"), d.ToString(Newtonsoft.Json.Formatting.None));
|
||||
// Util.ValidateErrorCodeResponse(a, 2200, 400);
|
||||
// Util.ShouldContainValidationError(a, "ListKey", "2203");
|
||||
|
||||
// }
|
||||
|
||||
|
||||
// /// <summary>
|
||||
// ///
|
||||
// /// </summary>
|
||||
// [Fact]
|
||||
// public async Task InvalidFieldNameShouldFail()
|
||||
// {
|
||||
// //CREATE
|
||||
// dynamic d = new JObject();
|
||||
// d.name = Util.Uniquify("Test DataListView");
|
||||
|
||||
// d["public"] = true;
|
||||
// d.listKey="TestWidgetDataList";
|
||||
|
||||
// //"[{fld:"name",op:"!=",value:"Notequaltothis"},{fld:"tags",op:"Eq",value:"[23,456,54]"}]
|
||||
// // dynamic dfilter = new JArray();
|
||||
// // dynamic df = new JObject();
|
||||
// // df.fld = "doesntexist";
|
||||
// // df.op = "%-";
|
||||
// // df.value = "Generic";//lots of seed widgets start with Generic
|
||||
// // dfilter.Add(df);
|
||||
|
||||
// // d.filter = dfilter.ToString();//it expects it to be a json string, not actual json
|
||||
|
||||
// // ApiResponse a = await Util.PostAsync("data-list-view", await Util.GetTokenAsync("BizAdmin"), d.ToString());
|
||||
// dynamic dListView = new JArray();
|
||||
// // dynamic df = new JObject();
|
||||
// // df.fld = "widgetname";
|
||||
// // df.op = "%-";
|
||||
// // df.value = "Generic";//lots of seed widgets start with Generic
|
||||
// // dListView.Add(df);
|
||||
// // d.filter = dListView.ToString();//it expects it to be a json string, not actual json
|
||||
// dListView.Add(Util.BuildSimpleFilterDataListViewColumn("doesntexist", Util.OpStartsWith, "Generic"));
|
||||
// d.listView=dListView.ToString(Newtonsoft.Json.Formatting.None);
|
||||
|
||||
// ApiResponse a = await Util.PostAsync("data-list-view", await Util.GetTokenAsync("BizAdmin"), d.ToString(Newtonsoft.Json.Formatting.None));
|
||||
// Util.ValidateErrorCodeResponse(a, 2200, 400);
|
||||
// Util.ShouldContainValidationError(a, "ListView", "2203");
|
||||
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// //REMOVED THIS VALIDATION TEST AS SWITCH TO DATALISTVIEW NO LONGER VALIDATES FILTER ANYWAY
|
||||
// //ONLY COLUMN NAMES AND LIST NAME
|
||||
// // /// <summary>
|
||||
// // ///
|
||||
// // /// </summary>
|
||||
// // [Fact]
|
||||
// // public async Task InvalidOperatorShouldFail()
|
||||
// // {
|
||||
// // //CREATE
|
||||
// // dynamic d = new JObject();
|
||||
// // d.name = Util.Uniquify("Test DataListView");
|
||||
|
||||
// // d["public"] = true;
|
||||
// // d.listKey="TestWidgetDataList";
|
||||
|
||||
// // //"[{fld:"name",op:"!=",value:"Notequaltothis"},{fld:"tags",op:"Eq",value:"[23,456,54]"}]
|
||||
// // dynamic dfilter = new JArray();
|
||||
// // dynamic df = new JObject();
|
||||
// // df.fld = "name";
|
||||
// // df.op = "wtf";
|
||||
// // df.value = "Generic";//lots of seed widgets start with Generic
|
||||
// // dfilter.Add(df);
|
||||
|
||||
// // d.filter = dfilter.ToString();//it expects it to be a json string, not actual json
|
||||
|
||||
// // ApiResponse a = await Util.PostAsync("data-list-view", await Util.GetTokenAsync("BizAdmin"), d.ToString());
|
||||
// // Util.ValidateErrorCodeResponse(a, 2200, 400);
|
||||
// // Util.ShouldContainValidationError(a, "Filter", "2203");
|
||||
|
||||
// // }
|
||||
|
||||
|
||||
|
||||
// //==================================================
|
||||
|
||||
// }//eoc
|
||||
// }//eons
|
||||
65
Memo/MemoCrud.cs
Normal file
65
Memo/MemoCrud.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace raven_integration
|
||||
{
|
||||
|
||||
public class MemoCrud
|
||||
{
|
||||
|
||||
private static long GetUserIdFromToken(string token)
|
||||
{
|
||||
var payloadB64 = token.Split('.')[1].Replace('-', '+').Replace('_', '/');
|
||||
while (payloadB64.Length % 4 != 0) payloadB64 += "=";
|
||||
var json = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(payloadB64));
|
||||
return JObject.Parse(json)["id"].Value<long>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a memo, read it back, then delete it and confirm it is gone.
|
||||
/// Memos are immutable after sending so there is no PUT step.
|
||||
///
|
||||
/// Note: the normal POST (users=[userId]) returns 202 Accepted with no body — the
|
||||
/// server creates per-recipient records but returns no id. To get a testable memo id
|
||||
/// we use the v8 migration compatibility path: users=[-7] authenticated as superuser
|
||||
/// (id=1). That path creates a single memo and returns 200 with {data:{id:N}}.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task CreateReadDelete()
|
||||
{
|
||||
// Must be superuser (id=1) to use the migration compatibility path
|
||||
var token = await Util.GetTokenAsync("superuser", "l3tm3in");
|
||||
|
||||
var isoNow = DateTime.UtcNow.ToString("o");
|
||||
var subject = Util.Uniquify("Test Memo");
|
||||
|
||||
// CREATE via migration path: users=[-7], superuser auth → 200 with {data:{id:N}}
|
||||
// fromId=1, toId=1 so that foreign-key constraints are satisfied
|
||||
var payload = $$"""
|
||||
{"memo":{"id":0,"concurrency":0,"name":"{{subject}}","notes":"Test memo body text.","wiki":null,"customFields":"{}","tags":[],"viewed":false,"replied":false,"fromId":1,"toId":1,"sent":"{{isoNow}}"},"users":[-7]}
|
||||
""";
|
||||
|
||||
ApiResponse a = await Util.PostAsync("memo", token, payload);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
long Id = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
Id.Should().BeGreaterThan(0);
|
||||
|
||||
// GET
|
||||
a = await Util.GetAsync($"memo/{Id}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(subject);
|
||||
|
||||
// DELETE
|
||||
a = await Util.DeleteAsync($"memo/{Id}", token);
|
||||
Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// Confirm deleted
|
||||
a = await Util.GetAsync($"memo/{Id}", token);
|
||||
Util.ValidateResponseNotFound(a);
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
88
Notification/NotificationOps.cs
Normal file
88
Notification/NotificationOps.cs
Normal file
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace raven_integration
|
||||
{
|
||||
|
||||
public class NotificationOps
|
||||
{
|
||||
|
||||
private static long GetUserIdFromToken(string token)
|
||||
{
|
||||
var payloadB64 = token.Split('.')[1].Replace('-', '+').Replace('_', '/');
|
||||
while (payloadB64.Length % 4 != 0) payloadB64 += "=";
|
||||
var json = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(payloadB64));
|
||||
return JObject.Parse(json)["id"].Value<long>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a direct-message notification to self, verify it appears in the app-notification
|
||||
/// list, then delete it and confirm the count endpoint responds throughout.
|
||||
/// </summary>
|
||||
[Fact(Skip ="Works once every 12 hours due to bug at server case ")]
|
||||
public async Task DirectMessage_SendAndDelete()
|
||||
{
|
||||
var token = await Util.GetTokenAsync("BizAdmin");
|
||||
var userId = GetUserIdFromToken(token);
|
||||
|
||||
// GET new-count (baseline — count may be any value)
|
||||
ApiResponse a = await Util.GetAsync("notify/new-count", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
|
||||
// POST direct-message to self
|
||||
var messageText = Util.Uniquify("Integration test notification");
|
||||
var payload = $$"""
|
||||
{"message":"{{messageText}}","users":[{{userId}}]}
|
||||
""";
|
||||
|
||||
a = await Util.PostAsync("notify/direct-message", token, payload);
|
||||
Util.ValidateHTTPStatusCode(a, 204); // 204 No Content — server sends but returns no body
|
||||
|
||||
// Poll app-notifications until the message appears.
|
||||
// The server processes notify events on a background timer (20 s in debug,
|
||||
// 60 s in release), so the message may not be visible immediately.
|
||||
JToken matchedNotification = null;
|
||||
var deadline = DateTime.UtcNow.AddSeconds(65);
|
||||
|
||||
while (DateTime.UtcNow < deadline)
|
||||
{
|
||||
a = await Util.GetAsync("notify/app-notifications", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
|
||||
var notifications = (JArray)a.ObjectResponse["data"];
|
||||
notifications.Should().NotBeNull("a JArray of notifications should be returned");
|
||||
|
||||
foreach (var notification in notifications)
|
||||
{
|
||||
var body = notification["body"]?.ToString() ?? string.Empty;
|
||||
var message = notification["message"]?.ToString() ?? string.Empty;
|
||||
if (body.Contains(messageText) || message.Contains(messageText))
|
||||
{
|
||||
matchedNotification = notification;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedNotification != null) break;
|
||||
|
||||
await Task.Delay(3000);
|
||||
}
|
||||
|
||||
matchedNotification.Should().NotBeNull("the direct message just sent should appear in app-notifications within 65 seconds");
|
||||
|
||||
long notificationId = matchedNotification["id"].Value<long>();
|
||||
|
||||
// GET new-count again (just verify the endpoint responds)
|
||||
a = await Util.GetAsync("notify/new-count", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
|
||||
// DELETE the notification
|
||||
a = await Util.DeleteAsync($"notify/{notificationId}", token);
|
||||
Util.ValidateHTTPStatusCode(a, 204);
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
119
Part/PartCrud.cs
Normal file
119
Part/PartCrud.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace raven_integration
|
||||
{
|
||||
|
||||
public class PartCrud
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Full CRUD for a Part, including concurrency violation.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task CRUD()
|
||||
{
|
||||
var token = await Util.GetTokenAsync("BizAdmin");
|
||||
|
||||
// CREATE
|
||||
var name = Util.Uniquify("Test Part");
|
||||
var payload = $$"""
|
||||
{"id":0,"concurrency":0,"name":"{{name}}","active":true,"cost":9.99,"retail":14.99,"description":null,"notes":null,"wiki":null,"customFields":"{}","tags":[],"manufacturerId":null,"manufacturerNumber":null,"wholeSalerId":null,"wholeSalerNumber":null,"alternativeWholeSalerId":null,"alternativeWholeSalerNumber":null,"unitOfMeasure":null,"upc":null}
|
||||
""";
|
||||
|
||||
ApiResponse a = await Util.PostAsync("part", token, payload);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
long Id = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
Id.Should().BeGreaterThan(0);
|
||||
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(name);
|
||||
|
||||
// GET
|
||||
a = await Util.GetAsync($"part/{Id}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(name);
|
||||
a.ObjectResponse["data"]["cost"].Value<decimal>().Should().Be(9.99m);
|
||||
var concurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
||||
|
||||
// PUT (update name and cost)
|
||||
var updatedName = Util.Uniquify("Updated Part");
|
||||
var putPayload = $$"""
|
||||
{"id":{{Id}},"concurrency":{{concurrency}},"name":"{{updatedName}}","active":true,"cost":11.99,"retail":14.99,"description":null,"notes":null,"wiki":null,"customFields":"{}","tags":[],"manufacturerId":null,"manufacturerNumber":null,"wholeSalerId":null,"wholeSalerNumber":null,"alternativeWholeSalerId":null,"alternativeWholeSalerNumber":null,"unitOfMeasure":null,"upc":null}
|
||||
""";
|
||||
a = await Util.PutAsync("part", token, putPayload);
|
||||
Util.ValidateHTTPStatusCode(a, 200);
|
||||
var newConcurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
||||
newConcurrency.Should().NotBe(concurrency, "concurrency should have been bumped by the update");
|
||||
|
||||
// Verify the update was persisted
|
||||
a = await Util.GetAsync($"part/{Id}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(updatedName);
|
||||
a.ObjectResponse["data"]["cost"].Value<decimal>().Should().Be(11.99m);
|
||||
|
||||
// CONCURRENCY VIOLATION: PUT with stale concurrency should return 409
|
||||
a = await Util.PutAsync("part", token, putPayload); // putPayload still has old concurrency
|
||||
Util.ValidateConcurrencyError(a);
|
||||
|
||||
// DELETE
|
||||
a = await Util.DeleteAsync($"part/{Id}", token);
|
||||
Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// Confirm deleted
|
||||
a = await Util.GetAsync($"part/{Id}", token);
|
||||
Util.ValidateResponseNotFound(a);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that serial numbers can be written to and read back from a Part.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Serials_RoundTrip()
|
||||
{
|
||||
var token = await Util.GetTokenAsync("BizAdmin");
|
||||
|
||||
// CREATE a part to attach serials to
|
||||
var name = Util.Uniquify("Test Part");
|
||||
var payload = $$"""
|
||||
{"id":0,"concurrency":0,"name":"{{name}}","active":true,"cost":9.99,"retail":14.99,"description":null,"notes":null,"wiki":null,"customFields":"{}","tags":[],"manufacturerId":null,"manufacturerNumber":null,"wholeSalerId":null,"wholeSalerNumber":null,"alternativeWholeSalerId":null,"alternativeWholeSalerNumber":null,"unitOfMeasure":null,"upc":null}
|
||||
""";
|
||||
|
||||
ApiResponse a = await Util.PostAsync("part", token, payload);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
long Id = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
Id.Should().BeGreaterThan(0);
|
||||
|
||||
// GET serials — should be empty initially
|
||||
a = await Util.GetAsync($"part/serials/{Id}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
Util.ValidateHTTPStatusCode(a, 200);
|
||||
((JArray)a.ObjectResponse["data"]).Count.Should().Be(0);
|
||||
|
||||
// PUT serials — write three serial numbers
|
||||
var serialsPayload = """["SN-A001","SN-A002","SN-A003"]""";
|
||||
a = await Util.PutAsync($"part/serials/{Id}", token, serialsPayload);
|
||||
Util.ValidateHTTPStatusCode(a, 200);
|
||||
|
||||
// GET serials again — verify all three are returned
|
||||
a = await Util.GetAsync($"part/serials/{Id}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
Util.ValidateHTTPStatusCode(a, 200);
|
||||
var serials = (JArray)a.ObjectResponse["data"];
|
||||
serials.Count.Should().Be(3);
|
||||
serials.Values<string>().Should().Contain("SN-A001");
|
||||
serials.Values<string>().Should().Contain("SN-A002");
|
||||
serials.Values<string>().Should().Contain("SN-A003");
|
||||
|
||||
// Clean up: clear serials before deleting (parts with serials cannot be deleted)
|
||||
a = await Util.PutAsync($"part/serials/{Id}", token, "[]");
|
||||
Util.ValidateHTTPStatusCode(a, 200);
|
||||
|
||||
a = await Util.DeleteAsync($"part/{Id}", token);
|
||||
Util.ValidateHTTPStatusCode(a, 204);
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
199
Reference/ReferenceCrud.cs
Normal file
199
Reference/ReferenceCrud.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace raven_integration
|
||||
{
|
||||
|
||||
public class ServiceRateCrud
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Full CRUD for a ServiceRate, including concurrency violation and 404 on deleted record.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task CRUD()
|
||||
{
|
||||
var token = await Util.GetTokenAsync("BizAdmin");
|
||||
|
||||
// CREATE
|
||||
var name = Util.Uniquify("Test Service Rate");
|
||||
var payload = $$"""
|
||||
{"id":0,"concurrency":0,"name":"{{name}}","active":true,"notes":null,"wiki":null,"customFields":"{}","tags":[],"accountNumber":null,"cost":50.00,"charge":75.00,"unit":null,"contractOnly":false}
|
||||
""";
|
||||
|
||||
ApiResponse a = await Util.PostAsync("service-rate", token, payload);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
long Id = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
Id.Should().BeGreaterThan(0);
|
||||
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(name);
|
||||
|
||||
// GET
|
||||
a = await Util.GetAsync($"service-rate/{Id}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(name);
|
||||
a.ObjectResponse["data"]["cost"].Value<decimal>().Should().Be(50.00m);
|
||||
var concurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
||||
|
||||
// PUT (update name and charge)
|
||||
var updatedName = Util.Uniquify("Updated Service Rate");
|
||||
var putPayload = $$"""
|
||||
{"id":{{Id}},"concurrency":{{concurrency}},"name":"{{updatedName}}","active":true,"notes":null,"wiki":null,"customFields":"{}","tags":[],"accountNumber":null,"cost":50.00,"charge":80.00,"unit":null,"contractOnly":false}
|
||||
""";
|
||||
a = await Util.PutAsync("service-rate", token, putPayload);
|
||||
Util.ValidateHTTPStatusCode(a, 200);
|
||||
var newConcurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
||||
newConcurrency.Should().NotBe(concurrency, "concurrency should have been bumped by the update");
|
||||
|
||||
// Verify the update was persisted
|
||||
a = await Util.GetAsync($"service-rate/{Id}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(updatedName);
|
||||
a.ObjectResponse["data"]["charge"].Value<decimal>().Should().Be(80.00m);
|
||||
|
||||
// CONCURRENCY VIOLATION: PUT with stale concurrency should return 409
|
||||
a = await Util.PutAsync("service-rate", token, putPayload); // putPayload still has old concurrency
|
||||
Util.ValidateConcurrencyError(a);
|
||||
|
||||
// DELETE
|
||||
a = await Util.DeleteAsync($"service-rate/{Id}", token);
|
||||
Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// Confirm deleted
|
||||
a = await Util.GetAsync($"service-rate/{Id}", token);
|
||||
Util.ValidateResponseNotFound(a);
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
|
||||
|
||||
public class TravelRateCrud
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Full CRUD for a TravelRate, including concurrency violation and 404 on deleted record.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task CRUD()
|
||||
{
|
||||
var token = await Util.GetTokenAsync("BizAdmin");
|
||||
|
||||
// CREATE
|
||||
var name = Util.Uniquify("Test Travel Rate");
|
||||
var payload = $$"""
|
||||
{"id":0,"concurrency":0,"name":"{{name}}","active":true,"notes":null,"wiki":null,"customFields":"{}","tags":[],"accountNumber":null,"cost":50.00,"charge":75.00,"unit":null,"contractOnly":false}
|
||||
""";
|
||||
|
||||
ApiResponse a = await Util.PostAsync("travel-rate", token, payload);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
long Id = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
Id.Should().BeGreaterThan(0);
|
||||
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(name);
|
||||
|
||||
// GET
|
||||
a = await Util.GetAsync($"travel-rate/{Id}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(name);
|
||||
a.ObjectResponse["data"]["cost"].Value<decimal>().Should().Be(50.00m);
|
||||
var concurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
||||
|
||||
// PUT (update name and charge)
|
||||
var updatedName = Util.Uniquify("Updated Travel Rate");
|
||||
var putPayload = $$"""
|
||||
{"id":{{Id}},"concurrency":{{concurrency}},"name":"{{updatedName}}","active":true,"notes":null,"wiki":null,"customFields":"{}","tags":[],"accountNumber":null,"cost":50.00,"charge":80.00,"unit":null,"contractOnly":false}
|
||||
""";
|
||||
a = await Util.PutAsync("travel-rate", token, putPayload);
|
||||
Util.ValidateHTTPStatusCode(a, 200);
|
||||
var newConcurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
||||
newConcurrency.Should().NotBe(concurrency, "concurrency should have been bumped by the update");
|
||||
|
||||
// Verify the update was persisted
|
||||
a = await Util.GetAsync($"travel-rate/{Id}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(updatedName);
|
||||
a.ObjectResponse["data"]["charge"].Value<decimal>().Should().Be(80.00m);
|
||||
|
||||
// CONCURRENCY VIOLATION: PUT with stale concurrency should return 409
|
||||
a = await Util.PutAsync("travel-rate", token, putPayload); // putPayload still has old concurrency
|
||||
Util.ValidateConcurrencyError(a);
|
||||
|
||||
// DELETE
|
||||
a = await Util.DeleteAsync($"travel-rate/{Id}", token);
|
||||
Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// Confirm deleted
|
||||
a = await Util.GetAsync($"travel-rate/{Id}", token);
|
||||
Util.ValidateResponseNotFound(a);
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
|
||||
|
||||
public class TaxCodeCrud
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Full CRUD for a TaxCode, including concurrency violation and 404 on deleted record.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task CRUD()
|
||||
{
|
||||
var token = await Util.GetTokenAsync("BizAdmin");
|
||||
|
||||
// CREATE
|
||||
var name = Util.Uniquify("Test Tax Code");
|
||||
var payload = $$"""
|
||||
{"id":0,"concurrency":0,"name":"{{name}}","active":true,"notes":null,"wiki":null,"customFields":"{}","tags":[],"taxAPct":5.0,"taxBPct":2.5,"taxOnTax":false}
|
||||
""";
|
||||
|
||||
ApiResponse a = await Util.PostAsync("tax-code", token, payload);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
long Id = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
Id.Should().BeGreaterThan(0);
|
||||
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(name);
|
||||
|
||||
// GET
|
||||
a = await Util.GetAsync($"tax-code/{Id}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(name);
|
||||
a.ObjectResponse["data"]["taxAPct"].Value<decimal>().Should().Be(5.0m);
|
||||
a.ObjectResponse["data"]["taxBPct"].Value<decimal>().Should().Be(2.5m);
|
||||
var concurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
||||
|
||||
// PUT (update name only; taxAPct and taxBPct stay the same)
|
||||
var updatedName = Util.Uniquify("Updated Tax Code");
|
||||
var putPayload = $$"""
|
||||
{"id":{{Id}},"concurrency":{{concurrency}},"name":"{{updatedName}}","active":true,"notes":null,"wiki":null,"customFields":"{}","tags":[],"taxAPct":5.0,"taxBPct":2.5,"taxOnTax":false}
|
||||
""";
|
||||
a = await Util.PutAsync("tax-code", token, putPayload);
|
||||
Util.ValidateHTTPStatusCode(a, 200);
|
||||
var newConcurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
||||
newConcurrency.Should().NotBe(concurrency, "concurrency should have been bumped by the update");
|
||||
|
||||
// Verify the update was persisted
|
||||
a = await Util.GetAsync($"tax-code/{Id}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(updatedName);
|
||||
a.ObjectResponse["data"]["taxAPct"].Value<decimal>().Should().Be(5.0m);
|
||||
a.ObjectResponse["data"]["taxBPct"].Value<decimal>().Should().Be(2.5m);
|
||||
|
||||
// CONCURRENCY VIOLATION: PUT with stale concurrency should return 409
|
||||
a = await Util.PutAsync("tax-code", token, putPayload); // putPayload still has old concurrency
|
||||
Util.ValidateConcurrencyError(a);
|
||||
|
||||
// DELETE
|
||||
a = await Util.DeleteAsync($"tax-code/{Id}", token);
|
||||
Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// Confirm deleted
|
||||
a = await Util.GetAsync($"tax-code/{Id}", token);
|
||||
Util.ValidateResponseNotFound(a);
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
|
||||
}//eons
|
||||
60
Schedule/ScheduleReads.cs
Normal file
60
Schedule/ScheduleReads.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using Xunit;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace raven_integration
|
||||
{
|
||||
|
||||
public class ScheduleReads
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Verify the service schedule endpoint returns a well-formed response for a 7-day window
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task ServiceSchedule_ReturnsSuccessfully()
|
||||
{
|
||||
var token = await Util.GetTokenAsync("BizAdmin");
|
||||
|
||||
var start = DateTime.Today.ToString("o");
|
||||
var end = DateTime.Today.AddDays(7).ToString("o");
|
||||
|
||||
var payload = $$"""
|
||||
{"view":2,"start":"{{start}}","end":"{{end}}","wisuColorSource":2,"tags":[],"dark":false}
|
||||
""";
|
||||
|
||||
ApiResponse a = await Util.PostAsync("schedule/svc", token, payload);
|
||||
Util.ValidateHTTPStatusCode(a, 200);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
|
||||
// Verify the response shape — items must be present as a JArray (may be empty if no WOs are scheduled)
|
||||
a.ObjectResponse["data"]["items"].Should().NotBeNull("schedule/svc response data should contain an items key");
|
||||
a.ObjectResponse["data"]["items"].Should().BeOfType<JArray>("items should be a JArray");
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Verify the personal schedule endpoint returns a well-formed response for a 7-day window
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task PersonalSchedule_ReturnsSuccessfully()
|
||||
{
|
||||
var token = await Util.GetTokenAsync("BizAdmin");
|
||||
|
||||
var start = DateTime.Today.ToString("o");
|
||||
var end = DateTime.Today.AddDays(7).ToString("o");
|
||||
|
||||
var payload = $$"""
|
||||
{"view":2,"start":"{{start}}","end":"{{end}}","wisuColorSource":2,"wisu":false,"reviews":false,"reminders":false,"dark":false,"userId":0}
|
||||
""";
|
||||
|
||||
ApiResponse a = await Util.PostAsync("schedule/personal", token, payload);
|
||||
Util.ValidateHTTPStatusCode(a, 200);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
}
|
||||
|
||||
|
||||
//==================================================
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
130
Unit/UnitCrud.cs
Normal file
130
Unit/UnitCrud.cs
Normal file
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace raven_integration
|
||||
{
|
||||
|
||||
public class UnitModelCrud
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Full CRUD for a UnitModel, including concurrency violation.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task CRUD()
|
||||
{
|
||||
var token = await Util.GetTokenAsync("BizAdmin");
|
||||
|
||||
// CREATE
|
||||
var name = Util.Uniquify("Test Unit Model");
|
||||
var payload = $$"""
|
||||
{"id":0,"concurrency":0,"name":"{{name}}","active":true,"notes":null,"wiki":null,"customFields":"{}","tags":[],"vendorId":null,"upc":null,"lifeTimeWarranty":false,"introducedDate":null,"discontinued":false,"discontinuedDate":null,"warrantyLength":null,"warrantyTerms":null}
|
||||
""";
|
||||
|
||||
ApiResponse a = await Util.PostAsync("unit-model", token, payload);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
long Id = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
Id.Should().BeGreaterThan(0);
|
||||
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(name);
|
||||
|
||||
// GET
|
||||
a = await Util.GetAsync($"unit-model/{Id}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(name);
|
||||
var concurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
||||
|
||||
// PUT (update name)
|
||||
var updatedName = Util.Uniquify("Updated Unit Model");
|
||||
var putPayload = $$"""
|
||||
{"id":{{Id}},"concurrency":{{concurrency}},"name":"{{updatedName}}","active":true,"notes":null,"wiki":null,"customFields":"{}","tags":[],"vendorId":null,"upc":null,"lifeTimeWarranty":false,"introducedDate":null,"discontinued":false,"discontinuedDate":null,"warrantyLength":null,"warrantyTerms":null}
|
||||
""";
|
||||
a = await Util.PutAsync("unit-model", token, putPayload);
|
||||
Util.ValidateHTTPStatusCode(a, 200);
|
||||
var newConcurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
||||
newConcurrency.Should().NotBe(concurrency, "concurrency should have been bumped by the update");
|
||||
|
||||
// Verify the update was persisted
|
||||
a = await Util.GetAsync($"unit-model/{Id}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(updatedName);
|
||||
|
||||
// CONCURRENCY VIOLATION: PUT with stale concurrency should return 409
|
||||
a = await Util.PutAsync("unit-model", token, putPayload); // putPayload still has old concurrency
|
||||
Util.ValidateConcurrencyError(a);
|
||||
|
||||
// DELETE
|
||||
a = await Util.DeleteAsync($"unit-model/{Id}", token);
|
||||
Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// Confirm deleted
|
||||
a = await Util.GetAsync($"unit-model/{Id}", token);
|
||||
Util.ValidateResponseNotFound(a);
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
|
||||
|
||||
public class UnitCrud
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Full CRUD for a Unit, including concurrency violation.
|
||||
/// Uses customerId=1, the always-safe seeded customer.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task CRUD()
|
||||
{
|
||||
var token = await Util.GetTokenAsync("BizAdmin");
|
||||
|
||||
// CREATE
|
||||
var serial = Util.Uniquify("SN").Replace(" ", "");
|
||||
var payload = $$"""
|
||||
{"id":0,"concurrency":0,"active":true,"notes":null,"wiki":null,"customFields":"{}","tags":[],"serial":"{{serial}}","customerId":1,"parentUnitId":null,"unitModelId":null,"unitHasOwnAddress":false,"boughtHere":false,"purchasedFromVendorId":null,"receipt":null,"purchasedDate":null,"description":null,"replacedByUnitId":null,"overrideModelWarranty":false,"warrantyLength":null,"warrantyTerms":null,"contractId":null,"contractExpires":null,"metered":false,"lifeTimeWarranty":false,"text1":null,"text2":null,"text3":null,"text4":null,"address":null,"city":null,"region":null,"country":null,"addressPostal":null,"latitude":null,"longitude":null}
|
||||
""";
|
||||
|
||||
ApiResponse a = await Util.PostAsync("unit", token, payload);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
long Id = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
Id.Should().BeGreaterThan(0);
|
||||
a.ObjectResponse["data"]["serial"].Value<string>().Should().Be(serial);
|
||||
|
||||
// GET
|
||||
a = await Util.GetAsync($"unit/{Id}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
a.ObjectResponse["data"]["serial"].Value<string>().Should().Be(serial);
|
||||
a.ObjectResponse["data"]["customerId"].Value<long>().Should().Be(1);
|
||||
var concurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
||||
|
||||
// PUT (update description)
|
||||
var putPayload = $$"""
|
||||
{"id":{{Id}},"concurrency":{{concurrency}},"active":true,"notes":null,"wiki":null,"customFields":"{}","tags":[],"serial":"{{serial}}","customerId":1,"parentUnitId":null,"unitModelId":null,"unitHasOwnAddress":false,"boughtHere":false,"purchasedFromVendorId":null,"receipt":null,"purchasedDate":null,"description":"Updated description","replacedByUnitId":null,"overrideModelWarranty":false,"warrantyLength":null,"warrantyTerms":null,"contractId":null,"contractExpires":null,"metered":false,"lifeTimeWarranty":false,"text1":null,"text2":null,"text3":null,"text4":null,"address":null,"city":null,"region":null,"country":null,"addressPostal":null,"latitude":null,"longitude":null}
|
||||
""";
|
||||
a = await Util.PutAsync("unit", token, putPayload);
|
||||
Util.ValidateHTTPStatusCode(a, 200);
|
||||
var newConcurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
||||
newConcurrency.Should().NotBe(concurrency, "concurrency should have been bumped by the update");
|
||||
|
||||
// Verify the update was persisted
|
||||
a = await Util.GetAsync($"unit/{Id}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
a.ObjectResponse["data"]["description"].Value<string>().Should().Be("Updated description");
|
||||
|
||||
// CONCURRENCY VIOLATION: PUT with stale concurrency should return 409
|
||||
a = await Util.PutAsync("unit", token, putPayload); // putPayload still has old concurrency
|
||||
Util.ValidateConcurrencyError(a);
|
||||
|
||||
// DELETE
|
||||
a = await Util.DeleteAsync($"unit/{Id}", token);
|
||||
Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// Confirm deleted
|
||||
a = await Util.GetAsync($"unit/{Id}", token);
|
||||
Util.ValidateResponseNotFound(a);
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
75
Vendor/VendorCrud.cs
vendored
Normal file
75
Vendor/VendorCrud.cs
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using Xunit;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace raven_integration
|
||||
{
|
||||
|
||||
public class VendorCrud
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Full CRUD for a Vendor, including concurrency violation and alert retrieval.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task CRUD()
|
||||
{
|
||||
var token = await Util.GetTokenAsync("BizAdmin");
|
||||
|
||||
// CREATE
|
||||
var name = Util.Uniquify("Test Vendor");
|
||||
var payload = $$"""
|
||||
{"id":0,"concurrency":0,"name":"{{name}}","active":true,"notes":"Test vendor notes","wiki":null,"customFields":"{\"c1\":\"test\"}","tags":[],"contact":null,"contactNotes":null,"alertNotes":"Vendor alert test text","webAddress":null,"accountNumber":null,"phone1":"555-1234","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("vendor", token, payload);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
long Id = a.ObjectResponse["data"]["id"].Value<long>();
|
||||
Id.Should().BeGreaterThan(0);
|
||||
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(name);
|
||||
|
||||
// GET
|
||||
a = await Util.GetAsync($"vendor/{Id}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(name);
|
||||
a.ObjectResponse["data"]["phone1"].Value<string>().Should().Be("555-1234");
|
||||
var concurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
||||
|
||||
// GET ALERT
|
||||
a = await Util.GetAsync($"vendor/alert/{Id}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
a.ObjectResponse["data"].Value<string>().Should().Be("Vendor alert test text");
|
||||
|
||||
// PUT (update name and phone)
|
||||
var updatedName = Util.Uniquify("Updated Vendor");
|
||||
var putPayload = $$"""
|
||||
{"id":{{Id}},"concurrency":{{concurrency}},"name":"{{updatedName}}","active":true,"notes":"Test vendor notes","wiki":null,"customFields":"{\"c1\":\"test\"}","tags":[],"contact":null,"contactNotes":null,"alertNotes":"Vendor alert test text","webAddress":null,"accountNumber":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("vendor", token, putPayload);
|
||||
Util.ValidateHTTPStatusCode(a, 200);
|
||||
var newConcurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
||||
newConcurrency.Should().NotBe(concurrency, "concurrency should have been bumped by the update");
|
||||
|
||||
// Verify the update was persisted
|
||||
a = await Util.GetAsync($"vendor/{Id}", token);
|
||||
Util.ValidateDataReturnResponseOk(a);
|
||||
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(updatedName);
|
||||
a.ObjectResponse["data"]["phone1"].Value<string>().Should().Be("555-9999");
|
||||
|
||||
// CONCURRENCY VIOLATION: PUT with stale concurrency should return 409
|
||||
a = await Util.PutAsync("vendor", token, putPayload); // putPayload still has old concurrency
|
||||
Util.ValidateConcurrencyError(a);
|
||||
|
||||
// DELETE
|
||||
a = await Util.DeleteAsync($"vendor/{Id}", token);
|
||||
Util.ValidateHTTPStatusCode(a, 204);
|
||||
|
||||
// Confirm deleted
|
||||
a = await Util.GetAsync($"vendor/{Id}", token);
|
||||
Util.ValidateResponseNotFound(a);
|
||||
}
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
106
claude.md
106
claude.md
@@ -2,32 +2,98 @@
|
||||
|
||||
## What This Is
|
||||
|
||||
Old integration test project for the AyaNova v8 API. These tests were written during initial API development and have not been maintained. They target .NET Core 3.x and need evaluation before any further use.
|
||||
Integration test project for the AyaNova v8 API. Tests target .NET 8 and run against a locally running server (`http://localhost:7575`).
|
||||
|
||||
## Current Goal
|
||||
Run with:
|
||||
|
||||
Triage these tests to determine what is salvageable:
|
||||
```
|
||||
dotnet test raven-integration.csproj
|
||||
```
|
||||
|
||||
1. Get the project compiling against .NET 8 (update TargetFramework, NuGet packages)
|
||||
2. Inventory all test files and what they cover
|
||||
3. Identify tests that still match current API endpoints and behavior
|
||||
4. Decide per-test: **fix it**, **rewrite it**, or **delete it**
|
||||
5. Broken tests that aren't worth fixing should be deleted — broken tests are worse than no tests
|
||||
## Current Status (March 2026)
|
||||
|
||||
## The Current Backend
|
||||
The initial triage and rebuild is complete. The project now compiles against net8.0, old broken tests have been removed or rewritten, and new gap-coverage tests have been added. All tests pass except three that are blocked by known server-side bugs:
|
||||
|
||||
The production API these tests should validate lives at `../raven/server/AyaNova/` and runs ASP.NET Core 8 with PostgreSQL. Compare test expectations against the actual current API, not what the API looked like when these tests were written.
|
||||
- **CustomerWithWorkorders_cannotbedeleted** — server returns 500 instead of 400 (case 4653)
|
||||
- **ContractCrud.CRUD** — minor server-side issue (tracked separately)
|
||||
- **DirectMessage_SendAndDelete** - server issue prevents delivery as considered a "duplicate" incorrectly. (case 4654)
|
||||
|
||||
## Known Issues
|
||||
These are intentionally left failing until server-side fixes are applied as part of the refactor phase. Except the directMessage test is set to skip as it has a 65 second timeout that would slow down testing unnecessarily.
|
||||
|
||||
- `dotnet test` fails because multiple .csproj/.sln files exist — specify the correct one
|
||||
- Project targets outdated .NET Core 3.x — needs TargetFramework update to net8.0
|
||||
- NuGet package references are likely outdated and may need updating
|
||||
- Test data setup and API endpoints may have changed significantly
|
||||
## What Is Covered
|
||||
|
||||
## What Success Looks Like
|
||||
### Tier 1 (Critical) — Complete
|
||||
|
||||
- A clear inventory of what tests exist and what they cover
|
||||
- Any salvageable tests updated to compile and run against .NET 8
|
||||
- Dead or irrelevant tests removed
|
||||
- A gap analysis: what critical API paths have no test coverage
|
||||
- **DataList** — `DataList/DataListOperations.cs`
|
||||
- Saved filter CRUD (create, list, update visibility, delete, confirm deleted)
|
||||
- Default filter creation blocked by API
|
||||
- String filtering: contains, starts-with, equals, not-equal
|
||||
- Date filtering: range (GT/LT), relative keywords (*yesterday*, *tomorrow*, *thisyear*, *NULL*)
|
||||
- Multi-column AND filter
|
||||
- Sort ascending and descending on date column
|
||||
- Pagination (offset/limit, totalRecordCount consistency, non-overlapping pages)
|
||||
- Column view CRUD (custom column order round-trip)
|
||||
- Rights enforcement (user with no roles gets 403)
|
||||
|
||||
- **Quote** — `Quote/QuoteCrud.cs`
|
||||
- Header CRUD + concurrency violation + id-from-number lookup
|
||||
- QuoteItem CRUD
|
||||
- QuoteItemLabor sub-type CRUD
|
||||
|
||||
- **Customer** — `Customer/CustomerCrud.cs`
|
||||
- CRUD + concurrency violation + alert retrieval
|
||||
- Referential integrity: customer with linked work order cannot be deleted (test exists; server bug pending fix)
|
||||
|
||||
### Tier 2 (Important) — Complete
|
||||
|
||||
- **Contract** — `Contract/ContractCrud.cs` — CRUD + concurrency
|
||||
- **PM** — `PM/PMCrud.cs` — header CRUD + PMItem + concurrency + id-from-number
|
||||
- **Authorization** — `Authentication/AuthRights.cs` — unauthenticated 401, unauthorized 403
|
||||
- **Schedule** — `Schedule/ScheduleReads.cs` — service schedule + personal schedule reads
|
||||
- **Part** — `Part/PartCrud.cs` — CRUD + concurrency + serial numbers round-trip
|
||||
|
||||
### Tier 3 (Reference Data / Nice to Have) — Complete
|
||||
|
||||
- **Vendor** — `Vendor/VendorCrud.cs` — CRUD + concurrency + alert
|
||||
- **ServiceRate / TravelRate / TaxCode** — `Reference/ReferenceCrud.cs` — CRUD + concurrency
|
||||
- **Unit / UnitModel** — `Unit/UnitCrud.cs` — CRUD for both types
|
||||
- **Memo** — `Memo/MemoCrud.cs` — create + read + delete
|
||||
- **Notification** — `Notification/NotificationOps.cs` — count, list, send, delete
|
||||
|
||||
### Already Covered (from original tests)
|
||||
|
||||
- User CRUD, concurrency, password change, inactive user login
|
||||
- Project CRUD and concurrency
|
||||
- WorkOrder + nested items/parts/labor/units CRUD
|
||||
- Attachments (upload/download/delete/authorization)
|
||||
- Search (phrase, wildcard, tags, serial, deletion cleanup)
|
||||
- Custom forms
|
||||
- Pick lists
|
||||
- Translations
|
||||
- Event log (object log, user log, pagination)
|
||||
- Tag bulk operations
|
||||
- Server health, metrics, log files
|
||||
- Global biz settings (fetch + round-trip PUT)
|
||||
|
||||
## What Is Intentionally Not Covered
|
||||
|
||||
- **Report generation** — async job pattern with polling; deferred
|
||||
- **Stock level updates** — require seeded warehouse IDs not known at test time
|
||||
- **Auth roles deep test** — role-change behavioral tests; the existing AuthRights.cs covers the critical 401/403 paths
|
||||
- **EnumList, Name lookup** — low-risk reference reads; not needed for refactor protection
|
||||
|
||||
## Known Configuration
|
||||
|
||||
- API base: `http://localhost:7575/api/v8/`
|
||||
- Seeded logins: `superuser` (password: `l3tm3in`), `BizAdmin`, `OpsAdmin`, `SubContractorRestricted`
|
||||
- Seeded customer id=1 is always safe to reference
|
||||
- Time zone adjustment for date filters: -7 (see `util.cs` `TIME_ZONE_ADJUSTMENT`)
|
||||
- DataList class names ARE the list keys (e.g., `WorkOrderDataList`, `CustomerDataList`)
|
||||
|
||||
## What Comes Next (Refactor Phase)
|
||||
|
||||
This test suite exists to protect against regressions during the Step D refactor. Before starting the refactor:
|
||||
|
||||
1. Confirm all tests pass (except the three known server bugs above)
|
||||
2. Fix the three server-side bugs (case 4653, case 4654 and contract issue)
|
||||
3. Then proceed with the Step D refactor — tests will catch regressions
|
||||
|
||||
157
todo.md
157
todo.md
@@ -1,157 +0,0 @@
|
||||
# Gap Analysis: Tests Needed Before Refactoring
|
||||
|
||||
## Context
|
||||
|
||||
The goal of adding tests before a refactor is **regression protection** — not completeness for its own sake. Every test added should answer the question: *"If I change how this code works internally, will a test catch it if I accidentally changed the behavior?"*
|
||||
|
||||
---
|
||||
|
||||
## What's Already Well-Covered (Don't Duplicate)
|
||||
|
||||
- User CRUD, concurrency, referential integrity, password change, inactive user login
|
||||
- Project CRUD and concurrency
|
||||
- WorkOrder + nested items/parts/labor/units CRUD
|
||||
- Attachments (upload/download/delete/authorization)
|
||||
- Search (phrase, wildcard, tags, serial, deletion cleanup)
|
||||
- Custom forms
|
||||
- Pick lists
|
||||
- Translations
|
||||
- Event log (object log, user log, pagination)
|
||||
- Tag bulk operations
|
||||
- Auth rights (unauthenticated 401, unauthorized 403)
|
||||
- Server health, metrics, log files
|
||||
|
||||
---
|
||||
|
||||
## Tier 1 — Critical Gaps (Block the Refactor)
|
||||
|
||||
These cover the most logic-dense areas where redundancies are most likely to exist. Without these, a refactor is risky.
|
||||
|
||||
### 1. DataList — Filtering, Sorting, Saved Filters
|
||||
|
||||
The single biggest gap. ~80 tests are commented out from case 4648. This is the cross-cutting query infrastructure used everywhere in the UI. A refactor of service/repository layers will almost certainly touch this.
|
||||
|
||||
**Add:**
|
||||
- Filter by each field type: string (contains/starts-with/ends-with/equals), date range, boolean, decimal/numeric range, null/not-null
|
||||
- Multi-condition AND filters
|
||||
- Sort ascending/descending on multiple fields
|
||||
- Pagination (limit/offset) correctness
|
||||
- Saved filter CRUD: create, list, update, delete, apply
|
||||
- Column view CRUD: create, update, delete, apply
|
||||
- Rights enforcement: user without list rights gets 403
|
||||
|
||||
Maps to: `DataListController`, `DataListSavedFilterController`, `DataListColumnViewController`
|
||||
|
||||
### 2. Quote — Full CRUD With Nested Hierarchy
|
||||
|
||||
Quote is structurally parallel to WorkOrder (header → items → labor/parts/expenses/tasks/travels/units → states) but has zero test coverage. If the refactor consolidates duplicated patterns between these two, tests on both are needed.
|
||||
|
||||
**Add:**
|
||||
- Quote header CRUD + concurrency violation
|
||||
- Quote item CRUD
|
||||
- At least one nested sub-type (labor or parts) CRUD
|
||||
- Quote state CRUD
|
||||
- Lookup by quote number (`/id-from-number/{number}`)
|
||||
|
||||
### 3. Customer — Core Relationships
|
||||
|
||||
Customer is the root of the customer hierarchy. Many other objects (WO, Quote, Contract, PM) belong to a Customer. A refactor could touch customer-related join logic.
|
||||
|
||||
**Add:**
|
||||
- Customer CRUD + concurrency violation
|
||||
- Customer alert retrieval
|
||||
- Referential integrity: customer with work orders should not be deletable (or verify delete cascades correctly per business rules)
|
||||
|
||||
---
|
||||
|
||||
## Tier 2 — Important Before a Thorough Refactor
|
||||
|
||||
These areas have enough complexity that refactoring without coverage is risky, but slightly less immediately critical than Tier 1.
|
||||
|
||||
### 4. Part — Inventory and Serials
|
||||
|
||||
Parts have a richer data model than simple CRUD (serials, stock levels per warehouse, cost tracking). If the refactor touches inventory or cost logic:
|
||||
|
||||
**Add:**
|
||||
- Part CRUD
|
||||
- Get/update serial numbers
|
||||
- Get/update stock levels
|
||||
- Get/update part cost
|
||||
|
||||
### 5. Contract CRUD
|
||||
|
||||
Contracts can have complex billing rules. Even basic CRUD coverage ensures the object graph survives the refactor.
|
||||
|
||||
**Add:**
|
||||
- Contract CRUD + concurrency violation
|
||||
|
||||
### 6. Preventive Maintenance (PM) CRUD
|
||||
|
||||
PM is structurally similar to WorkOrder (hierarchical items). If the refactor consolidates WorkOrder/PM patterns, both need coverage.
|
||||
|
||||
**Add:**
|
||||
- PM header CRUD
|
||||
- PM item CRUD
|
||||
- At least one PM item sub-type CRUD
|
||||
|
||||
### 7. Authorization Roles
|
||||
|
||||
The `AuthorizationRoles` business logic file is the largest in the codebase (~64KB). A refactor here is high risk.
|
||||
|
||||
**Add:**
|
||||
- List authorization roles
|
||||
- Verify role-based rights are enforced for at least 2-3 different role types
|
||||
- Verify that rights changes take effect (create a user with a role, test access, change role, re-test)
|
||||
|
||||
### 8. Schedule Reads
|
||||
|
||||
The schedule endpoint is used for the main dispatch board. Even basic read tests give a safety net.
|
||||
|
||||
**Add:**
|
||||
- Fetch service schedule for a date range
|
||||
- Fetch user schedule for a date range
|
||||
|
||||
---
|
||||
|
||||
## Tier 3 — Lower Priority (Nice to Have Before Refactor)
|
||||
|
||||
These are worth adding eventually but won't block a careful refactor if Tier 1 and 2 are covered.
|
||||
|
||||
| Area | What to Add |
|
||||
|---|---|
|
||||
| **Memo** | CRUD + concurrency |
|
||||
| **Unit / UnitModel** | CRUD |
|
||||
| **Vendor** | CRUD |
|
||||
| **ServiceRate / TravelRate / TaxCode** | CRUD (simple reference data) |
|
||||
| **Notification** | New count, fetch, delete one |
|
||||
| **EnumList** | Get by key, list keys |
|
||||
| **Name lookup** | Get name for a known object |
|
||||
| **Report** | Create, list, generate data (async job pattern) |
|
||||
| **GlobalBizSettings** | Fetch client settings |
|
||||
|
||||
---
|
||||
|
||||
## Cross-Cutting Concerns to Verify Everywhere
|
||||
|
||||
These should be confirmed on any *new* test entity, not just the ones already tested:
|
||||
|
||||
| Concern | Why It Matters for Refactor |
|
||||
|---|---|
|
||||
| **Concurrency (`ETag`/`rowVersion`)** | Optimistic locking is often in a shared base class — refactoring that class risks breaking all objects |
|
||||
| **Soft delete / referential integrity** | If a shared delete handler is consolidated, it must still block deletes where referenced |
|
||||
| **Custom field round-trip** | Custom fields are stored/retrieved through a shared mechanism; any refactor of that mechanism needs a test |
|
||||
| **Tags round-trip** | Tags use batch operations through shared infrastructure |
|
||||
|
||||
---
|
||||
|
||||
## Recommended Order of Work
|
||||
|
||||
1. **DataList filtering + sorting + saved filters** — biggest risk, most logic, rebuild from scratch
|
||||
2. **Quote CRUD** — parallels WorkOrder, needed to de-risk consolidation of the two
|
||||
3. **Customer CRUD + referential integrity** — root of object graph
|
||||
4. **Contract CRUD** — completes the main business objects
|
||||
5. **Auth roles / rights behavior** — largest single business logic file
|
||||
6. **Part CRUD** — inventory complexity
|
||||
7. **PM CRUD** — parallels WorkOrder
|
||||
8. **Schedule reads**
|
||||
9. Everything in Tier 3 as capacity allows
|
||||
Reference in New Issue
Block a user