From 27b77c5273543c2f30d94334c5cb269b0fa9fb08 Mon Sep 17 00:00:00 2001 From: John Cardinal Date: Tue, 18 Dec 2018 16:33:35 +0000 Subject: [PATCH] --- .vscode/launch.json | 28 + .vscode/settings.json | 3 + .vscode/tasks.json | 15 + ApiResponse.cs | 13 + ApiTextResponse.cs | 13 + Attachments/AttachmentTest.cs | 182 + Authentication/Auth.cs | 36 + AutoId.cs | 26 + AyaType/AyaType.cs | 36 + DataFilter/DataFilterCrud.cs | 176 + DataFilter/DataFilterFilteringLists.cs | 6237 ++++++++++++++++++++++++ DataFilter/DataFilterOrderBy.cs | 439 ++ Enum/EnumListOps.cs | 48 + EventLog/EventLog.cs | 122 + ImportV7/ImportV7.cs | 57 + JobOperations/JobOperations.cs | 59 + Locale/Locale.cs | 186 + Locale/RequestedLocaleKeys.cs | 67 + LogFiles/LogFiles.cs | 32 + Metrics/Metrics.cs | 51 + Privacy/Privacy.cs | 28 + Search/SearchOps.cs | 577 +++ ServerState/ServerStateTest.cs | 79 + User/UserCrud.cs | 592 +++ User/UserInactive.cs | 31 + User/UserOptionsRu.cs | 127 + Widget/WidgetCrud.cs | 244 + Widget/WidgetLists.cs | 214 + Widget/WidgetRights.cs | 261 + Widget/WidgetValidationTests.cs | 232 + burntest.bat | 8 + raven-integration.csproj | 17 + testdata/ayanova.data.dump.xxx.zip | Bin 0 -> 87822 bytes testdata/test.png | Bin 0 -> 11745 bytes testdata/test.zip | Bin 0 -> 2306 bytes util.cs | 404 ++ 36 files changed, 10640 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 ApiResponse.cs create mode 100644 ApiTextResponse.cs create mode 100644 Attachments/AttachmentTest.cs create mode 100644 Authentication/Auth.cs create mode 100644 AutoId.cs create mode 100644 AyaType/AyaType.cs create mode 100644 DataFilter/DataFilterCrud.cs create mode 100644 DataFilter/DataFilterFilteringLists.cs create mode 100644 DataFilter/DataFilterOrderBy.cs create mode 100644 Enum/EnumListOps.cs create mode 100644 EventLog/EventLog.cs create mode 100644 ImportV7/ImportV7.cs create mode 100644 JobOperations/JobOperations.cs create mode 100644 Locale/Locale.cs create mode 100644 Locale/RequestedLocaleKeys.cs create mode 100644 LogFiles/LogFiles.cs create mode 100644 Metrics/Metrics.cs create mode 100644 Privacy/Privacy.cs create mode 100644 Search/SearchOps.cs create mode 100644 ServerState/ServerStateTest.cs create mode 100644 User/UserCrud.cs create mode 100644 User/UserInactive.cs create mode 100644 User/UserOptionsRu.cs create mode 100644 Widget/WidgetCrud.cs create mode 100644 Widget/WidgetLists.cs create mode 100644 Widget/WidgetRights.cs create mode 100644 Widget/WidgetValidationTests.cs create mode 100644 burntest.bat create mode 100644 raven-integration.csproj create mode 100644 testdata/ayanova.data.dump.xxx.zip create mode 100644 testdata/test.png create mode 100644 testdata/test.zip create mode 100644 util.cs diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..8a3f0fd --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,28 @@ +{ + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/bin/Debug/netcoreapp2.0/raven-integration.dll", + "args": [], + "cwd": "${workspaceFolder}", + // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window + "console": "internalConsole", + "stopAtEntry": false, + "internalConsoleOptions": "openOnSessionStart" + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c2aabc7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.codeLens": true +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..51afd6a --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,15 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "taskName": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/raven-integration.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/ApiResponse.cs b/ApiResponse.cs new file mode 100644 index 0000000..b626901 --- /dev/null +++ b/ApiResponse.cs @@ -0,0 +1,13 @@ +using System.Net.Http; +using Newtonsoft.Json.Linq; + +namespace raven_integration +{ + public class ApiResponse + { + public HttpResponseMessage HttpResponse {get;set;} + public JObject ObjectResponse {get;set;} + + + }//eoc +}//eons \ No newline at end of file diff --git a/ApiTextResponse.cs b/ApiTextResponse.cs new file mode 100644 index 0000000..6cad35a --- /dev/null +++ b/ApiTextResponse.cs @@ -0,0 +1,13 @@ +using System.Net.Http; +using Newtonsoft.Json.Linq; + +namespace raven_integration +{ + public class ApiTextResponse + { + public HttpResponseMessage HttpResponse {get;set;} + public string TextResponse {get;set;} + + + }//eoc +}//eons \ No newline at end of file diff --git a/Attachments/AttachmentTest.cs b/Attachments/AttachmentTest.cs new file mode 100644 index 0000000..bc77b88 --- /dev/null +++ b/Attachments/AttachmentTest.cs @@ -0,0 +1,182 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Net.Http; +using System.Net.Http.Headers; +using System.IO; + +namespace raven_integration +{ + //https://stackoverflow.com/questions/17725882/testing-asp-net-web-api-multipart-form-data-file-upload + public class AttachmentTest + { + /// + /// test attach CRUD + /// + [Fact] + public async void AttachmentUploadDownloadDeleteShouldWork() + { + + ////////////////////////////////////////// + //// Upload the files + MultipartFormDataContent formDataContent = new MultipartFormDataContent(); + + //Form data like the bizobject type and id + formDataContent.Add(new StringContent("2"), name: "AttachToObjectType"); + formDataContent.Add(new StringContent("1"), name: "AttachToObjectId"); + //or if testing non-existant this is probably safe: long.MaxValue + + + StreamContent file1 = new StreamContent(File.OpenRead($"{Util.TEST_DATA_FOLDER}\\test.png")); + file1.Headers.ContentType = new MediaTypeHeaderValue("image/png"); + file1.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data"); + file1.Headers.ContentDisposition.FileName = "test.png"; + formDataContent.Add(file1); + StreamContent file2 = new StreamContent(File.OpenRead($"{Util.TEST_DATA_FOLDER}\\test.zip")); + file2.Headers.ContentType = new MediaTypeHeaderValue("application/zip"); + file2.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data"); + file2.Headers.ContentDisposition.FileName = "test.zip"; + formDataContent.Add(file2); + + //create via inventory full test user as attachments use the role of the object attaching to + ApiResponse a = await Util.PostFormDataAsync("Attachment", formDataContent, await Util.GetTokenAsync("InventoryFull")); + + + Util.ValidateDataReturnResponseOk(a); + + + long lTestPngAttachmentId = a.ObjectResponse["data"][0]["id"].Value(); + long lTestZipAttachmentId = a.ObjectResponse["data"][1]["id"].Value(); + + //saw negative values on a db issue that I corrected (I think) + //Keeping these in case it arises again, if it does, see log, it's a db error with async issue of some kind + lTestPngAttachmentId.Should().BePositive(); + lTestZipAttachmentId.Should().BePositive(); + + ////////////////////////////////////////// + //// DOWNLOAD: Get the file attachment + + //Get the inventoryfull account download token + // { + // "data": { + // "dlkey": "w7iE1cXF8kOxo8eomd1r8A", + // "expires": "2018-04-25T23:45:39.05665" + // } + // } + a = await Util.GetAsync("Attachment/DownloadToken", await Util.GetTokenAsync("InventoryFull")); + Util.ValidateDataReturnResponseOk(a); + string downloadToken = a.ObjectResponse["data"]["dlkey"].Value(); + + //now get the file https://rockfish.ayanova.com/api/rfcaseblob/download/248?dlkey=9O2eDAAlZ0Wknj19SBK2iA + var dlresponse = await Util.DownloadFileAsync("Attachment/Download/" + lTestZipAttachmentId.ToString() + "?dlkey=" + downloadToken, await Util.GetTokenAsync("InventoryFull")); + + //ensure it's the zip file we expected + dlresponse.Content.Headers.ContentDisposition.FileName.Should().Be("test.zip"); + dlresponse.Content.Headers.ContentLength.Should().BeGreaterThan(2000); + + + ////////////////////////////////////////// + //// DELETE: Delete the file attachments + ApiResponse d = await Util.DeleteAsync("Attachment/" + lTestPngAttachmentId.ToString(), await Util.GetTokenAsync("InventoryFull")); + Util.ValidateHTTPStatusCode(d, 204); + + d = await Util.DeleteAsync("Attachment/" + lTestZipAttachmentId.ToString(), await Util.GetTokenAsync("InventoryFull")); + Util.ValidateHTTPStatusCode(d, 204); + + + } + + + + /// + /// test no rights + /// + [Fact] + public async void NoRightsTest() + { + + MultipartFormDataContent formDataContent = new MultipartFormDataContent(); + + formDataContent.Add(new StringContent("2"), name: "AttachToObjectType"); + formDataContent.Add(new StringContent("1"), name: "AttachToObjectId"); + + StreamContent file1 = new StreamContent(File.OpenRead($"{Util.TEST_DATA_FOLDER}\\test.png")); + file1.Headers.ContentType = new MediaTypeHeaderValue("image/png"); + file1.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data"); + file1.Headers.ContentDisposition.FileName = "test.png"; + formDataContent.Add(file1); + + //ERROR CONDITION: BizAdminLimited user should not be able to attach a file to a widget + ApiResponse a = await Util.PostFormDataAsync("Attachment", formDataContent, await Util.GetTokenAsync("BizAdminLimited")); + + //2004 unauthorized + Util.ValidateErrorCodeResponse(a, 2004, 401); + + } + + + /// + /// test not attachable + /// + [Fact] + public async void UnattachableTest() + { + MultipartFormDataContent formDataContent = new MultipartFormDataContent(); + + //Form data bizobject type and id + + //HERE IS THE ERROR CONDITION: LICENSE TYPE OBJECT WHICH IS UNATTACHABLE + formDataContent.Add(new StringContent("5"), name: "AttachToObjectType"); + formDataContent.Add(new StringContent("1"), name: "AttachToObjectId"); + + StreamContent file1 = new StreamContent(File.OpenRead($"{Util.TEST_DATA_FOLDER}\\test.png")); + file1.Headers.ContentType = new MediaTypeHeaderValue("image/png"); + file1.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data"); + file1.Headers.ContentDisposition.FileName = "test.png"; + formDataContent.Add(file1); + ApiResponse a = await Util.PostFormDataAsync("Attachment", formDataContent, await Util.GetTokenAsync("InventoryFull")); + + //2203 unattachable object + Util.ValidateErrorCodeResponse(a, 2203, 400); + + } + + + /// + /// test bad object values + /// + [Fact] + public async void BadObject() + { + MultipartFormDataContent formDataContent = new MultipartFormDataContent(); + + //Form data like the bizobject type and id + formDataContent.Add(new StringContent("2"), name: "AttachToObjectType"); + + //HERE IS THE ERROR CONDITION, A NON EXISTENT ID VALUE FOR THE WIDGET + formDataContent.Add(new StringContent(long.MaxValue.ToString()), name: "AttachToObjectId");//non-existent widget + + StreamContent file1 = new StreamContent(File.OpenRead($"{Util.TEST_DATA_FOLDER}\\test.png")); + file1.Headers.ContentType = new MediaTypeHeaderValue("image/png"); + file1.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data"); + file1.Headers.ContentDisposition.FileName = "test.png"; + formDataContent.Add(file1); + ApiResponse a = await Util.PostFormDataAsync("Attachment", formDataContent, await Util.GetTokenAsync("InventoryFull")); + + //2203 invalid attachment object + Util.ValidateErrorCodeResponse(a, 2203, 400); + + } + + + + + + + //================================================== + + }//eoc +}//eons diff --git a/Authentication/Auth.cs b/Authentication/Auth.cs new file mode 100644 index 0000000..9e9233f --- /dev/null +++ b/Authentication/Auth.cs @@ -0,0 +1,36 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; + +namespace raven_integration +{ + + public class Auth + { + /// + /// + /// + [Fact] + public async void BadLoginShouldNotWork() + { + //Expect status code 401 and result: + // {{ + // "error": { + // "code": "2003", + // "message": "Authentication failed" + // } + // }} + + dynamic d = new JObject(); + d.login = "BOGUS"; + d.password = "ACCOUNT"; + ApiResponse a = await Util.PostAsync("Auth", null, d.ToString()); + Util.ValidateErrorCodeResponse(a,2003,401); + } + + //================================================== + + }//eoc +}//eons + diff --git a/AutoId.cs b/AutoId.cs new file mode 100644 index 0000000..676fef9 --- /dev/null +++ b/AutoId.cs @@ -0,0 +1,26 @@ +using System; +using System.Threading.Tasks; +namespace raven_integration +{ + public class AutoId + { + private readonly object valueLock = new object(); + private uint currentValue; //postgre bigint + + public AutoId(uint initialValue) + { + currentValue = initialValue; + } + + public uint GetNext() + { + lock (valueLock) + { + currentValue += 1; + if (currentValue == 0) + currentValue += 1; + return currentValue; + } + } + } +} \ No newline at end of file diff --git a/AyaType/AyaType.cs b/AyaType/AyaType.cs new file mode 100644 index 0000000..17a2d86 --- /dev/null +++ b/AyaType/AyaType.cs @@ -0,0 +1,36 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; +using System.Collections.Generic; +using System.Collections.Concurrent; + +namespace raven_integration +{ + + public class AyaType + { + + /// + /// + /// + [Fact] + public async void AyaTypeListShouldWork() + { + ApiResponse a = await Util.GetAsync("AyaType"); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + //there should be at least 8 of them (at time of writing late March 2018) + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterOrEqualTo(8); + + //Number 2 is widget and list is zero based so confirm: + a.ObjectResponse["data"][2]["id"].Value().Should().Be(2); + a.ObjectResponse["data"][2]["name"].Value().Should().Be("Widget [Attachable]"); + + } + + + //================================================== + + }//eoc +}//eons diff --git a/DataFilter/DataFilterCrud.cs b/DataFilter/DataFilterCrud.cs new file mode 100644 index 0000000..d337fd1 --- /dev/null +++ b/DataFilter/DataFilterCrud.cs @@ -0,0 +1,176 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; +using System.Collections.Generic; +using System.Collections.Concurrent; + +namespace raven_integration +{ + + public class DataFilterCrud + { + + /// + /// Test all CRUD routes + /// + [Fact] + public async void CRUD() + { + //CREATE + dynamic d = new JObject(); + d.name = Util.Uniquify("Test DataFilter"); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + //"[{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("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long Id = a.ObjectResponse["data"]["id"].Value(); + + + //RETRIEVE + //Get one + a = await Util.GetAsync("DataFilter/" + Id.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateDataReturnResponseOk(a); + a.ObjectResponse["data"]["name"].Value().Should().StartWith("Test DataFilter"); + + //Get as alternate user should work for public filter + a = await Util.GetAsync("DataFilter/" + Id.ToString(), await Util.GetTokenAsync("SubContractorLimited")); + Util.ValidateDataReturnResponseOk(a); + a.ObjectResponse["data"]["name"].Value().Should().StartWith("Test DataFilter"); + + + //UPDATE + + //PUT, make private + d["public"] = false; + d.name = Util.Uniquify("Put - Test DataFilter (privatized)"); + d.concurrencyToken = a.ObjectResponse["data"]["concurrencyToken"].Value(); + a = await Util.PutAsync("DataFilter/" + Id.ToString(), await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateHTTPStatusCode(a, 200); + + //check PUT worked + a = await Util.GetAsync("DataFilter/" + Id.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateNoErrorInResponse(a); + a.ObjectResponse["data"]["name"].Value().Should().Be(d.name.ToString()); + + + //FETCH DISALLOWED + //Get as alternate user should fail for private filter + a = await Util.GetAsync("DataFilter/" + Id.ToString(), await Util.GetTokenAsync("SubContractorLimited")); + Util.ValidateResponseNotFound(a); + + // //DELETE + ApiResponse DELETETestResponse = await Util.DeleteAsync("DataFilter/" + Id.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(DELETETestResponse, 204); + + } + + /// + /// + /// + [Fact] + public async void InvalidListKeyShouldFail() + { + //CREATE + dynamic d = new JObject(); + d.name = Util.Uniquify("Test DataFilter"); + // d.ownerId = 1L; + 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("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateErrorCodeResponse(a, 2200, 400); + Util.ShouldContainValidationError(a, "ListKey", "InvalidValue"); + + } + + + /// + /// + /// + [Fact] + public async void InvalidFieldNameShouldFail() + { + //CREATE + dynamic d = new JObject(); + d.name = Util.Uniquify("Test DataFilter"); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + //"[{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("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateErrorCodeResponse(a, 2200, 400); + Util.ShouldContainValidationError(a, "Filter", "InvalidValue"); + + } + + + + /// + /// + /// + [Fact] + public async void InvalidOperatorShouldFail() + { + //CREATE + dynamic d = new JObject(); + d.name = Util.Uniquify("Test DataFilter"); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + //"[{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("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateErrorCodeResponse(a, 2200, 400); + Util.ShouldContainValidationError(a, "Filter", "InvalidValue"); + + } + + + + //================================================== + + }//eoc +}//eons diff --git a/DataFilter/DataFilterFilteringLists.cs b/DataFilter/DataFilterFilteringLists.cs new file mode 100644 index 0000000..cb0c267 --- /dev/null +++ b/DataFilter/DataFilterFilteringLists.cs @@ -0,0 +1,6237 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; +using System.Collections.Generic; +using System.Collections.Concurrent; + +namespace raven_integration +{ + + + /* + + EVERY TYPE, EVERY OP + + Using the widget object test all filtering options + for all data types, all operation types + + This is the supertest to always confirm the filtering code is working as expected. + + + */ + + public class DataFilterFilteringLists + { + + + + + + + /////////////////////////////////////////////////////////////////////////////// + //DATE + // + + #region DATE FILTER TESTS + + public const string TokenYesterday = "{[yesterday]}"; + public const string TokenToday = "{[today]}"; + public const string TokenTomorrow = "{[tomorrow]}"; + public const string TokenLastWeek = "{[lastweek]}"; + public const string TokenThisWeek = "{[thisweek]}"; + public const string TokenNextWeek = "{[nextweek]}"; + public const string TokenLastMonth = "{[lastmonth]}"; + public const string TokenThisMonth = "{[thismonth]}"; + public const string TokenNextMonth = "{[nextmonth]}"; + public const string TokenFourteenDayWindow = "{[14daywindow]}"; + public const string TokenPast = "{[past]}"; + public const string TokenFuture = "{[future]}"; + public const string TokenLastYear = "{[lastyear]}"; + public const string TokenThisYear = "{[thisyear]}"; + public const string TokenInTheLast3Months = "{[last3months]}"; + public const string TokenInTheLast6Months = "{[last6months]}"; + public const string TokenInTheLastYear = "{[lastcalendaryear]}"; + + //More business time frames + + public const string TokenYearToDate = "{[yeartodate]}"; + + public const string TokenPast90Days = "{[past90days]}"; + public const string TokenPast30Days = "{[past30days]}"; + public const string TokenPast24Hours = "{[past24hours]}"; + + + + + #region DATE REGULAR FILTERS + + /// + /// + /// + [Fact] + public async void DateOpEqualityFilterWorks() + { + + var WidgetNameStart = "DateOpEqualityFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = new DateTime(1968, 3, 12, 10, 0, 0).ToUniversalTime(); + w.endDate = new DateTime(1968, 3, 12, 11, 0, 0).ToUniversalTime(); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = new DateTime(1968, 3, 10, 10, 0, 0).ToUniversalTime(); + w.endDate = new DateTime(1968, 3, 10, 11, 0, 0).ToUniversalTime(); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = new DateTime(1968, 3, 12, 10, 0, 0).ToUniversalTime(); + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + + /// + /// + /// + [Fact] + public async void DateOpGreaterThanFilterWorks() + { + + var WidgetNameStart = "DateOpGreaterThanFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = new DateTime(1970, 3, 12, 10, 0, 0).ToUniversalTime(); + w.endDate = new DateTime(1970, 3, 12, 11, 0, 0).ToUniversalTime(); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = new DateTime(1968, 3, 10, 10, 0, 0).ToUniversalTime(); + w.endDate = new DateTime(1968, 3, 10, 11, 0, 0).ToUniversalTime(); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpGreaterThan; + FilterItem.value = new DateTime(1970, 3, 12, 9, 0, 0).ToUniversalTime(); + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + + /// + /// + /// + [Fact] + public async void DateOpGreaterThanOrEqualToFilterWorks() + { + + var WidgetNameStart = "DateOpGreaterThanOrEqualToFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = new DateTime(1970, 3, 12, 10, 0, 0).ToUniversalTime(); + w.endDate = new DateTime(1970, 3, 12, 11, 0, 0).ToUniversalTime(); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = new DateTime(1968, 3, 10, 10, 0, 0).ToUniversalTime(); + w.endDate = new DateTime(1968, 3, 10, 11, 0, 0).ToUniversalTime(); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpGreaterThanOrEqualTo; + FilterItem.value = new DateTime(1970, 3, 12, 10, 0, 0).ToUniversalTime(); + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + /// + /// + /// + [Fact] + public async void DateOpLessThanFilterWorks() + { + + var WidgetNameStart = "DateOpLessThanFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = new DateTime(1970, 3, 12, 10, 0, 0).ToUniversalTime(); + w.endDate = new DateTime(1970, 3, 12, 11, 0, 0).ToUniversalTime(); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = new DateTime(1970, 4, 10, 10, 0, 0).ToUniversalTime(); + w.endDate = new DateTime(1970, 4, 10, 11, 0, 0).ToUniversalTime(); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpLessThan; + FilterItem.value = new DateTime(1970, 3, 12, 11, 0, 0).ToUniversalTime(); + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + + /// + /// + /// + [Fact] + public async void DateOpLessThanOrEqualToFilterWorks() + { + + var WidgetNameStart = "DateOpLessThanOrEqualToFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = new DateTime(1970, 3, 12, 10, 0, 0).ToUniversalTime(); + w.endDate = new DateTime(1970, 3, 12, 11, 0, 0).ToUniversalTime(); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = new DateTime(1970, 4, 10, 10, 0, 0).ToUniversalTime(); + w.endDate = new DateTime(1970, 4, 10, 11, 0, 0).ToUniversalTime(); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpLessThanOrEqualTo; + FilterItem.value = new DateTime(1970, 3, 12, 10, 0, 0).ToUniversalTime(); + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + + /// + /// + /// + [Fact] + public async void DateOpNotEqualToFilterWorks() + { + + var WidgetNameStart = "DateOpNotEqualToFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = new DateTime(1970, 3, 12, 10, 0, 0).ToUniversalTime(); + w.endDate = new DateTime(1970, 3, 12, 11, 0, 0).ToUniversalTime(); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = new DateTime(1970, 4, 10, 10, 0, 0).ToUniversalTime(); + w.endDate = new DateTime(1970, 4, 10, 11, 0, 0).ToUniversalTime(); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpNotEqual; + FilterItem.value = new DateTime(1970, 4, 10, 10, 0, 0).ToUniversalTime(); + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + + /// + /// + /// + [Fact] + public async void DateTokenYesterdayFilterWorks() + { + + var WidgetNameStart = "DateTokenYesterdayFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = DateTime.UtcNow.AddDays(-1); + w.endDate = DateTime.UtcNow.AddHours(1).AddDays(-1); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = DateTime.UtcNow; + w.endDate = DateTime.UtcNow.AddHours(1); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = TokenYesterday; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + + /// + /// + /// + [Fact] + public async void DateTokenTodayFilterWorks() + { + + var WidgetNameStart = "DateTokenTodayFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = DateTime.UtcNow; + w.endDate = DateTime.UtcNow.AddHours(1); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = DateTime.UtcNow.AddDays(-1); + w.endDate = DateTime.UtcNow.AddHours(1).AddDays(-1); + + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = TokenToday; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + /// + /// + /// + [Fact] + public async void DateTokenTomorrowFilterWorks() + { + + var WidgetNameStart = "DateTokenTomorrowFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = DateTime.UtcNow.AddDays(1); + w.endDate = DateTime.UtcNow.AddDays(1).AddHours(1); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = DateTime.UtcNow; + w.endDate = DateTime.UtcNow.AddHours(1); + + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = TokenTomorrow; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + /// + /// + /// + [Fact] + public async void DateTokenLastWeekFilterWorks() + { + + var WidgetNameStart = "DateTokenLastWeekFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + //My theory is any date - 7 days is last week if you go sunday to sunday + w.startDate = DateTime.UtcNow.AddDays(-7); + w.endDate = DateTime.UtcNow.AddHours(1).AddDays(-7); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = DateTime.UtcNow; + w.endDate = DateTime.UtcNow.AddHours(1); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = TokenLastWeek; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + /// + /// + /// + [Fact] + public async void DateTokenThisWeekFilterWorks() + { + + var WidgetNameStart = "DateTokenThisWeekFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = DateTime.UtcNow; + w.endDate = DateTime.UtcNow.AddHours(1); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + //My theory is any date - 7 days is last week if you go sunday to sunday + w.startDate = DateTime.UtcNow.AddDays(-7); + w.endDate = DateTime.UtcNow.AddHours(1).AddDays(-7); + + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = TokenThisWeek; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + /// + /// + /// + [Fact] + public async void DateTokenNextWeekFilterWorks() + { + + var WidgetNameStart = "DateTokenNextWeekFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + //My theory is any date + 7 days is next week if you go sunday to sunday + w.startDate = DateTime.UtcNow.AddDays(7); + w.endDate = DateTime.UtcNow.AddHours(1).AddDays(7); + + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = DateTime.UtcNow; + w.endDate = DateTime.UtcNow.AddHours(1); + + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = TokenNextWeek; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + /// + /// + /// + [Fact] + public async void DateTokenLastMonthFilterWorks() + { + + var WidgetNameStart = "DateTokenLastMonthFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + //First day of this month minus 2 days equals second to last day of last month + w.startDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1, 00, 00, 00).AddDays(-2).ToUniversalTime(); + w.endDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1, 00, 00, 00).AddDays(-2).AddHours(1).ToUniversalTime(); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + //Put it right at midnight this month to ensure boundaries are respected + w.startDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1, 00, 00, 00).ToUniversalTime(); + w.endDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1, 00, 00, 00).AddHours(1).ToUniversalTime(); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = TokenLastMonth; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + + /// + /// + /// + [Fact] + public async void DateTokenThisMonthFilterWorks() + { + + var WidgetNameStart = "DateTokenThisMonthFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + //Put it right at midnight this month to ensure boundaries are respected + w.startDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1, 00, 00, 00).ToUniversalTime(); + w.endDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1, 00, 00, 00).AddHours(1).ToUniversalTime(); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + //First day of this month minus 2 days equals second to last day of last month + w.startDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1, 00, 00, 00).AddDays(-2).ToUniversalTime(); + w.endDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1, 00, 00, 00).AddDays(-2).AddHours(1).ToUniversalTime(); + + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = TokenThisMonth; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + + /// + /// + /// + [Fact] + public async void DateTokenNextMonthFilterWorks() + { + + var WidgetNameStart = "DateTokenNextMonthFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + //Put it right at midnight next month to ensure boundaries are respected + w.startDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1, 00, 00, 00).AddMonths(1).ToUniversalTime(); + w.endDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1, 00, 00, 00).AddMonths(1).AddHours(1).ToUniversalTime(); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + //First day of next month minus 1 second + w.startDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1, 00, 00, 00).AddMonths(1).AddSeconds(-1).ToUniversalTime(); + w.endDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1, 00, 00, 00).AddMonths(1).AddSeconds(-1).AddHours(1).ToUniversalTime(); + + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = TokenNextMonth; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + //SELECT *, xmin FROM AWIDGET where name Like 'DateTokenNextMonthFilterWorks%' AND startdate >'2019-01-01T15:59:59.0000000Z' AND startdate <'2019-02-01T16:00:00.0000000Z' + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + /// + /// + /// + [Fact] + public async void DateToken14DayWindowFilterWorks() + { + + var WidgetNameStart = "DateToken14DayWindowFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + + w.startDate = DateTime.UtcNow; + w.endDate = DateTime.UtcNow.AddHours(1); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + //9 days ago will be outside the 14 day window + w.startDate = DateTime.UtcNow.AddDays(-9); + w.endDate = DateTime.UtcNow.AddDays(-9).AddHours(1); + + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = TokenFourteenDayWindow; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + + /// + /// + /// + [Fact] + public async void DateTokenPastFilterWorks() + { + + var WidgetNameStart = "DateTokenPastFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + //Test if for the past, definitely going to be in the past when the list is fetched after saving, but will it work with the server on another machine?? + //Best to pad in 5 minutes to be on the safe side, these days no two computers on earth should be out by more than 5 minutes to UTC from each other + w.startDate = DateTime.UtcNow.AddMinutes(-5); + w.endDate = DateTime.UtcNow.AddMinutes(-5).AddHours(1); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + //8 days ago will be outside the 14 day window + w.startDate = DateTime.UtcNow.AddMonths(1); + w.endDate = DateTime.UtcNow.AddMonths(1).AddHours(1); + + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = TokenPast; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + /// + /// + /// + [Fact] + public async void DateTokenFutureFilterWorks() + { + + var WidgetNameStart = "DateTokenFutureFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + //#### + w.startDate = DateTime.UtcNow.AddMinutes(5); + w.endDate = DateTime.UtcNow.AddMinutes(5).AddHours(1); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + //#### + w.startDate = DateTime.UtcNow; + w.endDate = DateTime.UtcNow.AddHours(1); + + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = TokenFuture; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + /// + /// + /// + [Fact] + public async void DateTokenLastYearFilterWorks() + { + + var WidgetNameStart = "DateTokenLastYearFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + //#### + w.startDate = DateTime.UtcNow.AddYears(-1).AddMinutes(-5); + w.endDate = DateTime.UtcNow.AddYears(-1).AddMinutes(-5).AddHours(1); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + //#### + w.startDate = DateTime.UtcNow; + w.endDate = DateTime.UtcNow.AddHours(1); + + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = TokenLastYear; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + + + + /// + /// + /// + [Fact] + public async void DateTokenThisYearFilterWorks() + { + + var WidgetNameStart = "DateTokenThisYearFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + //#### + w.startDate = DateTime.UtcNow; + w.endDate = DateTime.UtcNow.AddHours(1); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + //#### + w.startDate = DateTime.UtcNow.AddYears(-1).AddMinutes(-5); + w.endDate = DateTime.UtcNow.AddYears(-1).AddMinutes(-5).AddHours(1); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = TokenThisYear; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + + + + /// + /// + /// + [Fact] + public async void DateTokenInTheLast3MonthsFilterWorks() + { + + var WidgetNameStart = "DateTokenInTheLast3MonthsFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + //#### + w.startDate = DateTime.UtcNow.AddMonths(-3).AddMinutes(5); + w.endDate = DateTime.UtcNow.AddMonths(-3).AddMinutes(5).AddHours(1); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + //#### + w.startDate = DateTime.UtcNow.AddMonths(-3).AddMinutes(-5); + w.endDate = DateTime.UtcNow.AddMonths(-3).AddMinutes(-5).AddHours(1); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = TokenInTheLast3Months; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + /// + /// + /// + [Fact] + public async void DateTokenInTheLast6MonthsFilterWorks() + { + + var WidgetNameStart = "DateTokenInTheLast6MonthsFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + //#### + w.startDate = DateTime.UtcNow.AddMonths(-6).AddMinutes(5); + w.endDate = DateTime.UtcNow.AddMonths(-6).AddMinutes(5).AddHours(1); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + //#### + w.startDate = DateTime.UtcNow.AddMonths(-6).AddMinutes(-5); + w.endDate = DateTime.UtcNow.AddMonths(-6).AddMinutes(-5).AddHours(1); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = TokenInTheLast6Months; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + /// + /// + /// + [Fact] + public async void DateTokenInTheLastYearFilterWorks() + { + + var WidgetNameStart = "DateTokenInTheLastYearFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + //#### + w.startDate = DateTime.UtcNow.AddDays(-365).AddMinutes(5); + w.endDate = DateTime.UtcNow.AddDays(-365).AddMinutes(5).AddHours(1); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + //#### + w.startDate = DateTime.UtcNow.AddDays(-365).AddMinutes(-5); + w.endDate = DateTime.UtcNow.AddDays(-365).AddMinutes(-5).AddHours(1); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = TokenInTheLastYear; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + #endregion DATE REGULAR FILTERS + + #region DATE TOKEN FILTERS + + + + + /// + /// + /// + [Fact] + public async void DateTokenYearToDateFilterWorks() + { + + var WidgetNameStart = "DateTokenYearToDateFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //BUILD DATES FOR THIS TEST + + DateTime InclusiveStartDate; + DateTime ExclusiveStartDate; + + //################################################################################## + var FilterToken = TokenYearToDate; + InclusiveStartDate = new DateTime(DateTime.Now.Year, 1, 1, 00, 5, 00).ToUniversalTime(); + ExclusiveStartDate = new DateTime(DateTime.Now.Year - 1, 4, 1, 00, 5, 00).ToUniversalTime(); + //################################################################################## + + + DateTime InclusiveEndDate = InclusiveStartDate.AddHours(1); + DateTime ExclusiveEndDate = ExclusiveStartDate.AddHours(1); + + //CREATE TEST WIDGETS + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = InclusiveStartDate; + w.endDate = InclusiveEndDate; + + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = ExclusiveStartDate; + w.endDate = ExclusiveEndDate; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = FilterToken; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + + /// + /// + /// + [Fact] + public async void DateTokenPast90DaysFilterWorks() + { + + var WidgetNameStart = "DateTokenPast90DaysFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //BUILD DATES FOR THIS TEST + + DateTime InclusiveStartDate; + DateTime ExclusiveStartDate; + + //################################################################################## + var FilterToken = TokenPast90Days; + InclusiveStartDate = DateTime.UtcNow.AddDays(-90).AddMinutes(5).ToUniversalTime(); + ExclusiveStartDate = DateTime.UtcNow.AddDays(-90).AddMinutes(-5).ToUniversalTime(); + //################################################################################## + + + DateTime InclusiveEndDate = InclusiveStartDate.AddHours(1); + DateTime ExclusiveEndDate = ExclusiveStartDate.AddHours(1); + + //CREATE TEST WIDGETS + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = InclusiveStartDate; + w.endDate = InclusiveEndDate; + + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = ExclusiveStartDate; + w.endDate = ExclusiveEndDate; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = FilterToken; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + /// + /// + /// + [Fact] + public async void DateTokenPast30DaysFilterWorks() + { + + var WidgetNameStart = "DateTokenPast30DaysFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //BUILD DATES FOR THIS TEST + + DateTime InclusiveStartDate; + DateTime ExclusiveStartDate; + + //################################################################################## + var FilterToken = TokenPast30Days; + InclusiveStartDate = DateTime.UtcNow.AddDays(-30).AddMinutes(5).ToUniversalTime(); + ExclusiveStartDate = DateTime.UtcNow.AddDays(-30).AddMinutes(-5).ToUniversalTime(); + //################################################################################## + + + DateTime InclusiveEndDate = InclusiveStartDate.AddHours(1); + DateTime ExclusiveEndDate = ExclusiveStartDate.AddHours(1); + + //CREATE TEST WIDGETS + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = InclusiveStartDate; + w.endDate = InclusiveEndDate; + + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = ExclusiveStartDate; + w.endDate = ExclusiveEndDate; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = FilterToken; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + /// + /// + /// + [Fact] + public async void DateTokenPast24HoursFilterWorks() + { + + var WidgetNameStart = "DateTokenPast24HoursFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //BUILD DATES FOR THIS TEST + + DateTime InclusiveStartDate; + DateTime ExclusiveStartDate; + + //################################################################################## + var FilterToken = TokenPast24Hours; + InclusiveStartDate = DateTime.UtcNow.AddHours(-24).AddMinutes(5).ToUniversalTime(); + ExclusiveStartDate = DateTime.UtcNow.AddHours(-24).AddMinutes(-5).ToUniversalTime(); + //################################################################################## + + + DateTime InclusiveEndDate = InclusiveStartDate.AddHours(1); + DateTime ExclusiveEndDate = ExclusiveStartDate.AddHours(1); + + //CREATE TEST WIDGETS + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = InclusiveStartDate; + w.endDate = InclusiveEndDate; + + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = ExclusiveStartDate; + w.endDate = ExclusiveEndDate; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //## INCLUSIVE FILTER + dynamic FilterItem = new JObject(); + FilterItem.fld = "startdate"; + FilterItem.op = Util.OpEquality; + FilterItem.value = FilterToken; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + #endregion date token filters + //======== + + #endregion date filter tests + + + + /////////////////////////////////////////////////////////////////////////////// + //TEXT + // + + #region STRING FILTER TESTS + + /// + /// + /// + [Fact] + public async void TextOpEqualityFilterWorks() + { + + var TestName = "TextOpEqualityFilterWorks"; + var WidgetRunNameStart = Util.Uniquify(TestName); + + List InclusiveWidgetIdList = new List(); + List ExclusiveWidgetIdList = new List(); + + //CREATE 4 TEST WIDGETS + //two inclusive and two not inclusive + + //first inclusive widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "aardvark"; + w.roles = 0; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second inclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //first exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "zebra"; + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetRunNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetRunNameStart; + dfilter.Add(DataFilterNameStart); + + //active bool test filter + dynamic DataFilterActive = new JObject(); + DataFilterActive.fld = "notes"; + DataFilterActive.op = Util.OpEquality; + DataFilterActive.value = "aardvark"; + dfilter.Add(DataFilterActive); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least two records + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(1); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (InclusiveWidgetIdList.Contains(o["id"].Value())) + InclusiveMatchCount++; + if (ExclusiveWidgetIdList.Contains(o["id"].Value())) + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().Be(InclusiveWidgetIdList.Count); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + foreach (long l in InclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + foreach (long l in ExclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + + + /// + /// Specifically test a string with an apostrophe in it (for inclusive) + /// + [Fact] + public async void TextApostropheOpEqualityFilterWorks() + { + + var TestName = "TextApostropheOpEqualityFilterWorks"; + var WidgetRunNameStart = Util.Uniquify(TestName); + + List InclusiveWidgetIdList = new List(); + List ExclusiveWidgetIdList = new List(); + + //CREATE 4 TEST WIDGETS + //two inclusive and two not inclusive + + //first inclusive widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "O'Flaherty's pub"; + w.roles = 0; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second inclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //first exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Outback steak house"; + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetRunNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetRunNameStart; + dfilter.Add(DataFilterNameStart); + + //active bool test filter + dynamic DataFilterActive = new JObject(); + DataFilterActive.fld = "notes"; + DataFilterActive.op = Util.OpEquality; + DataFilterActive.value = "O'Flaherty's pub"; + dfilter.Add(DataFilterActive); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least two records + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(1); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (InclusiveWidgetIdList.Contains(o["id"].Value())) + InclusiveMatchCount++; + if (ExclusiveWidgetIdList.Contains(o["id"].Value())) + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().Be(InclusiveWidgetIdList.Count); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + foreach (long l in InclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + foreach (long l in ExclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + /// + /// specifically test a string with an ampersand character in it for inclusive (finding it) + /// + [Fact] + public async void TextAmpersandOpEqualityFilterWorks() + { + + var TestName = "TextAmpersandOpEqualityFilterWorks"; + var WidgetRunNameStart = Util.Uniquify(TestName); + + List InclusiveWidgetIdList = new List(); + List ExclusiveWidgetIdList = new List(); + + //CREATE 4 TEST WIDGETS + //two inclusive and two not inclusive + + //first inclusive widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Bill & Ted's excellent adventure"; + w.roles = 0; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second inclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //first exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Strange things are afoot at the Circle-K"; + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetRunNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetRunNameStart; + dfilter.Add(DataFilterNameStart); + + //active bool test filter + dynamic DataFilterActive = new JObject(); + DataFilterActive.fld = "notes"; + DataFilterActive.op = Util.OpEquality; + DataFilterActive.value = "Bill & Ted's excellent adventure"; + dfilter.Add(DataFilterActive); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least two records + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(1); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (InclusiveWidgetIdList.Contains(o["id"].Value())) + InclusiveMatchCount++; + if (ExclusiveWidgetIdList.Contains(o["id"].Value())) + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().Be(InclusiveWidgetIdList.Count); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + foreach (long l in InclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + foreach (long l in ExclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + /// + /// specifically test a non english unicode string + /// + [Fact] + public async void TextUnicodeOpEqualityFilterWorks() + { + + var TestName = "TextUnicodeOpEqualityFilterWorks"; + var WidgetRunNameStart = Util.Uniquify(TestName); + + List InclusiveWidgetIdList = new List(); + List ExclusiveWidgetIdList = new List(); + + //CREATE 4 TEST WIDGETS + //two inclusive and two not inclusive + + //first inclusive widget + var InclusiveTestString = "Ādam Iñtërnâtiônàližætiøn"; + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = InclusiveTestString; + w.roles = 0; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second inclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //first exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Adam Internationalization"; + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetRunNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetRunNameStart; + dfilter.Add(DataFilterNameStart); + + //active bool test filter + dynamic DataFilterActive = new JObject(); + DataFilterActive.fld = "notes"; + DataFilterActive.op = Util.OpEquality; + DataFilterActive.value = InclusiveTestString; + dfilter.Add(DataFilterActive); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least two records + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(1); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (InclusiveWidgetIdList.Contains(o["id"].Value())) + InclusiveMatchCount++; + if (ExclusiveWidgetIdList.Contains(o["id"].Value())) + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().Be(InclusiveWidgetIdList.Count); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + foreach (long l in InclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + foreach (long l in ExclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + /// + /// + /// + [Fact] + public async void TextOpGreaterThanFilterWorks() + { + + var TestName = "TextOpGreaterThanFilterWorks"; + var WidgetRunNameStart = Util.Uniquify(TestName); + + List InclusiveWidgetIdList = new List(); + List ExclusiveWidgetIdList = new List(); + + //CREATE 4 TEST WIDGETS + //two inclusive and two not inclusive + + //first inclusive widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Alabama"; + w.roles = 0; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second inclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //first exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Aardvark"; + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetRunNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetRunNameStart; + dfilter.Add(DataFilterNameStart); + + //active bool test filter + dynamic DataFilterActive = new JObject(); + DataFilterActive.fld = "notes"; + DataFilterActive.op = Util.OpGreaterThan; + DataFilterActive.value = "Aardvark"; + dfilter.Add(DataFilterActive); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least two records + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(1); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (InclusiveWidgetIdList.Contains(o["id"].Value())) + InclusiveMatchCount++; + if (ExclusiveWidgetIdList.Contains(o["id"].Value())) + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().Be(InclusiveWidgetIdList.Count); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + foreach (long l in InclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + foreach (long l in ExclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + /// + /// + /// + [Fact] + public async void TextOpGreaterThanOrEqualToFilterWorks() + { + + var TestName = "TextOpGreaterThanOrEqualToFilterWorks"; + var WidgetRunNameStart = Util.Uniquify(TestName); + + List InclusiveWidgetIdList = new List(); + List ExclusiveWidgetIdList = new List(); + + //CREATE 4 TEST WIDGETS + //two inclusive and two not inclusive + + //first inclusive widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Bjorn"; + w.roles = 0; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second inclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //first exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Bing"; + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetRunNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetRunNameStart; + dfilter.Add(DataFilterNameStart); + + //active bool test filter + dynamic DataFilterActive = new JObject(); + DataFilterActive.fld = "notes"; + DataFilterActive.op = Util.OpGreaterThanOrEqualTo; + DataFilterActive.value = "Bjarn"; + dfilter.Add(DataFilterActive); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least two records + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(1); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (InclusiveWidgetIdList.Contains(o["id"].Value())) + InclusiveMatchCount++; + if (ExclusiveWidgetIdList.Contains(o["id"].Value())) + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().Be(InclusiveWidgetIdList.Count); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + foreach (long l in InclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + foreach (long l in ExclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + /// + /// + /// + [Fact] + public async void TextOpLessThanFilterWorks() + { + + var TestName = "TextOpLessThanFilterWorks"; + var WidgetRunNameStart = Util.Uniquify(TestName); + + List InclusiveWidgetIdList = new List(); + List ExclusiveWidgetIdList = new List(); + + //CREATE 4 TEST WIDGETS + //two inclusive and two not inclusive + + //first inclusive widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "California"; + w.roles = 0; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second inclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //first exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Cthulu"; + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetRunNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetRunNameStart; + dfilter.Add(DataFilterNameStart); + + //active bool test filter + dynamic DataFilterActive = new JObject(); + DataFilterActive.fld = "notes"; + DataFilterActive.op = Util.OpLessThan; + DataFilterActive.value = "Celery"; + dfilter.Add(DataFilterActive); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least two records + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(1); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (InclusiveWidgetIdList.Contains(o["id"].Value())) + InclusiveMatchCount++; + if (ExclusiveWidgetIdList.Contains(o["id"].Value())) + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().Be(InclusiveWidgetIdList.Count); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + foreach (long l in InclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + foreach (long l in ExclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + /// + /// + /// + [Fact] + public async void TextOpLessThanOrEqualToFilterWorks() + { + + var TestName = "TextOpLessThanOrEqualToFilterWorks"; + var WidgetRunNameStart = Util.Uniquify(TestName); + + List InclusiveWidgetIdList = new List(); + List ExclusiveWidgetIdList = new List(); + + //CREATE 4 TEST WIDGETS + //two inclusive and two not inclusive + + //first inclusive widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Donut"; + w.roles = 0; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second inclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //first exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Duvet"; + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetRunNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetRunNameStart; + dfilter.Add(DataFilterNameStart); + + //active bool test filter + dynamic DataFilterActive = new JObject(); + DataFilterActive.fld = "notes"; + DataFilterActive.op = Util.OpLessThanOrEqualTo; + DataFilterActive.value = "Dusseldorf"; + dfilter.Add(DataFilterActive); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least two records + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(1); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (InclusiveWidgetIdList.Contains(o["id"].Value())) + InclusiveMatchCount++; + if (ExclusiveWidgetIdList.Contains(o["id"].Value())) + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().Be(InclusiveWidgetIdList.Count); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + foreach (long l in InclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + foreach (long l in ExclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + /// + /// + /// + [Fact] + public async void TextOpNotEqualFilterWorks() + { + + var TestName = "TextOpNotEqualFilterWorks"; + var WidgetRunNameStart = Util.Uniquify(TestName); + + List InclusiveWidgetIdList = new List(); + List ExclusiveWidgetIdList = new List(); + + //CREATE 4 TEST WIDGETS + //two inclusive and two not inclusive + + //first inclusive widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Egg Salad Sandwich"; + w.roles = 0; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second inclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //first exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Elephant"; + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetRunNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetRunNameStart; + dfilter.Add(DataFilterNameStart); + + //active bool test filter + dynamic DataFilterActive = new JObject(); + DataFilterActive.fld = "notes"; + DataFilterActive.op = Util.OpNotEqual; + DataFilterActive.value = "Elephant"; + dfilter.Add(DataFilterActive); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least two records + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(1); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (InclusiveWidgetIdList.Contains(o["id"].Value())) + InclusiveMatchCount++; + if (ExclusiveWidgetIdList.Contains(o["id"].Value())) + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().Be(InclusiveWidgetIdList.Count); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + foreach (long l in InclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + foreach (long l in ExclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + /// + /// + /// + [Fact] + public async void TextOpNotContainsFilterWorks() + { + var TestName = "TextOpNotContainsFilterWorks"; + var WidgetRunNameStart = Util.Uniquify(TestName); + + List InclusiveWidgetIdList = new List(); + List ExclusiveWidgetIdList = new List(); + + //CREATE 4 TEST WIDGETS + //two inclusive and two not inclusive + + //first inclusive widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Gray poupon"; + w.roles = 0; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second inclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //first exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Get shorty"; + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetRunNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetRunNameStart; + dfilter.Add(DataFilterNameStart); + + //active bool test filter + dynamic DataFilterActive = new JObject(); + DataFilterActive.fld = "notes"; + DataFilterActive.op = Util.OpNotContains; + DataFilterActive.value = "short"; + dfilter.Add(DataFilterActive); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least two records + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(1); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (InclusiveWidgetIdList.Contains(o["id"].Value())) + InclusiveMatchCount++; + if (ExclusiveWidgetIdList.Contains(o["id"].Value())) + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().Be(InclusiveWidgetIdList.Count); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + foreach (long l in InclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + foreach (long l in ExclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + /// + /// + /// + [Fact] + public async void TextOpContainsFilterWorks() + { + var TestName = "TextOpContainsFilterWorks"; + var WidgetRunNameStart = Util.Uniquify(TestName); + + List InclusiveWidgetIdList = new List(); + List ExclusiveWidgetIdList = new List(); + + //CREATE 4 TEST WIDGETS + //two inclusive and two not inclusive + + //first inclusive widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Fast Freddy Freak"; + w.roles = 0; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second inclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //first exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Phineas Freak"; + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetRunNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetRunNameStart; + dfilter.Add(DataFilterNameStart); + + //active bool test filter + dynamic DataFilterActive = new JObject(); + DataFilterActive.fld = "notes"; + DataFilterActive.op = Util.OpContains; + DataFilterActive.value = "red"; + dfilter.Add(DataFilterActive); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least two records + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(1); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (InclusiveWidgetIdList.Contains(o["id"].Value())) + InclusiveMatchCount++; + if (ExclusiveWidgetIdList.Contains(o["id"].Value())) + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().Be(InclusiveWidgetIdList.Count); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + foreach (long l in InclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + foreach (long l in ExclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + /// + /// + /// + [Fact] + public async void TextOpStartsWithFilterWorks() + { + var TestName = "TextOpStartsWithFilterWorks"; + var WidgetRunNameStart = Util.Uniquify(TestName); + + List InclusiveWidgetIdList = new List(); + List ExclusiveWidgetIdList = new List(); + + //CREATE 4 TEST WIDGETS + //two inclusive and two not inclusive + + //first inclusive widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Granular"; + w.roles = 0; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second inclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //first exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Gus Grifferson"; + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetRunNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetRunNameStart; + dfilter.Add(DataFilterNameStart); + + //active bool test filter + dynamic DataFilterActive = new JObject(); + DataFilterActive.fld = "notes"; + DataFilterActive.op = Util.OpStartsWith; + DataFilterActive.value = "Gra"; + dfilter.Add(DataFilterActive); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least two records + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(1); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (InclusiveWidgetIdList.Contains(o["id"].Value())) + InclusiveMatchCount++; + if (ExclusiveWidgetIdList.Contains(o["id"].Value())) + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().Be(InclusiveWidgetIdList.Count); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + foreach (long l in InclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + foreach (long l in ExclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + + /// + /// + /// + [Fact] + public async void TextOpEndsWithFilterWorks() + { + var TestName = "TextOpEndsWithFilterWorks"; + var WidgetRunNameStart = Util.Uniquify(TestName); + + List InclusiveWidgetIdList = new List(); + List ExclusiveWidgetIdList = new List(); + + //CREATE 4 TEST WIDGETS + //two inclusive and two not inclusive + + //first inclusive widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Bo Horvat"; + w.roles = 0; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second inclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //first exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.notes = "Bo Duke"; + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetRunNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetRunNameStart; + dfilter.Add(DataFilterNameStart); + + //active bool test filter + dynamic DataFilterActive = new JObject(); + DataFilterActive.fld = "notes"; + DataFilterActive.op = Util.OpEndsWith; + DataFilterActive.value = "vat"; + dfilter.Add(DataFilterActive); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least two records + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(1); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (InclusiveWidgetIdList.Contains(o["id"].Value())) + InclusiveMatchCount++; + if (ExclusiveWidgetIdList.Contains(o["id"].Value())) + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().Be(InclusiveWidgetIdList.Count); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + foreach (long l in InclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + foreach (long l in ExclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + + + + + + //====================== + + #endregion string filter tests + + /////////////////////////////////////////////////////////////////////////////// + //INT + // + #region INTEGER TESTS + + /// + /// + /// + [Fact] + public async void IntegerOpEqualityFilterWorks() + { + + var WidgetNameStart = "IntegerDataFilterTest"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.active = true; + w.roles = 0; + w.count = 5; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.count = 3; + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify("Test INT DataFilter"); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //inclusive test filter + + dynamic FilterItem = new JObject(); + FilterItem.fld = "count"; + FilterItem.op = Util.OpEquality; + FilterItem.value = 5; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + /// + /// + /// + [Fact] + public async void IntegerOpGreaterThanFilterWorks() + { + + var WidgetNameStart = "IntegerOpGreaterThanFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.active = true; + w.roles = 0; + w.count = 55; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.count = -55; + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify("Test INT DataFilter"); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //inclusive test filter + + dynamic FilterItem = new JObject(); + FilterItem.fld = "count"; + FilterItem.op = Util.OpGreaterThan; + FilterItem.value = 54; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + /// + /// + /// + [Fact] + public async void IntegerOpGreaterThanOrEqualToFilterWorks() + { + + var WidgetNameStart = "IntegerOpGreaterThanOrEqualToFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.active = true; + w.roles = 0; + w.count = 555; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.count = 554; + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify("IntegerOpGreaterThanOrEqualToFilterWorks"); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //inclusive test filter + + dynamic FilterItem = new JObject(); + FilterItem.fld = "count"; + FilterItem.op = Util.OpGreaterThanOrEqualTo; + FilterItem.value = 555; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + + + /// + /// + /// + [Fact] + public async void IntegerOpLessThanFilterWorks() + { + + var WidgetNameStart = "IntegerOpLessThanFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.active = true; + w.roles = 0; + w.count = -5555; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.count = 5555; + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify("IntegerOpLessThanFilterWorks"); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //inclusive test filter + + dynamic FilterItem = new JObject(); + FilterItem.fld = "count"; + FilterItem.op = Util.OpLessThan; + FilterItem.value = 5555; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + + /// + /// + /// + [Fact] + public async void IntegerOpLessThanOrEqualToFilterWorks() + { + + var WidgetNameStart = "IntegerOpLessThanOrEqualToFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.active = true; + w.roles = 0; + w.count = -444; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.count = -443; + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify("IntegerOpLessThanOrEqualToFilterWorks"); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //inclusive test filter + + dynamic FilterItem = new JObject(); + FilterItem.fld = "count"; + FilterItem.op = Util.OpLessThanOrEqualTo; + FilterItem.value = -444; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + + + /// + /// + /// + [Fact] + public async void IntegerNotEqualToFilterWorks() + { + + var WidgetNameStart = "IntegerNotEqualToFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.active = true; + w.roles = 0; + w.count = 222; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.count = 223; + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify("IntegerNotEqualToFilterWorks"); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //inclusive test filter + + dynamic FilterItem = new JObject(); + FilterItem.fld = "count"; + FilterItem.op = Util.OpNotEqual; + FilterItem.value = 223; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + } + #endregion integer tests + + + /////////////////////////////////////////////////////////////////////////////// + //BOOL + // + + #region BOOLEAN TESTS + + /// + /// + /// + [Fact] + public async void BoolOpEqualityFilterWorks() + { + + //OPS: equal to, not equal to + //values: true, false + + var WidgetNameStart = "BoolDataFilterTest"; + + List ActiveWidgetIdList = new List(); + List NotActiveWidgetIdList = new List(); + + //CREATE 4 TEST WIDGETS + //two active and two non active + + //first active widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.active = true; + w.roles = 0; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ActiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second active widget + w.name = Util.Uniquify(WidgetNameStart); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ActiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + + //first NON active widget + w.name = Util.Uniquify(WidgetNameStart); + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + NotActiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second NON active widget + w.name = Util.Uniquify(WidgetNameStart); + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + NotActiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify("Test BOOL DataFilter"); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //active bool test filter + dynamic DataFilterActive = new JObject(); + DataFilterActive.fld = "active"; + DataFilterActive.op = Util.OpEquality; + DataFilterActive.value = true; + dfilter.Add(DataFilterActive); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least two records + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(1); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int nActiveMatches = 0; + int nInactiveMatches = 0; + foreach (JObject o in v) + { + if (ActiveWidgetIdList.Contains(o["id"].Value())) + nActiveMatches++; + if (NotActiveWidgetIdList.Contains(o["id"].Value())) + nInactiveMatches++; + } + + nActiveMatches.Should().Be(ActiveWidgetIdList.Count); + nInactiveMatches.Should().Be(0); + + //DELETE WIDGETS + foreach (long l in ActiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + foreach (long l in NotActiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + + + /// + /// + /// + [Fact] + public async void BoolOpNotEqualFilterWorks() + { + + //OPS: equal to, not equal to + //values: true, false + + var WidgetNameStart = "BoolDataFilterTest"; + + List ActiveWidgetIdList = new List(); + List NotActiveWidgetIdList = new List(); + + //CREATE 4 TEST WIDGETS + //two active and two non active + + //first active widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.active = true; + w.roles = 0; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ActiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second active widget + w.name = Util.Uniquify(WidgetNameStart); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ActiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + + //first NON active widget + w.name = Util.Uniquify(WidgetNameStart); + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + NotActiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second NON active widget + w.name = Util.Uniquify(WidgetNameStart); + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + NotActiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify("Test BOOL DataFilter"); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //active bool test filter + dynamic DataFilterActive = new JObject(); + DataFilterActive.fld = "active"; + DataFilterActive.op = Util.OpNotEqual; + DataFilterActive.value = true; + dfilter.Add(DataFilterActive); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least two records + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(1); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int nActiveMatches = 0; + int nInactiveMatches = 0; + foreach (JObject o in v) + { + if (ActiveWidgetIdList.Contains(o["id"].Value())) + nActiveMatches++; + if (NotActiveWidgetIdList.Contains(o["id"].Value())) + nInactiveMatches++; + } + + nInactiveMatches.Should().Be(NotActiveWidgetIdList.Count); + nActiveMatches.Should().Be(0); + + //DELETE WIDGETS + foreach (long l in ActiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + foreach (long l in NotActiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + #endregion boolean tests + + + /////////////////////////////////////////////////////////////////////////////// + //DECIMAL + // + #region DECIMAL TESTS + + /// + /// + /// + [Fact] + public async void DecimalOpEqualityFilterWorks() + { + + var WidgetNameStart = "DecimalOpEqualityFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.active = true; + w.roles = 0; + w.dollarAmount = 5.55; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.dollarAmount = 3.33; + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify("DecimalOpEqualityFilterWorks"); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //inclusive test filter + + dynamic FilterItem = new JObject(); + FilterItem.fld = "dollaramount"; + FilterItem.op = Util.OpEquality; + FilterItem.value = 5.55; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + /// + /// + /// + [Fact] + public async void DecimalOpGreaterThanFilterWorks() + { + + var WidgetNameStart = "DecimalOpGreaterThanFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.active = true; + w.roles = 0; + w.dollarAmount = 55.55; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.dollarAmount = -55.55; + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify("DecimalOpGreaterThanFilterWorks"); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //inclusive test filter + + dynamic FilterItem = new JObject(); + FilterItem.fld = "dollaramount"; + FilterItem.op = Util.OpGreaterThan; + FilterItem.value = 54.44; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + /// + /// + /// + [Fact] + public async void DecimalOpGreaterThanOrEqualToFilterWorks() + { + + var WidgetNameStart = "DecimalOpGreaterThanOrEqualToFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.active = true; + w.roles = 0; + w.dollarAmount = 555.55; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.dollarAmount = 554.54; + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify("DecimalOpGreaterThanOrEqualToFilterWorks"); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //inclusive test filter + + dynamic FilterItem = new JObject(); + FilterItem.fld = "dollaramount"; + FilterItem.op = Util.OpGreaterThanOrEqualTo; + FilterItem.value = 555.55; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + + + /// + /// + /// + [Fact] + public async void DecimalOpLessThanFilterWorks() + { + + var WidgetNameStart = "DecimalOpLessThanFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.active = true; + w.roles = 0; + w.dollarAmount = -5555.55; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.dollarAmount = 5555.55; + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify("DecimalOpLessThanFilterWorks"); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //inclusive test filter + + dynamic FilterItem = new JObject(); + FilterItem.fld = "dollaramount"; + FilterItem.op = Util.OpLessThan; + FilterItem.value = 5555.55; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + + /// + /// + /// + [Fact] + public async void DecimalOpLessThanOrEqualToFilterWorks() + { + + var WidgetNameStart = "DecimalOpLessThanOrEqualToFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.active = true; + w.roles = 0; + w.dollarAmount = -444.44; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.dollarAmount = -443.43; + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify("DecimalOpLessThanOrEqualToFilterWorks"); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //inclusive test filter + + dynamic FilterItem = new JObject(); + FilterItem.fld = "dollaramount"; + FilterItem.op = Util.OpLessThanOrEqualTo; + FilterItem.value = -444.44; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + + + /// + /// + /// + [Fact] + public async void DecimalNotEqualToFilterWorks() + { + + var WidgetNameStart = "DecimalNotEqualToFilterWorks"; + + long IncludedWidgetId = 0; + long ExcludedWidgetId = 0; + + //CREATE TEST WIDGETS + + //included widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.active = true; + w.roles = 0; + w.dollarAmount = 222.22; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + IncludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + //Excluded widget + w.name = Util.Uniquify(WidgetNameStart); + w.dollarAmount = 223.23; + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExcludedWidgetId = a.ObjectResponse["data"]["id"].Value(); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify("DecimalNotEqualToFilterWorks"); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + + //inclusive test filter + + dynamic FilterItem = new JObject(); + FilterItem.fld = "dollaramount"; + FilterItem.op = Util.OpNotEqual; + FilterItem.value = 223.23; + dfilter.Add(FilterItem); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least this test record + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(0); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (IncludedWidgetId == o["id"].Value()) + InclusiveMatchCount++; + if (ExcludedWidgetId == o["id"].Value())//whups + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().BeGreaterOrEqualTo(1); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + a = await Util.DeleteAsync("Widget/" + IncludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ExcludedWidgetId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + } + #endregion decimal tests + + /////////////////////////////////////////////////////////////////////////////// + //TAGS + // + #region TAG TESTS + + /// + /// + /// + [Fact] + public async void TagFilterWorks() + { + + var TestName = "TagFilterWorks"; + var WidgetRunNameStart = Util.Uniquify(TestName); + + List InclusiveWidgetIdList = new List(); + List ExclusiveWidgetIdList = new List(); + + //CREATE 4 TEST WIDGETS + //two inclusive and two not inclusive + + //first inclusive widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetRunNameStart); + //Tags + dynamic InclusiveTagsArray = new JArray(); + InclusiveTagsArray.Add("red-tag-test"); + InclusiveTagsArray.Add("orange-tag-test"); + InclusiveTagsArray.Add("yellow-tag-test"); + InclusiveTagsArray.Add("green-tag-test"); + InclusiveTagsArray.Add("blue-tag-test"); + InclusiveTagsArray.Add("indigo-tag-test"); + InclusiveTagsArray.Add("violet-tag-test"); + w.tags = InclusiveTagsArray; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second inclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //first exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + //Tags + dynamic ExclusiveTagsArray = new JArray(); + ExclusiveTagsArray.Add("crimson-tag-test"); + ExclusiveTagsArray.Add("amber-tag-test"); + ExclusiveTagsArray.Add("saffron-tag-test"); + ExclusiveTagsArray.Add("emerald-tag-test"); + ExclusiveTagsArray.Add("azure-tag-test"); + ExclusiveTagsArray.Add("red-tag-test"); + ExclusiveTagsArray.Add("blue-tag-test"); + ExclusiveTagsArray.Add("cobalt-tag-test"); + ExclusiveTagsArray.Add("magenta-tag-test"); + w.tags = ExclusiveTagsArray;//Missing green + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetRunNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetRunNameStart; + dfilter.Add(DataFilterNameStart); + + //active test filter + dynamic DataFilterActive = new JObject(); + DataFilterActive.fld = "tags"; + DataFilterActive.op = Util.OpEquality; + dynamic FilterTagsArray = new JArray(); + FilterTagsArray.Add("red-tag-test"); + FilterTagsArray.Add("green-tag-test");//green is the only one missing from the exclusive widget + FilterTagsArray.Add("blue-tag-test"); + DataFilterActive.value = FilterTagsArray; + dfilter.Add(DataFilterActive); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least two records + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(1); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (InclusiveWidgetIdList.Contains(o["id"].Value())) + InclusiveMatchCount++; + if (ExclusiveWidgetIdList.Contains(o["id"].Value())) + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().Be(InclusiveWidgetIdList.Count); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + foreach (long l in InclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + foreach (long l in ExclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + /// + /// + /// + [Fact] + public async void UnicodeTagFilterWorks() + { + + var TestName = "UnicodeTagFilterWorks"; + var WidgetRunNameStart = Util.Uniquify(TestName); + + List InclusiveWidgetIdList = new List(); + List ExclusiveWidgetIdList = new List(); + + //CREATE 4 TEST WIDGETS + //two inclusive and two not inclusive + + //first inclusive widget + dynamic w = new JObject(); + w.name = Util.Uniquify(WidgetRunNameStart); + //Tags + dynamic InclusiveTagsArray = new JArray(); + InclusiveTagsArray.Add("red-tag-test"); + InclusiveTagsArray.Add("orange-tag-test"); + InclusiveTagsArray.Add("Ādam Iñtërnâtiônàližætiøn"); + w.tags = InclusiveTagsArray; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second inclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + InclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //first exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + //Tags + dynamic ExclusiveTagsArray = new JArray(); + ExclusiveTagsArray.Add("crimson-tag-test"); + ExclusiveTagsArray.Add("amber-tag-test"); + ExclusiveTagsArray.Add("saffron-tag-test"); + ExclusiveTagsArray.Add("emerald-tag-test"); + ExclusiveTagsArray.Add("azure-tag-test"); + ExclusiveTagsArray.Add("red-tag-test"); + ExclusiveTagsArray.Add("blue-tag-test"); + ExclusiveTagsArray.Add("cobalt-tag-test"); + ExclusiveTagsArray.Add("magenta-tag-test"); + w.tags = ExclusiveTagsArray;//Missing green + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + //second exclusive widget + w.name = Util.Uniquify(WidgetRunNameStart); + w.active = false; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ExclusiveWidgetIdList.Add(a.ObjectResponse["data"]["id"].Value()); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetRunNameStart); + // d.ownerId = 1L; + d["public"] = true; + d.listKey = "widget"; + + dynamic dfilter = new JArray(); + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetRunNameStart; + dfilter.Add(DataFilterNameStart); + + //active test filter + dynamic DataFilterActive = new JObject(); + DataFilterActive.fld = "tags"; + DataFilterActive.op = Util.OpEquality; + dynamic FilterTagsArray = new JArray(); + FilterTagsArray.Add("red-tag-test"); + FilterTagsArray.Add("Ādam Iñtërnâtiônàližætiøn"); + DataFilterActive.value = FilterTagsArray; + dfilter.Add(DataFilterActive); + + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains at least two records + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(1); + var v = ((JArray)a.ObjectResponse["data"]); + List IDInResultList = new List(); + int InclusiveMatchCount = 0; + int ExclusiveMatchCount = 0; + foreach (JObject o in v) + { + if (InclusiveWidgetIdList.Contains(o["id"].Value())) + InclusiveMatchCount++; + if (ExclusiveWidgetIdList.Contains(o["id"].Value())) + ExclusiveMatchCount++; + } + + InclusiveMatchCount.Should().Be(InclusiveWidgetIdList.Count); + ExclusiveMatchCount.Should().Be(0); + + //DELETE WIDGETS + foreach (long l in InclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + foreach (long l in ExclusiveWidgetIdList) + { + a = await Util.DeleteAsync("Widget/" + l.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + #endregion tag tests + //================================================== + + }//eoc +}//eons diff --git a/DataFilter/DataFilterOrderBy.cs b/DataFilter/DataFilterOrderBy.cs new file mode 100644 index 0000000..f1d7686 --- /dev/null +++ b/DataFilter/DataFilterOrderBy.cs @@ -0,0 +1,439 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; +using System.Collections.Generic; +using System.Collections.Concurrent; + +namespace raven_integration +{ + + + public class DataFilterOrderBy + { + + /// + /// + /// + [Fact] + public async void 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); + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ThirdInOrderWidgetId = a.ObjectResponse["data"]["id"].Value(); + + w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + SecondInOrderWidgetId = a.ObjectResponse["data"]["id"].Value(); + + w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + FirstInOrderWidgetId = a.ObjectResponse["data"]["id"].Value(); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + d["public"] = true; + d.listKey = "widget"; + dynamic dfilter = new JArray(); + + + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + d.filter = dfilter.ToString();//it expects it to be a json string, not actual json + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("manager", "l3tm3in"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + 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]["id"].Value().Should().Be(FirstInOrderWidgetId); + a.ObjectResponse["data"][1]["id"].Value().Should().Be(SecondInOrderWidgetId); + a.ObjectResponse["data"][2]["id"].Value().Should().Be(ThirdInOrderWidgetId); + + + a = await Util.DeleteAsync("Widget/" + FirstInOrderWidgetId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + SecondInOrderWidgetId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ThirdInOrderWidgetId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + + /// + /// + /// + [Fact] + public async void 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.startDate = DateTime.Now; + w.endDate = DateTime.Now.AddHours(1); + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + FirstInOrderWidgetId = a.ObjectResponse["data"]["id"].Value(); + + w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = DateTime.Now.AddHours(1); + w.endDate = DateTime.Now.AddHours(2); + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + SecondInOrderWidgetId = a.ObjectResponse["data"]["id"].Value(); + + w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.startDate = DateTime.Now.AddHours(2); + w.endDate = DateTime.Now.AddHours(3); + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ThirdInOrderWidgetId = a.ObjectResponse["data"]["id"].Value(); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + d["public"] = true; + d.listKey = "widget"; + + //FILTER IN BY NAME FOR TESTING THIS RUN ONLY + dynamic dfilter = new JArray(); + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + d.filter = dfilter.ToString(); + + //SORT ORDER ################### + dynamic dsortarray = new JArray(); + dynamic dsort = new JObject(); + dsort.fld = "startdate"; + dsort.dir = "+"; + dsortarray.Add(dsort); + d.sort = dsortarray.ToString(); + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("manager", "l3tm3in"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + 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]["id"].Value().Should().Be(FirstInOrderWidgetId); + a.ObjectResponse["data"][1]["id"].Value().Should().Be(SecondInOrderWidgetId); + a.ObjectResponse["data"][2]["id"].Value().Should().Be(ThirdInOrderWidgetId); + + + a = await Util.DeleteAsync("Widget/" + FirstInOrderWidgetId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + SecondInOrderWidgetId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ThirdInOrderWidgetId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + + + /// + /// + /// + [Fact] + public async void 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.count = 999; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + FirstInOrderWidgetId = a.ObjectResponse["data"]["id"].Value(); + + w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.count = 666; + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + SecondInOrderWidgetId = a.ObjectResponse["data"]["id"].Value(); + + w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.count = 333; + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ThirdInOrderWidgetId = a.ObjectResponse["data"]["id"].Value(); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + d["public"] = true; + d.listKey = "widget"; + + //FILTER IN BY NAME FOR TESTING THIS RUN ONLY + dynamic dfilter = new JArray(); + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + d.filter = dfilter.ToString(); + + //SORT ORDER ################### + dynamic dsortarray = new JArray(); + dynamic dsort = new JObject(); + dsort.fld = "count"; + dsort.dir = "-"; + dsortarray.Add(dsort); + d.sort = dsortarray.ToString(); + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("manager", "l3tm3in"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + 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]["id"].Value().Should().Be(FirstInOrderWidgetId); + a.ObjectResponse["data"][1]["id"].Value().Should().Be(SecondInOrderWidgetId); + a.ObjectResponse["data"][2]["id"].Value().Should().Be(ThirdInOrderWidgetId); + + + a = await Util.DeleteAsync("Widget/" + FirstInOrderWidgetId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + SecondInOrderWidgetId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ThirdInOrderWidgetId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + + + + + /// + /// + /// + [Fact] + public async void 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.dollaramount = 2.22; + w.count = 1; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + FourthInOrderWidgetId = a.ObjectResponse["data"]["id"].Value(); + + w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.dollaramount = 1.11; + w.count = 2; + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + FirstInOrderWidgetId = a.ObjectResponse["data"]["id"].Value(); + + w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.dollaramount = 1.11; + w.count = 1; + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + SecondInOrderWidgetId = a.ObjectResponse["data"]["id"].Value(); + + w = new JObject(); + w.name = Util.Uniquify(WidgetNameStart); + w.dollaramount = 2.22; + w.count = 2; + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w.ToString()); + Util.ValidateDataReturnResponseOk(a); + ThirdInOrderWidgetId = a.ObjectResponse["data"]["id"].Value(); + + + //CREATE FILTER + dynamic d = new JObject(); + d.name = Util.Uniquify(WidgetNameStart); + d["public"] = true; + d.listKey = "widget"; + + //FILTER IN BY NAME FOR TESTING THIS RUN ONLY + dynamic dfilter = new JArray(); + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = WidgetNameStart; + dfilter.Add(DataFilterNameStart); + d.filter = dfilter.ToString(); + + //SORT ORDER ################### + dynamic dsortarray = new JArray(); + + //First column + dynamic dsort1 = new JObject(); + dsort1.fld = "dollaramount"; + dsort1.dir = "+"; + dsortarray.Add(dsort1); + + //Second column + dynamic dsort2 = new JObject(); + dsort2.fld = "count"; + dsort2.dir = "-"; + dsortarray.Add(dsort2); + + + d.sort = dsortarray.ToString(); + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("manager", "l3tm3in"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"Widget/listwidgets?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + 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]["id"].Value().Should().Be(FirstInOrderWidgetId); + a.ObjectResponse["data"][1]["id"].Value().Should().Be(SecondInOrderWidgetId); + a.ObjectResponse["data"][2]["id"].Value().Should().Be(ThirdInOrderWidgetId); + a.ObjectResponse["data"][3]["id"].Value().Should().Be(FourthInOrderWidgetId); + + + a = await Util.DeleteAsync("Widget/" + FirstInOrderWidgetId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + SecondInOrderWidgetId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + ThirdInOrderWidgetId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("Widget/" + FourthInOrderWidgetId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + //======================================================================== + + }//eoc +}//eons diff --git a/Enum/EnumListOps.cs b/Enum/EnumListOps.cs new file mode 100644 index 0000000..bd8fba5 --- /dev/null +++ b/Enum/EnumListOps.cs @@ -0,0 +1,48 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; + +namespace raven_integration +{ + + public class EnumListOps + { + + + /// + /// + /// + [Fact] + public async void GetListOfEnumListsWorks() + { + ApiResponse a = await Util.GetAsync("AyaEnumPickList/listkeys", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(2); + a.ObjectResponse["data"][0]["key"].Value().Should().Be("usertypes"); + } + + /// + /// + /// + [Fact] + public async void GetSpecificEnumListWorks() + { + ApiResponse a = await Util.GetAsync("AyaEnumPickList/list/usertypes", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(5); + a.ObjectResponse["data"][3]["name"].Value().Should().Be("Client user"); + a.ObjectResponse["data"][3]["id"].Value().Should().Be(4); + } + + + + + //================================================== + + }//eoc +}//eons diff --git a/EventLog/EventLog.cs b/EventLog/EventLog.cs new file mode 100644 index 0000000..859197c --- /dev/null +++ b/EventLog/EventLog.cs @@ -0,0 +1,122 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; +using System.Collections.Generic; +using System.Collections.Concurrent; + +namespace raven_integration +{ + + public class EventLog + { + + + + /// + /// + /// + [Fact] + public async void ObjectLogWorks() + { + //CRUD a widget and confirm it logs properly + //http://localhost:7575/api/v8.0/EventLog/UserLog?AyType=3&AyId=1 + //http://localhost:7575/api/v8.0/EventLog/ObjectLog?AyType=2&AyId=242 + //http://localhost:7575/api/v8.0/EventLog/UserLog?AyType=3&AyId=1&StartDate=2018-08-23&EndDate=2018-08-24 + + dynamic w = new JObject(); + w.name = Util.Uniquify("EventLog Test WIDGET"); + w.created = DateTime.Now.ToString(); + w.dollarAmount = 2.22m; + w.active = true; + w.roles = 0; + + ApiResponse r2 = await Util.PostAsync("Widget", await Util.GetTokenAsync("InventoryFull"), w.ToString()); + Util.ValidateDataReturnResponseOk(r2); + long w2Id = r2.ObjectResponse["data"]["id"].Value(); + + ApiResponse EventLogResponse = await Util.GetAsync($"EventLog/ObjectLog?AyType=2&AyId={w2Id}", await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(EventLogResponse, 200); + + //NOTE:was failing here, for no reason that makes any sense it thinks there are two created events by two users for the same widget. + //might have been some temporary bad code in the seeder at one point when I was experimenting with async stuff + + + ((JArray)EventLogResponse.ObjectResponse["data"]).Count.Should().Be(1);//only one event so far + EventLogResponse.ObjectResponse["data"][0]["date"].Value().Should().BeLessThan(new TimeSpan(1, 0, 0)).Before(DateTime.UtcNow);//should be less than one hour before now + EventLogResponse.ObjectResponse["data"][0]["userId"].Should().NotBeNull(); + EventLogResponse.ObjectResponse["data"][0]["event"].Value().Should().Be(1);//AyEvent 1 = created + EventLogResponse.ObjectResponse["data"][0]["textra"].Should().BeNullOrEmpty(); + + //Get current user doing modifications ID + long CurrentUserId = EventLogResponse.ObjectResponse["data"][0]["userId"].Value(); + + //RETRIEVE + + //Get one + ApiResponse r3 = await Util.GetAsync("Widget/" + w2Id.ToString(), await Util.GetTokenAsync("InventoryFull")); + Util.ValidateDataReturnResponseOk(r3); + r3.ObjectResponse["data"]["name"].Value().Should().Be(w.name.ToString()); + + EventLogResponse = await Util.GetAsync($"EventLog/ObjectLog?AyType=2&AyId={w2Id}", await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(EventLogResponse, 200); + ((JArray)EventLogResponse.ObjectResponse["data"]).Count.Should().Be(2); + EventLogResponse.ObjectResponse["data"][1]["date"].Value().Should().BeLessThan(new TimeSpan(1, 0, 0)).Before(DateTime.UtcNow);//should be less than one hour before now + EventLogResponse.ObjectResponse["data"][1]["userId"].Should().NotBeNull(); + EventLogResponse.ObjectResponse["data"][1]["event"].Value().Should().Be(2);//AyEvent 2 = retrieved + EventLogResponse.ObjectResponse["data"][1]["textra"].Should().BeNullOrEmpty(); + + + + //UPDATE + //PUT + + //update w2id + w.name = Util.Uniquify("UPDATED VIA PUT EVENTLOG TEST WIDGET"); + w.OwnerId = 1; + w.concurrencyToken = r2.ObjectResponse["data"]["concurrencyToken"].Value(); + ApiResponse PUTTestResponse = await Util.PutAsync("Widget/" + w2Id.ToString(), await Util.GetTokenAsync("InventoryFull"), w.ToString()); + Util.ValidateHTTPStatusCode(PUTTestResponse, 200); + + //check PUT worked + ApiResponse checkPUTWorked = await Util.GetAsync("Widget/" + w2Id.ToString(), await Util.GetTokenAsync("InventoryFull")); + Util.ValidateNoErrorInResponse(checkPUTWorked); + checkPUTWorked.ObjectResponse["data"]["name"].Value().Should().Be(w.name.ToString()); + uint concurrencyToken = PUTTestResponse.ObjectResponse["data"]["concurrencyToken"].Value(); + + EventLogResponse = await Util.GetAsync($"EventLog/ObjectLog?AyType=2&AyId={w2Id}", await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(EventLogResponse, 200); + ((JArray)EventLogResponse.ObjectResponse["data"]).Count.Should().Be(4); + EventLogResponse.ObjectResponse["data"][2]["date"].Value().Should().BeLessThan(new TimeSpan(1, 0, 0)).Before(DateTime.UtcNow);//should be less than one hour before now + EventLogResponse.ObjectResponse["data"][2]["userId"].Should().NotBeNull(); + EventLogResponse.ObjectResponse["data"][2]["event"].Value().Should().Be(3);//AyEvent 3 = Modified + EventLogResponse.ObjectResponse["data"][2]["textra"].Should().BeNullOrEmpty(); + + + //Check user log for basic accessibility + EventLogResponse = await Util.GetAsync($"EventLog/UserLog?AyType=3&AyId={CurrentUserId}", await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(EventLogResponse, 200); + ((JArray)EventLogResponse.ObjectResponse["data"]).Count.Should().BeGreaterOrEqualTo(4);//just one run of the above will be 4 events plus any others from other tests + //Not sure of any easy way to assert the User log is correct other than the count as other tests running concurrently could easily skew this + + + //DELETE + ApiResponse DELETETestResponse = await Util.DeleteAsync("Widget/" + w2Id.ToString(), await Util.GetTokenAsync("InventoryFull")); + Util.ValidateHTTPStatusCode(DELETETestResponse, 204); + + //All events should be cleared up on deletion with the sole exception of the deleted event + EventLogResponse = await Util.GetAsync($"EventLog/ObjectLog?AyType=2&AyId={w2Id}", await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(EventLogResponse, 200); + ((JArray)EventLogResponse.ObjectResponse["data"]).Count.Should().Be(1); + EventLogResponse.ObjectResponse["data"][0]["date"].Value().Should().BeLessThan(new TimeSpan(1, 0, 0)).Before(DateTime.UtcNow);//should be less than one hour before now + EventLogResponse.ObjectResponse["data"][0]["userId"].Should().NotBeNull(); + EventLogResponse.ObjectResponse["data"][0]["event"].Value().Should().Be(0);//AyEvent 0 = deleted + EventLogResponse.ObjectResponse["data"][0]["textra"].Value().Should().Be(w.name.ToString()); + + + } + + //================================================== + + }//eoc +}//eons diff --git a/ImportV7/ImportV7.cs b/ImportV7/ImportV7.cs new file mode 100644 index 0000000..ee18674 --- /dev/null +++ b/ImportV7/ImportV7.cs @@ -0,0 +1,57 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Net.Http; +using System.Net.Http.Headers; +using System.IO; + + +namespace raven_integration +{ + + public class ImportV7 + { + //================================================== + /// + /// Test Importv7 stuff + /// + [Fact] + public async void ImportV7FileRoutesShouldWork() + { + + string UploadFileName = "ayanova.data.dump.xxx.zip"; + + ////////////////////////////////////////// + //// Upload the files + MultipartFormDataContent formDataContent = new MultipartFormDataContent(); + + StreamContent file1 = new StreamContent(File.OpenRead($"{Util.TEST_DATA_FOLDER}\\{UploadFileName}")); + file1.Headers.ContentType = new MediaTypeHeaderValue("application/zip"); + file1.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data"); + file1.Headers.ContentDisposition.FileName = UploadFileName; + formDataContent.Add(file1); + + ApiResponse a = await Util.PostFormDataAsync("ImportAyaNova7", formDataContent, await Util.GetTokenAsync("OpsAdminFull")); + Util.ValidateDataReturnResponseOk(a); + + string importFileName = a.ObjectResponse["data"][0].Value(); + importFileName.Should().Be(UploadFileName); + + + ////////////////////////////////////////// + //// DELETE: Delete the file + ApiResponse d = await Util.DeleteAsync($"ImportAyaNova7/{UploadFileName}", await Util.GetTokenAsync("OpsAdminFull")); + Util.ValidateHTTPStatusCode(d, 204); + + + + } + + + //================================================== + + }//eoc +}//eons diff --git a/JobOperations/JobOperations.cs b/JobOperations/JobOperations.cs new file mode 100644 index 0000000..42bda16 --- /dev/null +++ b/JobOperations/JobOperations.cs @@ -0,0 +1,59 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; +using System.Collections.Generic; +using System.Collections.Concurrent; + +namespace raven_integration +{ + + public class JobOperations + { + + /// + /// + /// + [Fact] + public async void TestJobShouldSubmit() + { + ApiResponse a = await Util.GetAsync("Widget/TestWidgetJob", await Util.GetTokenAsync("OpsAdminFull")); + //Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 202); + //should return something like this: + /* + { + "testJobId": 4 + } + */ + + String jobId = a.ObjectResponse["jobId"].Value(); + + //Get a list of operations + a = await Util.GetAsync("JobOperations", await Util.GetTokenAsync("OpsAdminFull")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + + + //there should be at least 1 + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterOrEqualTo(1); + + //See if our job is in there + bool bFound=false; + foreach(JToken t in a.ObjectResponse["data"]) + { + if(t["gId"].Value()==jobId) + bFound=true; + } + bFound.Should().BeTrue(); + + + + } + + + //================================================== + + }//eoc +}//eons diff --git a/Locale/Locale.cs b/Locale/Locale.cs new file mode 100644 index 0000000..b9f57fc --- /dev/null +++ b/Locale/Locale.cs @@ -0,0 +1,186 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Collections.Concurrent; +namespace raven_integration +{ + + public class Locale + { + + /* + + ImportLocale(ct, ResourceFolderPath, "en"); - id 1 + ImportLocale(ct, ResourceFolderPath, "es"); - id 2 + ImportLocale(ct, ResourceFolderPath, "fr"); - id 3 + ImportLocale(ct, ResourceFolderPath, "de"); - id 4 + */ + + [Fact] + public async void LocalePickListWorks() + { + //Get all + ApiResponse a = await Util.GetAsync("Locale/picklist", await Util.GetTokenAsync("ClientLimited"));//lowest level test user because there are no limits on this route except to be authenticated + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + //there should be at least 4 of them as there are 4 stock locales + ((JArray)a.ObjectResponse["data"]).Count.Should().BeGreaterThan(3); + } + + + [Fact] + public async void GetFullLocaleWorks() + { + //Get all + ApiResponse a = await Util.GetAsync("Locale/1", await Util.GetTokenAsync("ClientLimited"));//lowest level test user because there are no limits on this route except to be authenticated + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + //there should be dozens of keys but at times there might only be a few during development so at least verify there is more than one + ((JArray)a.ObjectResponse["data"]["localeItems"]).Count.Should().BeGreaterThan(0); + } + + + [Fact] + public async void GetSubsetWorks() + { + /* + { + "localeId": 0, + "keys": [ + "string" + ] + } + */ + + List keys = new List(); + keys.AddRange(new string[] { "AddressType", "ClientName", "RateName", "WorkorderService" }); + dynamic d = new JObject(); + //d.localeId = 1; + //d.keys = JToken.FromObject(keys); + d = JToken.FromObject(keys); + + ApiResponse a = await Util.PostAsync("Locale/subset", await Util.GetTokenAsync("ClientLimited"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + //there should be dozens of keys but at times there might only be a few during development so at least verify there is more than one + ((JArray)a.ObjectResponse["data"]).Count.Should().Be(4); + } + + + [Fact] + public async void DuplicateUpdateAndDeleteWorks() + { + /* + { + "id": 1, + "name": "CustomTest1" + } + */ + + //DUPLICATE + dynamic d = new JObject(); + d.id = 1; + d.name = Util.Uniquify("INTEGRATION-TEST-LOCALE"); + + ApiResponse a = await Util.PostAsync("Locale/Duplicate", await Util.GetTokenAsync("BizAdminFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 201); + //verify the object returned is as expected + a.ObjectResponse["data"]["name"].Value().Should().Be(d.name.ToString()); + a.ObjectResponse["data"]["stock"].Value().Should().Be(false); + a.ObjectResponse["data"]["id"].Value().Should().BeGreaterThan(4); + a.ObjectResponse["data"]["concurrencyToken"].Value().Should().BeGreaterThan(0); + ((JArray)a.ObjectResponse["data"]["localeItems"]).Count.Should().BeGreaterThan(0); + + long NewId = a.ObjectResponse["data"]["id"].Value(); + + //UPDATE + //Update locale name + + /* + + { + "id": 10, + "newText": "What the hell?", + "concurrencyToken": 25174 + } + + */ + dynamic d2 = new JObject(); + d2.id = NewId; + d2.newText = Util.Uniquify("INTEGRATION-TEST-LOCALE NAME UPDATE"); + d2.concurrencyToken = a.ObjectResponse["data"]["concurrencyToken"].Value(); + ApiResponse PUTTestResponse = await Util.PutAsync("Locale/UpdateLocaleName", await Util.GetTokenAsync("BizAdminFull"), d2.ToString()); + Util.ValidateHTTPStatusCode(PUTTestResponse, 200); + + + ApiResponse checkPUTWorked = await Util.GetAsync("Locale/" + NewId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateNoErrorInResponse(checkPUTWorked); + checkPUTWorked.ObjectResponse["data"]["name"].Value().Should().Be(d2.newText.ToString()); + //uint concurrencyToken = PUTTestResponse.ObjectResponse["data"]["concurrencyToken"].Value(); + + + //Update locale key + var FirstLocaleKey = ((JArray)a.ObjectResponse["data"]["localeItems"])[0]; + long UpdatedLocaleKeyId = FirstLocaleKey["id"].Value(); + d2.id = UpdatedLocaleKeyId; + d2.newText = Util.Uniquify("INTEGRATION-TEST-LOCALEITEM DISPLAY UPDATE"); + d2.concurrencyToken = FirstLocaleKey["concurrencyToken"].Value(); + + string UpdatedLocaleKey = FirstLocaleKey["key"].Value(); + + PUTTestResponse = await Util.PutAsync("Locale/UpdateLocaleItemDisplayText", await Util.GetTokenAsync("BizAdminFull"), d2.ToString()); + Util.ValidateHTTPStatusCode(PUTTestResponse, 200); + + //create user that is set to new locale so can use getSubset + var Login = Util.Uniquify("LOGIN"); + var Password = Util.Uniquify("PASSWORD"); + dynamic DUSER = new JObject(); + DUSER.name = Util.Uniquify("LocaleUpdateSubsetTestUser"); + DUSER.ownerId = 1L; + DUSER.active = true; + DUSER.login = Login; + DUSER.password = Password; + DUSER.roles = 0;//norole (any role can get a subset of locale keys) + DUSER.localeId = NewId;//random locale + DUSER.userType = 3;//non scheduleable + a = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), DUSER.ToString()); + Util.ValidateDataReturnResponseOk(a); + long DUSERID = a.ObjectResponse["data"]["id"].Value(); + + + + List keys = new List(); + keys.AddRange(new string[] { UpdatedLocaleKey }); + dynamic d3 = new JObject(); + //d3.localeId = NewId; + d3 = JToken.FromObject(keys); + + checkPUTWorked = await Util.PostAsync("Locale/subset", await Util.GetTokenAsync(Login, Password), d3.ToString()); + Util.ValidateDataReturnResponseOk(checkPUTWorked); + Util.ValidateHTTPStatusCode(checkPUTWorked, 200); + ((JArray)checkPUTWorked.ObjectResponse["data"]).Count.Should().Be(1); + var FirstLocaleKeyUpdated = ((JArray)checkPUTWorked.ObjectResponse["data"])[0]; + + FirstLocaleKeyUpdated["value"].Value().Should().Be(d2.newText.ToString()); + + //DELETE TEMPORARY USER SO CAN DELETE LOCALE + a = await Util.DeleteAsync("User/" + DUSERID.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + //DELETE TEMP LOCALE + a = await Util.DeleteAsync("Locale/" + NewId.ToString(), await Util.GetTokenAsync("BizAdminFull")); + Util.ValidateHTTPStatusCode(a, 204); + + + } + + + + //================================================== + + }//eoc +}//eons diff --git a/Locale/RequestedLocaleKeys.cs b/Locale/RequestedLocaleKeys.cs new file mode 100644 index 0000000..d068dfd --- /dev/null +++ b/Locale/RequestedLocaleKeys.cs @@ -0,0 +1,67 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Collections.Concurrent; +namespace raven_integration +{ + + public class RequestedLocaleKeys + { + + + + [Fact] + public async void RequestedLocaleKeysWorks() + { + //First determine if there is a requested key route because it's debug build dependent + //And doesn't exists if server was not debug built + ApiResponse a = await Util.GetAsync("BuildMode"); + Util.ValidateDataReturnResponseOk(a); + var BuildMode = a.ObjectResponse["data"]["buildMode"].Value(); + BuildMode.Should().BeOneOf((new string[] { "DEBUG", "RELEASE" })); + + if (BuildMode == "DEBUG") + { + + //Make a "list" of keys to fetch the values for + List keys = new List(); + keys.AddRange(new string[] { "HelpLicense", "ClientName" }); + dynamic d = new JObject(); + //d.localeId = 1; + d = JToken.FromObject(keys); + + //Fetch the values to force RAVEN to track at least these two + a = await Util.PostAsync("Locale/subset", await Util.GetTokenAsync("ClientLimited"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + //there should be dozens of keys but at times there might only be a few during development so at least verify there is more than one + ((JArray)a.ObjectResponse["data"]).Count.Should().Be(2); + + //Now ensure there are at least two keys in the fetched keys array + a = await Util.GetAsync("Locale/LocaleKeyCoverage", await Util.GetTokenAsync("ClientLimited")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + var RequestedKeyCount = a.ObjectResponse["data"]["requestedKeyCount"].Value(); + RequestedKeyCount.Should().BeGreaterOrEqualTo(2); + var NotRequestedKeyCount = a.ObjectResponse["data"]["notRequestedKeyCount"].Value(); + NotRequestedKeyCount.Should().BeGreaterThan(1);//For now at least, once we have this dialed in it will be zero ultimately + + //there should be dozens of keys but at times there might only be a few during development so at least verify there is more than one + ((JArray)a.ObjectResponse["data"]["requestedKeys"]).Count.Should().Be(RequestedKeyCount); + ((JArray)a.ObjectResponse["data"]["notRequestedKeys"]).Count.Should().Be(NotRequestedKeyCount); + + } + } + + + + + + //================================================== + + }//eoc +}//eons diff --git a/LogFiles/LogFiles.cs b/LogFiles/LogFiles.cs new file mode 100644 index 0000000..6e7f504 --- /dev/null +++ b/LogFiles/LogFiles.cs @@ -0,0 +1,32 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; +using System.Collections.Generic; +using System.Collections.Concurrent; + +namespace raven_integration +{ + + public class LogFiles + { + + + + /// + /// + /// + [Fact] + public async void MostRecentLogShouldFetch() + { + ApiTextResponse t = await Util.GetTextResultAsync("LogFiles/log-ayanova.txt", await Util.GetTokenAsync("OpsAdminFull")); + Util.ValidateHTTPStatusCode(t, 200); + string[] ExpectedLogItems = {"|INFO|","|ERROR|", "|WARN|"};//assumes any log will have at least one of these items in it + t.TextResponse.Should().ContainAny(ExpectedLogItems); + + } + + //================================================== + + }//eoc +}//eons diff --git a/Metrics/Metrics.cs b/Metrics/Metrics.cs new file mode 100644 index 0000000..712f2ba --- /dev/null +++ b/Metrics/Metrics.cs @@ -0,0 +1,51 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; +using System.Collections.Generic; +using System.Collections.Concurrent; + +namespace raven_integration +{ + + public class Metrics + { + + /// + /// + /// + [Fact] + public async void TextMetricsShouldFetch() + { + ApiTextResponse t = await Util.GetTextResultAsync("Metrics/TextSnapShot", await Util.GetTokenAsync("OpsAdminFull")); + + Util.ValidateHTTPStatusCode(t, 200); + + t.TextResponse.Should().StartWith("# TIMESTAMP:"); + + } + + + + /// + /// + /// + [Fact] + public async void JsonMetricsShouldFetch() + { + ApiResponse a = await Util.GetAsync("Metrics/JsonSnapShot", await Util.GetTokenAsync("OpsAdminFull")); + + Util.ValidateDataReturnResponseOk(a); + + a.ObjectResponse["data"]["timestamp"].Should().NotBeNull(); + ((JArray)a.ObjectResponse["data"]["contexts"]).Count.Should().BeGreaterThan(0); + + + + } + + + //================================================== + + }//eoc +}//eons diff --git a/Privacy/Privacy.cs b/Privacy/Privacy.cs new file mode 100644 index 0000000..5457b08 --- /dev/null +++ b/Privacy/Privacy.cs @@ -0,0 +1,28 @@ +using Xunit; +using FluentAssertions; + +namespace raven_integration +{ + + public class Privacy + { + + + + /// + /// + /// + [Fact] + public async void LogShouldNotContainPrivateData() + { + ApiResponse a = await Util.GetAsync("AyaType", await Util.GetTokenAsync("TEST_PRIVACY_USER_ACCOUNT")); + ApiTextResponse t = await Util.GetTextResultAsync("LogFiles/log-ayanova.txt", await Util.GetTokenAsync("TEST_PRIVACY_USER_ACCOUNT")); + Util.ValidateHTTPStatusCode(t, 200); + t.TextResponse.Should().NotContain("TEST_PRIVACY_USER_ACCOUNT"); + + } + + //================================================== + + }//eoc +}//eons diff --git a/Search/SearchOps.cs b/Search/SearchOps.cs new file mode 100644 index 0000000..aa11ef7 --- /dev/null +++ b/Search/SearchOps.cs @@ -0,0 +1,577 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using System.Linq; +using FluentAssertions; +using System.Collections.Generic; + +namespace raven_integration +{ + + public class SearchOps + { + + [Fact] + public async void PhraseOnlySearchShouldReturnCorrectResultsInOrder() + { + const string TEST_SEARCH_PHRASE = "simple dogs"; + + //CREATE A WIDGET + dynamic D = new JObject(); + D.name = Util.Uniquify("Search NOTES Test WIDGET"); + D.dollarAmount = 1.11m; + D.active = true; + D.roles = 0; + D.notes = "This record will match in notes: The quick brown and simple fox jumped over the six lazy dogs!"; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(a); + long MatchWidgetInNotesId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FIRST TEST USER WITH PHRASE IN NAME + D = new JObject(); + D.name = Util.Uniquify("Search NAME DOGS simple Test User"); + D.notes = "This user has the match in it's name"; + D.ownerId = 1L; + D.active = true; + D.login = Util.Uniquify("LOGIN"); + D.password = Util.Uniquify("PASSWORD"); + D.roles = 0;//norole + D.localeId = 1;//random locale + D.userType = 3;//non scheduleable + + a = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(a); + long MatchUserInNameId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE A SECOND TEST USER WITH PHRASE IN NOTES + D = new JObject(); + D.name = Util.Uniquify("Search NOTES Test User"); + D.notes = "This user has the match simple dogs in its notes"; + D.ownerId = 1L; + D.active = true; + D.login = Util.Uniquify("LOGIN"); + D.password = Util.Uniquify("PASSWORD"); + D.roles = 0;//norole + D.localeId = 1;//random locale + D.userType = 3;//non scheduleable + + a = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(a); + long MatchUserInNotesId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE A SECOND WIDGET + D = new JObject(); + D.name = Util.Uniquify("Search NAME simple as in dogs Test WIDGET"); + D.dollarAmount = 1.11m; + D.active = true; + D.roles = 0; + D.notes = "This Widget matches in name"; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(a); + long MatchWidgetInNameId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE A THIRD WIDGET + D = new JObject(); + D.name = Util.Uniquify("Search NO-MATCH THIRD Test WIDGET"); + D.dollarAmount = 1.11m; + D.active = true; + D.roles = 0; + D.notes = "This Widget should not be returned in the search as it only contains a single keyword in the name not both"; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(a); + long MatchNothingWidgetId = a.ObjectResponse["data"]["id"].Value(); + + + //Now see if can find those objects with a phrase search + dynamic SearchParameters = new JObject(); + + SearchParameters.phrase = TEST_SEARCH_PHRASE; + SearchParameters.nameOnly = false; + SearchParameters.typeOnly = 0;//no type + SearchParameters.maxResults = 0; + a = await Util.PostAsync("Search", await Util.GetTokenAsync("manager", "l3tm3in"), SearchParameters.ToString()); + Util.ValidateDataReturnResponseOk(a); + + //Now validate the return list + ((JArray)a.ObjectResponse["data"]["searchResults"]).Count.Should().BeGreaterOrEqualTo(3); + + //Turn the list into an array of id's + var v = ((JArray)a.ObjectResponse["data"]["searchResults"]); + List MatchingIdList = new List(); + foreach (JObject j in v) + { + MatchingIdList.Add(j["id"].Value()); + } + + //Ensure the expected items are returned + MatchingIdList.Should().Contain(MatchWidgetInNotesId, "ShouldContainMatchWidgetInNotesId"); + MatchingIdList.Should().Contain(MatchWidgetInNameId, "ShouldContainMatchWidgetInNameId"); + MatchingIdList.Should().Contain(MatchUserInNotesId, "ShouldContainMatchUserInNotesId"); + MatchingIdList.Should().Contain(MatchUserInNameId, "ShouldContainMatchUserInNameId"); + MatchingIdList.Should().NotContain(MatchNothingWidgetId, "ShouldNotContainMatchNothingWidgetId"); + + //Assert the order (roughly, this is kind of a waste of time, either the code is sorting or not, it's not going to change) + //first item must be a widget + a.ObjectResponse["data"]["searchResults"][0]["type"].Value().Should().Be(2); + + //final item must be a user + a.ObjectResponse["data"]["searchResults"][MatchingIdList.Count - 1]["type"].Value().Should().Be(3); + + + //FULL BODY SEARCH RIGHTS + //First up test a full record search returns no results due to insufficient rights + //even though the record exists + //Just re-run the above search exactly but with a no rights to full User or Widget role instead + + //Only BizAdmin* roles can read a full user record but anyone should be able to see names + //This search should return zero items + a = await Util.PostAsync("Search", await Util.GetTokenAsync("SubContractorLimited"), SearchParameters.ToString()); + Util.ValidateDataReturnResponseOk(a); + ((JArray)a.ObjectResponse["data"]["searchResults"]).Count.Should().Be(0, "User with no rights should not see any results in body search"); + + + //NAME ONLY SEARCH SHOULD WORK WITH NO RIGHTS TO READ FULL RECORD + //repeat same search but with nameOnly = true and should return at two records at least but not any of the body ones + SearchParameters = new JObject(); + SearchParameters.phrase = TEST_SEARCH_PHRASE; + SearchParameters.nameOnly = true; + SearchParameters.typeOnly = 0;//no type + SearchParameters.maxResults = 0; + a = await Util.PostAsync("Search", await Util.GetTokenAsync("SubContractorLimited"), SearchParameters.ToString()); + Util.ValidateDataReturnResponseOk(a); + ((JArray)a.ObjectResponse["data"]["searchResults"]).Count.Should().BeGreaterOrEqualTo(2); + //Check that list does *not* include the notes only records + MatchingIdList = new List(); + v = ((JArray)a.ObjectResponse["data"]["searchResults"]); + foreach (JObject j in v) + { + MatchingIdList.Add(j["id"].Value()); + } + MatchingIdList.Should().NotContain(MatchWidgetInNotesId, "ShouldNotContainMatchWidgetInNotesId"); + MatchingIdList.Should().Contain(MatchWidgetInNameId, "ShouldContainMatchWidgetInNameId"); + MatchingIdList.Should().NotContain(MatchUserInNotesId, "ShouldContainMatchUserInNotesId"); + MatchingIdList.Should().Contain(MatchUserInNameId, "ShouldContainMatchUserInNameId"); + MatchingIdList.Should().NotContain(MatchNothingWidgetId, "ShouldNotContainThirdWidget"); + + }//eot + + + + [Fact] + public async void WildCardStartsWithSearchShouldWork() + { + const string TEST_SEARCH_PHRASE = "hap* goose"; + + //CREATE A WIDGET + dynamic D = new JObject(); + D.name = Util.Uniquify("Wildcard startswith search test WIDGET"); + D.dollarAmount = 1.11m; + D.active = true; + D.roles = 0; + D.notes = "This record will match in notes: The quick brown and hapless goose"; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(a); + long MatchWidgetInNotesId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FIRST TEST USER WITH PHRASE IN NAME + D = new JObject(); + D.name = Util.Uniquify("Wildcard startswith search NAME happy goose Test User"); + D.notes = "This user has the match in it's name"; + D.ownerId = 1L; + D.active = true; + D.login = Util.Uniquify("LOGIN"); + D.password = Util.Uniquify("PASSWORD"); + D.roles = 0;//norole + D.localeId = 1;//random locale + D.userType = 3;//non scheduleable + + a = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(a); + long MatchUserInNameId = a.ObjectResponse["data"]["id"].Value(); + + + //Now see if can find those objects with a phrase search + dynamic SearchParameters = new JObject(); + + SearchParameters.phrase = TEST_SEARCH_PHRASE; + SearchParameters.nameOnly = false; + SearchParameters.typeOnly = 0;//no type + SearchParameters.maxResults = 0; + a = await Util.PostAsync("Search", await Util.GetTokenAsync("manager", "l3tm3in"), SearchParameters.ToString()); + Util.ValidateDataReturnResponseOk(a); + + //Now validate the return list + ((JArray)a.ObjectResponse["data"]["searchResults"]).Count.Should().BeGreaterOrEqualTo(2); + + //Turn the list into an array of id's + var v = ((JArray)a.ObjectResponse["data"]["searchResults"]); + List MatchingIdList = new List(); + foreach (JObject j in v) + { + MatchingIdList.Add(j["id"].Value()); + } + + //Ensure the expected items are returned + MatchingIdList.Should().Contain(MatchWidgetInNotesId, "ShouldContainMatchWidgetInNotesId"); + MatchingIdList.Should().Contain(MatchUserInNameId, "ShouldContainMatchUserInNameId"); + + }//eot + + + [Fact] + public async void WildCardEndsWithSearchShouldWork() + { + const string TEST_SEARCH_PHRASE = "goose *act"; + + //CREATE A WIDGET + dynamic D = new JObject(); + D.name = Util.Uniquify("Wildcard endswith search test WIDGET"); + D.dollarAmount = 1.11m; + D.active = true; + D.roles = 0; + D.notes = "This record will match in notes: react to the goose"; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(a); + long MatchWidgetInNotesId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FIRST TEST USER WITH PHRASE IN NAME + D = new JObject(); + D.name = Util.Uniquify("Wildcard endswith search NAME goose exact Test User"); + D.notes = "This user has the match in it's name"; + D.ownerId = 1L; + D.active = true; + D.login = Util.Uniquify("LOGIN"); + D.password = Util.Uniquify("PASSWORD"); + D.roles = 0;//norole + D.localeId = 1;//random locale + D.userType = 3;//non scheduleable + + a = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(a); + long MatchUserInNameId = a.ObjectResponse["data"]["id"].Value(); + + + //Now see if can find those objects with a phrase search + dynamic SearchParameters = new JObject(); + + SearchParameters.phrase = TEST_SEARCH_PHRASE; + SearchParameters.nameOnly = false; + SearchParameters.typeOnly = 0;//no type + SearchParameters.maxResults = 0; + a = await Util.PostAsync("Search", await Util.GetTokenAsync("manager", "l3tm3in"), SearchParameters.ToString()); + Util.ValidateDataReturnResponseOk(a); + + //Now validate the return list + ((JArray)a.ObjectResponse["data"]["searchResults"]).Count.Should().BeGreaterOrEqualTo(2); + + //Turn the list into an array of id's + var v = ((JArray)a.ObjectResponse["data"]["searchResults"]); + List MatchingIdList = new List(); + foreach (JObject j in v) + { + MatchingIdList.Add(j["id"].Value()); + } + + //Ensure the expected items are returned + MatchingIdList.Should().Contain(MatchWidgetInNotesId, "ShouldContainMatchWidgetInNotesId"); + MatchingIdList.Should().Contain(MatchUserInNameId, "ShouldContainMatchUserInNameId"); + + }//eot + + + + + [Fact] + public async void WildCardContainsSearchShouldWork() + { + const string TEST_SEARCH_PHRASE = "*cast* goose"; + + //CREATE A WIDGET + dynamic D = new JObject(); + D.name = Util.Uniquify("Wildcard contains search test WIDGET"); + D.dollarAmount = 1.11m; + D.active = true; + D.roles = 0; + D.notes = "This record will match in notes: broadcasting to the goose"; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(a); + long MatchWidgetInNotesId = a.ObjectResponse["data"]["id"].Value(); + + //CREATE FIRST TEST USER WITH PHRASE IN NAME + D = new JObject(); + D.name = Util.Uniquify("Wildcard contains search NAME goose elcastro Test User"); + D.notes = "This user has the match in it's name"; + D.ownerId = 1L; + D.active = true; + D.login = Util.Uniquify("LOGIN"); + D.password = Util.Uniquify("PASSWORD"); + D.roles = 0;//norole + D.localeId = 1;//random locale + D.userType = 3;//non scheduleable + + a = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(a); + long MatchUserInNameId = a.ObjectResponse["data"]["id"].Value(); + + + //Now see if can find those objects with a phrase search + dynamic SearchParameters = new JObject(); + + SearchParameters.phrase = TEST_SEARCH_PHRASE; + SearchParameters.nameOnly = false; + SearchParameters.typeOnly = 0;//no type + SearchParameters.maxResults = 0; + a = await Util.PostAsync("Search", await Util.GetTokenAsync("manager", "l3tm3in"), SearchParameters.ToString()); + Util.ValidateDataReturnResponseOk(a); + + //Now validate the return list + ((JArray)a.ObjectResponse["data"]["searchResults"]).Count.Should().BeGreaterOrEqualTo(2); + + //Turn the list into an array of id's + var v = ((JArray)a.ObjectResponse["data"]["searchResults"]); + List MatchingIdList = new List(); + foreach (JObject j in v) + { + MatchingIdList.Add(j["id"].Value()); + } + + //Ensure the expected items are returned + MatchingIdList.Should().Contain(MatchWidgetInNotesId, "ShouldContainMatchWidgetInNotesId"); + MatchingIdList.Should().Contain(MatchUserInNameId, "ShouldContainMatchUserInNameId"); + + }//eot + + + [Fact] + public async void TagSearchShouldWork() + { + const string TEST_SEARCH_PHRASE = "element* aardvark"; + + // //CREATE A TAG + // dynamic D = new JObject(); + // D.name = Util.Uniquify("TAGSEARCH"); + // ApiResponse a = await Util.PostAsync("Tag", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + // Util.ValidateDataReturnResponseOk(a); + // long TagId = a.ObjectResponse["data"]["id"].Value(); + + + var SearchTagPhrase = Util.Uniquify("TAGSEARCH"); + //Tags + dynamic TagsArray = new JArray(); + TagsArray.Add(SearchTagPhrase); + + //w.tags = InclusiveTagsArray; + + //CREATE A WIDGET + dynamic D = new JObject(); + D.name = Util.Uniquify("TAG search test WIDGET TAG AND PHRASE"); + D.dollarAmount = 1.11m; + D.active = true; + D.roles = 0; + D.notes = "This record will match in notes and tag: elementary my dear aardvark"; + D.tags = TagsArray; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(a); + long MatchWidgetInNotesId = a.ObjectResponse["data"]["id"].Value(); + + + + + //CREATE A WIDGET WITH TAG BUT NOT SEARCH PHRASE + D = new JObject(); + D.name = Util.Uniquify("TAG search test WIDGET TAG ONLY"); + D.dollarAmount = 1.11m; + D.active = true; + D.roles = 0; + D.notes = "This record will match in tag but no search phrase"; + D.tags = TagsArray; + + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(a); + long NoPhraseMatchWidgetInTagId = a.ObjectResponse["data"]["id"].Value(); + + + + //CREATE FIRST TEST USER WITH PHRASE IN NAME BUT NO TAG + D = new JObject(); + D.name = Util.Uniquify("Wildcard contains search NAME elementary aardvark Test User"); + D.notes = "This user has the match in it's name but no tag match"; + D.ownerId = 1L; + D.active = true; + D.login = Util.Uniquify("LOGIN"); + D.password = Util.Uniquify("PASSWORD"); + D.roles = 0;//norole + D.localeId = 1;//random locale + D.userType = 3;//non scheduleable + + a = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(a); + long MatchUserInNameId = a.ObjectResponse["data"]["id"].Value(); + + + //Now see if can find those objects with a phrase search + dynamic SearchParameters = new JObject(); + + SearchParameters.phrase = TEST_SEARCH_PHRASE + " " + SearchTagPhrase; + SearchParameters.nameOnly = false; + SearchParameters.typeOnly = 0;//no type + + a = await Util.PostAsync("Search", await Util.GetTokenAsync("manager", "l3tm3in"), SearchParameters.ToString()); + Util.ValidateDataReturnResponseOk(a); + + //Now validate the return list + ((JArray)a.ObjectResponse["data"]["searchResults"]).Count.Should().BeGreaterOrEqualTo(1); + + //Turn the list into an array of id's + var v = ((JArray)a.ObjectResponse["data"]["searchResults"]); + List MatchingIdList = new List(); + foreach (JObject j in v) + { + MatchingIdList.Add(j["id"].Value()); + } + + //Ensure the expected items are returned + MatchingIdList.Should().Contain(MatchWidgetInNotesId, "ShouldContainMatchWidgetInNotesId"); + MatchingIdList.Should().NotContain(MatchUserInNameId, "ShouldNotContainMatchUserInNameId"); + MatchingIdList.Should().NotContain(NoPhraseMatchWidgetInTagId, "ShouldNotContainNoPhraseMatchWidgetInTagId"); + + }//eot + + + + [Fact] + public async void ConstrainedBigDataSearchShouldHonourMaxResultsAndBeRelativelyFast() + { + //THIS test is a bit different in that it relies partly on the big dataset for testing + //so it has different paths depending upon if it's testing against the big data or not + const string TEST_SEARCH_PHRASE = "et*"; + + //Now see if can find those objects with a phrase search + dynamic SearchParameters = new JObject(); + + SearchParameters.phrase = TEST_SEARCH_PHRASE; + SearchParameters.nameOnly = false; + SearchParameters.typeOnly = 0;//no type + SearchParameters.maxResults = 1000;//default is 500 + + var watch = new System.Diagnostics.Stopwatch(); + watch.Start(); + ApiResponse a = await Util.PostAsync("Search", await Util.GetTokenAsync("manager", "l3tm3in"), SearchParameters.ToString()); + watch.Stop(); + + var TimeToSearch = watch.ElapsedMilliseconds; + + Util.ValidateDataReturnResponseOk(a); + + //Now validate the return list + var ResultCount = ((JArray)a.ObjectResponse["data"]["searchResults"]).Count; + //assert it's not unbounded + ResultCount.Should().BeLessOrEqualTo(1000); + + if (ResultCount > 999) + { + //assert the TotalResultsFound is greater than the results returned + var TotalResultsFound = a.ObjectResponse["data"]["totalResultsFound"].Value(); + //assert it's not unbounded + TotalResultsFound.Should().BeGreaterThan(ResultCount); + } + //5456 ms is the longest I've seen in initial testing with all 1000 results so setting 10% above + TimeToSearch.Should().BeLessThan(6000, "Constrained big data search should not be too slow"); + + }//eot + + + [Fact] + public async void UnboundBigDataSearchShouldBeRelativelyFast() + { + //THIS test is a bit different in that it relies partly on the big dataset for testing + //so it has different paths depending upon if it's testing against the big data or not + const string TEST_SEARCH_PHRASE = "et*"; + + //Now see if can find those objects with a phrase search + dynamic SearchParameters = new JObject(); + + SearchParameters.phrase = TEST_SEARCH_PHRASE; + SearchParameters.nameOnly = false; + SearchParameters.typeOnly = 0;//no type + SearchParameters.maxResults = 0;//0=return all results + + var watch = new System.Diagnostics.Stopwatch(); + watch.Start(); + ApiResponse a = await Util.PostAsync("Search", await Util.GetTokenAsync("manager", "l3tm3in"), SearchParameters.ToString()); + watch.Stop(); + + var TimeToSearch = watch.ElapsedMilliseconds; + + Util.ValidateDataReturnResponseOk(a); + + //Now validate the return list + var ResultCount = ((JArray)a.ObjectResponse["data"]["searchResults"]).Count; + + + //Set this time based on testing. 52385 Slowest run plus 10% + TimeToSearch.Should().BeLessThan(57623, "Unconstrained big data search should not be too slow"); + + + }//eot + + + + [Fact] + public async void SearchForSerialFieldShouldWork() + { + //CREATE A WIDGET + dynamic D = new JObject(); + D.name = Util.Uniquify("Serial search test WIDGET"); + D.dollarAmount = 1.11m; + D.active = true; + D.roles = 0; + D.notes = "Test for serial number search"; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(a); + long MatchWidgetInSerialId = a.ObjectResponse["data"]["id"].Value(); + var MatchWidgetSerial = a.ObjectResponse["data"]["serial"].Value(); + + //TODO: get this from the return object + string SerialSearch = MatchWidgetSerial.ToString(); ; + + + //Now see if can find those objects with a phrase search + dynamic SearchParameters = new JObject(); + SearchParameters.phrase = SerialSearch; + SearchParameters.nameOnly = false; + SearchParameters.typeOnly = 0;//no type + SearchParameters.maxResults = 0; + a = await Util.PostAsync("Search", await Util.GetTokenAsync("manager", "l3tm3in"), SearchParameters.ToString()); + Util.ValidateDataReturnResponseOk(a); + + //Now validate the return list + ((JArray)a.ObjectResponse["data"]["searchResults"]).Count.Should().BeGreaterOrEqualTo(1); + + //Turn the list into an array of id's + var v = ((JArray)a.ObjectResponse["data"]["searchResults"]); + List MatchingIdList = new List(); + foreach (JObject j in v) + { + MatchingIdList.Add(j["id"].Value()); + } + + //Ensure the expected items are returned + MatchingIdList.Should().Contain(MatchWidgetInSerialId, "ShouldContainMatchWidgetInSerialId"); + + + }//eot + + + //================================================== + + }//eoc +}//eons diff --git a/ServerState/ServerStateTest.cs b/ServerState/ServerStateTest.cs new file mode 100644 index 0000000..6344d6c --- /dev/null +++ b/ServerState/ServerStateTest.cs @@ -0,0 +1,79 @@ +using Xunit; + +namespace raven_integration +{ + + public class ServerStateTest + { + + + // ApiFixture fixture; + + // public ServerStateTest(ApiFixture _fixture) + // { + // this.fixture = _fixture; + // } + + + + /// + /// Test get state + /// + [Fact] + public async void ServerStateShouldReturnOk() + { + ApiResponse a = await Util.GetAsync("ServerState"); + Util.ValidateDataReturnResponseOk(a); + } + + + + + //can't test this because it fucks up other tests and the fixture + + // /// + // /// Test set state + // /// + // [Fact] + // public async void ServerStateShouldSet() + // { + + // /*{ + // "serverState": "open" + // } */ + + + // //open + // dynamic dd = new JObject(); + // dd.serverState = "open"; + + // ApiResponse aa = await Util.PostAsync("ServerState", fixture.ManagerAuthToken, dd.ToString()); + // Util.ValidateHTTPStatusCode(aa, 204); + + // //close + // dynamic d = new JObject(); + // d.serverState = "closed"; + + // ApiResponse a = await Util.PostAsync("ServerState", fixture.ManagerAuthToken, d.ToString()); + // Util.ValidateHTTPStatusCode(a, 204); + + // //open + // dynamic ddd = new JObject(); + // dd.serverState = "open"; + + // ApiResponse aaa = await Util.PostAsync("ServerState", fixture.ManagerAuthToken, ddd.ToString()); + // Util.ValidateHTTPStatusCode(aaa, 204); + + + + // } + + + + + + + //================================================== + + }//eoc +}//eons diff --git a/User/UserCrud.cs b/User/UserCrud.cs new file mode 100644 index 0000000..4a30cde --- /dev/null +++ b/User/UserCrud.cs @@ -0,0 +1,592 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; + +namespace raven_integration +{ + + public class UserCrud + { + + /// + /// Test all CRUD routes for a User + /// + [Fact] + public async void CRUD() + { + + //CREATE + dynamic D1 = new JObject(); + D1.name = Util.Uniquify("First Test User"); + D1.ownerId = 1L; + D1.active = true; + D1.login = Util.Uniquify("LOGIN"); + D1.password = Util.Uniquify("PASSWORD"); + D1.roles = 0;//norole + D1.localeId = 1;//random locale + D1.userType = 3;//non scheduleable + + ApiResponse R1 = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), D1.ToString()); + Util.ValidateDataReturnResponseOk(R1); + long d1Id = R1.ObjectResponse["data"]["id"].Value(); + + + dynamic D2 = new JObject(); + D2.name = Util.Uniquify("Second Test User"); + D2.ownerId = 1L; + D2.active = true; + D2.login = Util.Uniquify("LOGIN"); + D2.password = Util.Uniquify("PASSWORD"); + D2.roles = 0;//norole + D2.localeId = 1;//random locale + D2.userType = 3;//non scheduleable + + ApiResponse R2 = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), D2.ToString()); + Util.ValidateDataReturnResponseOk(R2); + long d2Id = R2.ObjectResponse["data"]["id"].Value(); + + + //RETRIEVE + + //Get one + ApiResponse R3 = await Util.GetAsync("User/" + d2Id.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(R3); + R3.ObjectResponse["data"]["name"].Value().Should().Be(D2.name.ToString()); + + + + //UPDATE + //PUT + + //update w2id + D2.name = Util.Uniquify("UPDATED VIA PUT SECOND TEST User"); + D2.OwnerId = 1; + D2.concurrencyToken = R2.ObjectResponse["data"]["concurrencyToken"].Value(); + ApiResponse PUTTestResponse = await Util.PutAsync("User/" + d2Id.ToString(), await Util.GetTokenAsync("manager", "l3tm3in"), D2.ToString()); + Util.ValidateHTTPStatusCode(PUTTestResponse, 200); + + //check PUT worked + ApiResponse checkPUTWorked = await Util.GetAsync("User/" + d2Id.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateNoErrorInResponse(checkPUTWorked); + checkPUTWorked.ObjectResponse["data"]["name"].Value().Should().Be(D2.name.ToString()); + uint concurrencyToken = PUTTestResponse.ObjectResponse["data"]["concurrencyToken"].Value(); + + //PATCH + var newName = Util.Uniquify("UPDATED VIA PATCH SECOND TEST User"); + string patchJson = "[{\"value\": \"" + newName + "\",\"path\": \"/name\",\"op\": \"replace\"}]"; + ApiResponse PATCHTestResponse = await Util.PatchAsync("User/" + d2Id.ToString() + "/" + concurrencyToken.ToString(), await Util.GetTokenAsync("manager", "l3tm3in"), patchJson); + Util.ValidateHTTPStatusCode(PATCHTestResponse, 200); + + //check PATCH worked + ApiResponse checkPATCHWorked = await Util.GetAsync("User/" + d2Id.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateNoErrorInResponse(checkPATCHWorked); + checkPATCHWorked.ObjectResponse["data"]["name"].Value().Should().Be(newName); + + //DELETE + ApiResponse DELETETestResponse = await Util.DeleteAsync("User/" + d2Id.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(DELETETestResponse, 204); + } + + + /// + /// + /// + [Fact] + public async void UserWithActivityShouldNotBeDeleteable() + { + ApiResponse a = await Util.DeleteAsync("User/1", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateErrorCodeResponse(a, 2200, 400); + a.ObjectResponse["error"]["details"][0]["message"].Value().Should().Contain("[E_ACTIVE_NOT_DELETABLE]"); + } + + + /// + /// Test not found + /// + [Fact] + public async void GetNonExistentItemShouldError() + { + //Get non existant + //Should return status code 404, api error code 2010 + ApiResponse R = await Util.GetAsync("User/999999", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateResponseNotFound(R); + } + + /// + /// Test bad modelstate + /// + [Fact] + public async void GetBadModelStateShouldError() + { + //Get non existant + //Should return status code 400, api error code 2200 and a first target in details of "id" + ApiResponse R = await Util.GetAsync("User/2q2", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateBadModelStateResponse(R, "id"); + } + + + /// + /// + /// + [Fact] + public async void PutConcurrencyViolationShouldFail() + { + //CREATE + dynamic D = new JObject(); + D.name = Util.Uniquify("PutConcurrencyViolationShouldFail"); + D.ownerId = 1L; + D.active = true; + D.login = Util.Uniquify("LOGIN"); + D.password = Util.Uniquify("PASSWORD"); + D.roles = 0;//norole + D.localeId = 1;//random locale + D.userType = 3;//non scheduleable + + ApiResponse R = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(R); + long D1Id = R.ObjectResponse["data"]["id"].Value(); + uint OriginalConcurrencyToken = R.ObjectResponse["data"]["concurrencyToken"].Value(); + + + //UPDATE + //PUT + + D.name = Util.Uniquify("PutConcurrencyViolationShouldFail UPDATE VIA PUT "); + D.concurrencyToken = OriginalConcurrencyToken - 1;//bad token + ApiResponse PUTTestResponse = await Util.PutAsync("User/" + D1Id.ToString(), await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateConcurrencyError(PUTTestResponse); + } + + + /// + /// + /// + [Fact] + public async void PatchConcurrencyViolationShouldFail() + { + //CREATE + dynamic D = new JObject(); + D.name = Util.Uniquify("PatchConcurrencyViolationShouldFail"); + D.ownerId = 1L; + D.active = true; + D.login = Util.Uniquify("LOGIN"); + D.password = Util.Uniquify("PASSWORD"); + D.roles = 0;//norole + D.localeId = 1;//random locale + D.userType = 3;//non scheduleable + + ApiResponse R = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(R); + long w2Id = R.ObjectResponse["data"]["id"].Value(); + uint OriginalConcurrencyToken = R.ObjectResponse["data"]["concurrencyToken"].Value(); + + + //PATCH + var newName = Util.Uniquify("PutConcurrencyViolationShouldFail UPDATED VIA PATCH"); + string patchJson = "[{\"value\": \"" + newName + "\",\"path\": \"/name\",\"op\": \"replace\"}]"; + ApiResponse PATCHTestResponse = await Util.PatchAsync("User/" + w2Id.ToString() + "/" + (OriginalConcurrencyToken - 1).ToString(), await Util.GetTokenAsync("manager", "l3tm3in"), patchJson); + Util.ValidateConcurrencyError(PATCHTestResponse); + } + + + + /// + /// + /// + [Fact] + public async void DisallowedPatchAttemptsShouldFail() + { + //CREATE + dynamic D = new JObject(); + D.name = Util.Uniquify("DisallowedPatchAttemptsShouldFail"); + D.ownerId = 1L; + D.active = true; + D.login = Util.Uniquify("LOGIN"); + D.password = Util.Uniquify("PASSWORD"); + D.roles = 0;//norole + D.localeId = 1;//random locale + D.userType = 3;//non scheduleable + + ApiResponse R = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(R); + long w2Id = R.ObjectResponse["data"]["id"].Value(); + uint OriginalConcurrencyToken = R.ObjectResponse["data"]["concurrencyToken"].Value(); + + + //PATCH attempt on Id + string patchJson = "[{\"value\": \"0\",\"path\": \"/id\",\"op\": \"replace\"}]"; + ApiResponse PATCHTestResponse = await Util.PatchAsync("User/" + w2Id.ToString() + "/" + (OriginalConcurrencyToken - 1).ToString(), await Util.GetTokenAsync("manager", "l3tm3in"), patchJson); + Util.ValidateErrorCodeResponse(PATCHTestResponse, 2200, 400); + + //PATCH attempt on OwnerId + patchJson = "[{\"value\": \"0\",\"path\": \"/ownerid\",\"op\": \"replace\"}]"; + PATCHTestResponse = await Util.PatchAsync("User/" + w2Id.ToString() + "/" + (OriginalConcurrencyToken - 1).ToString(), await Util.GetTokenAsync("manager", "l3tm3in"), patchJson); + Util.ValidateErrorCodeResponse(PATCHTestResponse, 2200, 400); + + //PATCH attempt add field + patchJson = "[{\"value\": \"0\",\"path\": \"/bogus\",\"op\": \"add\"}]"; + PATCHTestResponse = await Util.PatchAsync("User/" + w2Id.ToString() + "/" + (OriginalConcurrencyToken - 1).ToString(), await Util.GetTokenAsync("manager", "l3tm3in"), patchJson); + Util.ValidateErrorCodeResponse(PATCHTestResponse, 2200, 400); + + //PATCH attempt remove name field + patchJson = "[{\"path\": \"/name\",\"op\": \"remove\"}]"; + PATCHTestResponse = await Util.PatchAsync("User/" + w2Id.ToString() + "/" + (OriginalConcurrencyToken - 1).ToString(), await Util.GetTokenAsync("manager", "l3tm3in"), patchJson); + Util.ValidateErrorCodeResponse(PATCHTestResponse, 2200, 400); + } + + + /// + /// + /// + [Fact] + public async void PatchPasswordShouldWork() + { + //CREATE + dynamic D = new JObject(); + D.name = Util.Uniquify("PatchPasswordShouldWork"); + D.ownerId = 1L; + D.active = true; + D.login = Util.Uniquify("LOGIN"); + D.password = Util.Uniquify("PASSWORD"); + D.roles = 0;//norole + D.localeId = 1;//random locale + D.userType = 3;//non scheduleable + + ApiResponse R = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(R); + long UserId = R.ObjectResponse["data"]["id"].Value(); + uint OriginalConcurrencyToken = R.ObjectResponse["data"]["concurrencyToken"].Value(); + + //Test can login + dynamic DCreds = new JObject(); + DCreds.password = D.password; + DCreds.login = D.login; + R = await Util.PostAsync("Auth", null, DCreds.ToString()); + Util.ValidateDataReturnResponseOk(R); + + //PUT + var NewPassword = "NEW_PASSWORD"; + D.password = NewPassword; + D.concurrencyToken = OriginalConcurrencyToken; + R = await Util.PutAsync("User/" + UserId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(R); + + //Test can login with new creds + //dynamic DCreds = new JObject(); + DCreds.password = NewPassword; + DCreds.login = D.login; + R = await Util.PostAsync("Auth", null, DCreds.ToString()); + Util.ValidateDataReturnResponseOk(R); + } + + + /// + /// + /// + [Fact] + public async void PutPasswordShouldWork() + { + //CREATE + dynamic D = new JObject(); + D.name = Util.Uniquify("PutPasswordShouldWork"); + D.ownerId = 1L; + D.active = true; + D.login = Util.Uniquify("LOGIN"); + D.password = Util.Uniquify("PASSWORD"); + D.roles = 0;//norole + D.localeId = 1;//random locale + D.userType = 3;//non scheduleable + + ApiResponse R = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), D.ToString()); + Util.ValidateDataReturnResponseOk(R); + long UserId = R.ObjectResponse["data"]["id"].Value(); + uint OriginalConcurrencyToken = R.ObjectResponse["data"]["concurrencyToken"].Value(); + + //Test can login + dynamic DCreds = new JObject(); + DCreds.password = D.password; + DCreds.login = D.login; + R = await Util.PostAsync("Auth", null, DCreds.ToString()); + Util.ValidateDataReturnResponseOk(R); + + //PATCH + var newPassword = "NEW_PASSWORD"; + string patchJson = "[{\"value\": \"" + newPassword + "\",\"path\": \"/password\",\"op\": \"replace\"}]"; + R = await Util.PatchAsync("User/" + UserId.ToString() + "/" + OriginalConcurrencyToken.ToString(), await Util.GetTokenAsync("manager", "l3tm3in"), patchJson); + Util.ValidateDataReturnResponseOk(R); + + //Test can login with new creds + //dynamic DCreds = new JObject(); + DCreds.password = newPassword; + DCreds.login = D.login; + R = await Util.PostAsync("Auth", null, DCreds.ToString()); + Util.ValidateDataReturnResponseOk(R); + } + + + + + /// + /// + /// + [Fact] + public async void UserListFilterAndSortWorks() + { + + var ObjectNameStart = Util.Uniquify("UserListFilterAndSortWorks"); + + //CREATE 3 TEST OBJECTS TO TEST ORDER + long FirstInOrdertId = 0; + long SecondInOrderId = 0; + long ThirdInOrderId = 0; + + dynamic d = new JObject(); + d.name = Util.Uniquify(ObjectNameStart); + d.active = false; + d.login = Util.Uniquify("LOGIN"); + d.password = Util.Uniquify("PASSWORD"); + d.roles = 0;//norole + d.localeId = 1;//random locale + d.userType = 3;//non scheduleable + + ApiResponse a = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + FirstInOrdertId = a.ObjectResponse["data"]["id"].Value(); + + d = new JObject(); + d.name = Util.Uniquify(ObjectNameStart); + d.login = Util.Uniquify("LOGIN"); + d.password = Util.Uniquify("PASSWORD"); + d.roles = 0;//norole + d.localeId = 1;//random locale + d.userType = 3;//non scheduleable + d.active = true; + + a = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + ThirdInOrderId = a.ObjectResponse["data"]["id"].Value(); + + d = new JObject(); + d.name = Util.Uniquify(ObjectNameStart); + d.login = Util.Uniquify("LOGIN"); + d.password = Util.Uniquify("PASSWORD"); + d.roles = 0;//norole + d.localeId = 1;//random locale + d.userType = 3;//non scheduleable + d.active = false; + a = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + SecondInOrderId = a.ObjectResponse["data"]["id"].Value(); + + + //CREATE FILTER + d = new JObject(); + d.name = Util.Uniquify(ObjectNameStart); + d["public"] = true; + d.listKey = "user"; + + //FILTER IN BY NAME FOR TESTING THIS RUN ONLY + dynamic dfilter = new JArray(); + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = ObjectNameStart; + dfilter.Add(DataFilterNameStart); + d.filter = dfilter.ToString(); + + dynamic dsortarray = new JArray(); + + //SORT ORDER ################### + //sort by active then by ID + + dynamic dsort = new JObject(); + dsort.fld = "active"; + dsort.dir = "+"; + dsortarray.Add(dsort); + + dsort = new JObject(); + dsort.fld = "id"; + dsort.dir = "+"; + dsortarray.Add(dsort); + + d.sort = dsortarray.ToString(); + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("manager", "l3tm3in"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"User/listusers?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + 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]["id"].Value().Should().Be(FirstInOrdertId); + a.ObjectResponse["data"][1]["id"].Value().Should().Be(SecondInOrderId); + a.ObjectResponse["data"][2]["id"].Value().Should().Be(ThirdInOrderId); + + + a = await Util.DeleteAsync("User/" + FirstInOrdertId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("User/" + SecondInOrderId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("User/" + ThirdInOrderId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + + /// + /// + /// + [Fact] + public async void UserPickListDefaultSortNoFilterWorks() + { + var RouteName = "User";//########## + + //NOW FETCH LIST WITH FILTER + ApiResponse a = await Util.GetAsync($"{RouteName}/picklist?Offset=0&Limit=999", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains exactly 3 records + var ItemCount = ((JArray)a.ObjectResponse["data"]).Count; + + for (int i = 0; i < ItemCount - 1; i++) + { + var firstName = a.ObjectResponse["data"][i]["name"].Value().Replace(" ", ""); + var secondName = a.ObjectResponse["data"][i + 1]["name"].Value().Replace(" ", ""); + int comparison = String.Compare(firstName, secondName, comparisonType: StringComparison.OrdinalIgnoreCase); + comparison.Should().BeNegative(); + } + } + + + + /// + /// + /// + [Fact] + public async void UserPickListSortByFieldAscendingWorks() + { + + var NameStart = Util.Uniquify("UserPickListSortByFieldAscendingWorks"); + + //CREATE 3 TEST objects TO TEST ORDER + long FirstInOrderId = 0; + long SecondInOrderId = 0; + long ThirdInOrderId = 0; + + dynamic d = new JObject(); + d.name = Util.Uniquify(NameStart); + d.active = false; + d.login = Util.Uniquify("LOGIN"); + d.password = Util.Uniquify("PASSWORD"); + d.roles = 0;//norole + d.localeId = 1;//random locale + d.userType = 3;//non scheduleable + + ApiResponse a = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + ThirdInOrderId = a.ObjectResponse["data"]["id"].Value(); + + d = new JObject(); + d.name = Util.Uniquify(NameStart); + d.active = true; + d.login = Util.Uniquify("LOGIN"); + d.password = Util.Uniquify("PASSWORD"); + d.roles = 0;//norole + d.localeId = 1;//random locale + d.userType = 2;//non scheduleable + a = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + SecondInOrderId = a.ObjectResponse["data"]["id"].Value(); + + d = new JObject(); + d.name = Util.Uniquify(NameStart); + d.active = false; + d.login = Util.Uniquify("LOGIN"); + d.password = Util.Uniquify("PASSWORD"); + d.roles = 0;//norole + d.localeId = 1;//random locale + d.userType = 1;//non scheduleable + a = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + FirstInOrderId = a.ObjectResponse["data"]["id"].Value(); + + + //CREATE FILTER + d = new JObject(); + d.name = Util.Uniquify(NameStart); + d["public"] = true; + d.listKey = "user"; + + //FILTER IN BY NAME FOR TESTING THIS RUN ONLY + dynamic dfilter = new JArray(); + //name starts with filter to constrict to widgets that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = NameStart; + dfilter.Add(DataFilterNameStart); + d.filter = dfilter.ToString(); + + //SORT ORDER ################### + dynamic dsortarray = new JArray(); + dynamic dsort = new JObject(); + dsort.fld = "usertype"; + dsort.dir = "+"; + dsortarray.Add(dsort); + d.sort = dsortarray.ToString(); + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("manager", "l3tm3in"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH WIDGET LIST WITH FILTER + a = await Util.GetAsync($"User/picklist?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + 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]["id"].Value().Should().Be(FirstInOrderId); + a.ObjectResponse["data"][1]["id"].Value().Should().Be(SecondInOrderId); + a.ObjectResponse["data"][2]["id"].Value().Should().Be(ThirdInOrderId); + + + a = await Util.DeleteAsync("User/" + FirstInOrderId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("User/" + SecondInOrderId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync("User/" + ThirdInOrderId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + + //================================================== + + }//eoc +}//eons diff --git a/User/UserInactive.cs b/User/UserInactive.cs new file mode 100644 index 0000000..64b99fe --- /dev/null +++ b/User/UserInactive.cs @@ -0,0 +1,31 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; + +namespace raven_integration +{ + + public class UserInactive + { + + /// + /// Inactive user should not be able to login + /// + [Fact] + public async void InactiveUserCantLogin() + { + dynamic DCreds = new JObject(); + DCreds.password = DCreds.login = "TEST_INACTIVE"; + ApiResponse a = await Util.PostAsync("Auth", null, DCreds.ToString()); + Util.ValidateErrorCodeResponse(a,2004, 401); + } + + + + + + //================================================== + + }//eoc +}//eons diff --git a/User/UserOptionsRu.cs b/User/UserOptionsRu.cs new file mode 100644 index 0000000..841963e --- /dev/null +++ b/User/UserOptionsRu.cs @@ -0,0 +1,127 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; + +namespace raven_integration +{ + + public class UserOptionsRu + { + + /// + /// Test all CRUD routes for a UserOptions object + /// + [Fact] + public async void RU() + { + + //CREATE a user + dynamic D1 = new JObject(); + D1.name = Util.Uniquify("Test UserOptions User"); + D1.ownerId = 1L; + D1.active = true; + D1.login = Util.Uniquify("LOGIN"); + D1.password = Util.Uniquify("PASSWORD"); + D1.roles = 0;//norole + D1.localeId = 1;//random locale + D1.userType = 3;//non scheduleable + + ApiResponse R = await Util.PostAsync("User", await Util.GetTokenAsync("manager", "l3tm3in"), D1.ToString()); + Util.ValidateDataReturnResponseOk(R); + long UserId = R.ObjectResponse["data"]["id"].Value(); + + //Now there should be a user options available for this user + + + //RETRIEVE companion USEROPTIONS object + + //Get it + R = await Util.GetAsync("UserOptions/" + UserId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(R); + //ensure the default value is set + R.ObjectResponse["data"]["uiColor"].Value().Should().Be(0); + uint concurrencyToken = R.ObjectResponse["data"]["concurrencyToken"].Value(); + + //UPDATE + //PUT + dynamic D2 = new JObject(); + D2.emailaddress = "testuseroptions@helloayanova.com"; + D2.TimeZoneOffset = -7.5M;//Decimal value + D2.UiColor = -2097216;//Int value (no suffix for int literals) + D2.concurrencyToken = concurrencyToken; + ApiResponse PUTTestResponse = await Util.PutAsync("UserOptions/" + UserId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in"), D2.ToString()); + Util.ValidateHTTPStatusCode(PUTTestResponse, 200); + + //VALIDATE + R = await Util.GetAsync("UserOptions/" + UserId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(R); + //ensure the default value is set + R.ObjectResponse["data"]["emailAddress"].Value().Should().Be(D2.emailaddress.ToString()); + R.ObjectResponse["data"]["timeZoneOffset"].Value().Should().Be((decimal)D2.TimeZoneOffset); + R.ObjectResponse["data"]["uiColor"].Value().Should().Be((int)D2.UiColor); + concurrencyToken = R.ObjectResponse["data"]["concurrencyToken"].Value(); + + + //PATCH + string newEmail = "patchtestuseroptions@helloayanova.com"; + string patchJson = "[{\"value\": \"" + newEmail + "\",\"path\": \"/emailAddress\",\"op\": \"replace\"}]"; + ApiResponse PATCHTestResponse = await Util.PatchAsync("UserOptions/" + UserId.ToString() + "/" + concurrencyToken.ToString(), await Util.GetTokenAsync("manager", "l3tm3in"), patchJson); + Util.ValidateHTTPStatusCode(PATCHTestResponse, 200); + + //check PATCH worked + R = await Util.GetAsync("UserOptions/" + UserId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(R); + //ensure the default value is set + R.ObjectResponse["data"]["emailAddress"].Value().Should().Be(newEmail); + R.ObjectResponse["data"]["timeZoneOffset"].Value().Should().Be((decimal)D2.TimeZoneOffset); + R.ObjectResponse["data"]["uiColor"].Value().Should().Be((int)D2.UiColor); + // concurrencyToken = R.ObjectResponse["data"]["concurrencyToken"].Value(); + + //DELETE USER + ApiResponse DELETETestResponse = await Util.DeleteAsync("User/" + UserId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(DELETETestResponse, 204); + + //CHECK DELETE USER REMOVED USEROPTIONS + R = await Util.GetAsync("UserOptions/" + UserId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateResponseNotFound(R); + } + + + /// + /// Test not found + /// + [Fact] + public async void GetNonExistentItemShouldError() + { + //Get non existant + //Should return status code 404, api error code 2010 + ApiResponse R = await Util.GetAsync("UserOptions/999999", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateResponseNotFound(R); + } + + /// + /// Test bad modelstate + /// + [Fact] + public async void GetBadModelStateShouldError() + { + //Get non existant + //Should return status code 400, api error code 2200 and a first target in details of "id" + ApiResponse R = await Util.GetAsync("UserOptions/2q2", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateBadModelStateResponse(R, "id"); + } + + + + + + + + + + + //================================================== + + }//eoc +}//eons diff --git a/Widget/WidgetCrud.cs b/Widget/WidgetCrud.cs new file mode 100644 index 0000000..44df52a --- /dev/null +++ b/Widget/WidgetCrud.cs @@ -0,0 +1,244 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; + +namespace raven_integration +{ + + public class WidgetCrud + { + + /// + /// Test all CRUD routes for a widget + /// + [Fact] + public async void CRUD() + { + /* + { + "id": 0, + "name": "string", + "dollarAmount": 0, + "active": true, + "roles": 0 + } + */ + //CREATE + dynamic w1 = new JObject(); + w1.name = Util.Uniquify("First Test WIDGET"); + w1.dollarAmount = 1.11m; + w1.active = true; + w1.roles = 0; + w1.notes = "The quick brown fox jumped over the six lazy dogs!"; + + //Tags + dynamic dTagsArray = new JArray(); + dTagsArray.Add("Red Tag"); + dTagsArray.Add("ORANGE IS THE NEW BLACK"); + dTagsArray.Add("yellow"); + dTagsArray.Add("green"); + dTagsArray.Add("blue"); + dTagsArray.Add("indigo"); + dTagsArray.Add("VIOLET Tag"); + w1.tags = dTagsArray; + + ApiResponse r1 = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w1.ToString()); + Util.ValidateDataReturnResponseOk(r1); + long w1Id = r1.ObjectResponse["data"]["id"].Value(); + + dynamic w2 = new JObject(); + w2.name = Util.Uniquify("Second Test WIDGET"); + w2.dollarAmount = 2.22m; + w2.active = true; + w2.roles = 0; + w2.notes = "What is the frequency Kenneth?"; + w2.tags = dTagsArray; + + ApiResponse r2 = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w2.ToString()); + Util.ValidateDataReturnResponseOk(r2); + long w2Id = r2.ObjectResponse["data"]["id"].Value(); + + //RETRIEVE + + //Get one + ApiResponse r3 = await Util.GetAsync("Widget/" + w2Id.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(r3); + r3.ObjectResponse["data"]["name"].Value().Should().Be(w2.name.ToString()); + r3.ObjectResponse["data"]["notes"].Value().Should().Be(w2.notes.ToString()); + var returnedTags = ((JArray)r3.ObjectResponse["data"]["tags"]); + returnedTags.Count.Should().Be(7); + returnedTags[0].Value().Should().Be("red-tag"); + returnedTags[1].Value().Should().Be("orange-is-the-new-black"); + returnedTags[2].Value().Should().Be("yellow"); + returnedTags[3].Value().Should().Be("green"); + returnedTags[4].Value().Should().Be("blue"); + returnedTags[5].Value().Should().Be("indigo"); + returnedTags[6].Value().Should().Be("violet-tag"); + + + + //UPDATE + //PUT + + //update w2id + w2.name = Util.Uniquify("UPDATED VIA PUT SECOND TEST WIDGET"); + w2.OwnerId = 1; + w2.concurrencyToken = r2.ObjectResponse["data"]["concurrencyToken"].Value(); + ApiResponse PUTTestResponse = await Util.PutAsync("Widget/" + w2Id.ToString(), await Util.GetTokenAsync("manager", "l3tm3in"), w2.ToString()); + Util.ValidateHTTPStatusCode(PUTTestResponse, 200); + + //check PUT worked + ApiResponse checkPUTWorked = await Util.GetAsync("Widget/" + w2Id.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateNoErrorInResponse(checkPUTWorked); + checkPUTWorked.ObjectResponse["data"]["name"].Value().Should().Be(w2.name.ToString()); + uint concurrencyToken = PUTTestResponse.ObjectResponse["data"]["concurrencyToken"].Value(); + + //PATCH + var newName = Util.Uniquify("UPDATED VIA PATCH SECOND TEST WIDGET"); + string patchJson = "[{\"value\": \"" + newName + "\",\"path\": \"/name\",\"op\": \"replace\"}]"; + ApiResponse PATCHTestResponse = await Util.PatchAsync("Widget/" + w2Id.ToString() + "/" + concurrencyToken.ToString(), await Util.GetTokenAsync("manager", "l3tm3in"), patchJson); + Util.ValidateHTTPStatusCode(PATCHTestResponse, 200); + + //check PATCH worked + ApiResponse checkPATCHWorked = await Util.GetAsync("Widget/" + w2Id.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateNoErrorInResponse(checkPATCHWorked); + checkPATCHWorked.ObjectResponse["data"]["name"].Value().Should().Be(newName); + + //DELETE + ApiResponse DELETETestResponse = await Util.DeleteAsync("Widget/" + w2Id.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(DELETETestResponse, 204); + } + + + + + + + /// + /// Test not found + /// + [Fact] + public async void GetNonExistentItemShouldError() + { + //Get non existant + //Should return status code 404, api error code 2010 + ApiResponse a = await Util.GetAsync("Widget/999999", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateResponseNotFound(a); + } + + /// + /// Test bad modelstate + /// + [Fact] + public async void GetBadModelStateShouldError() + { + //Get non existant + //Should return status code 400, api error code 2200 and a first target in details of "id" + ApiResponse a = await Util.GetAsync("Widget/2q2", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateBadModelStateResponse(a, "id"); + } + + + /// + /// Test server exception + /// + [Fact] + public async void ServerExceptionShouldErrorPropertly() + { + //Get non existant + //Should return status code 400, api error code 2200 and a first target in details of "id" + ApiResponse a = await Util.GetAsync("Widget/exception", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateServerExceptionResponse(a); + } + + + + /// + /// Test server alt exception + /// + [Fact] + public async void ServerAltExceptionShouldErrorPropertly() + { + //Get non existant + //Should return status code 400, api error code 2200 and a first target in details of "id" + ApiResponse a = await Util.GetAsync("Widget/altexception", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateServerExceptionResponse(a); + } + + + + + /// + /// + /// + [Fact] + public async void PutConcurrencyViolationShouldFail() + { + + //CREATE + + dynamic w2 = new JObject(); + w2.name = Util.Uniquify("PutConcurrencyViolationShouldFail"); + w2.dollarAmount = 2.22m; + w2.active = true; + w2.roles = 0; + + ApiResponse r2 = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w2.ToString()); + Util.ValidateDataReturnResponseOk(r2); + long w2Id = r2.ObjectResponse["data"]["id"].Value(); + uint OriginalConcurrencyToken = r2.ObjectResponse["data"]["concurrencyToken"].Value(); + + + + //UPDATE + //PUT + + w2.name = Util.Uniquify("PutConcurrencyViolationShouldFail UPDATE VIA PUT "); + w2.OwnerId = 1; + w2.concurrencyToken = OriginalConcurrencyToken - 1;//bad token + ApiResponse PUTTestResponse = await Util.PutAsync("Widget/" + w2Id.ToString(), await Util.GetTokenAsync("manager", "l3tm3in"), w2.ToString()); + Util.ValidateConcurrencyError(PUTTestResponse); + + + } + + + + + /// + /// + /// + [Fact] + public async void PatchConcurrencyViolationShouldFail() + { + + //CREATE + + dynamic w2 = new JObject(); + w2.name = Util.Uniquify("PatchConcurrencyViolationShouldFail"); + w2.dollarAmount = 2.22m; + w2.active = true; + w2.roles = 0; + + ApiResponse r2 = await Util.PostAsync("Widget", await Util.GetTokenAsync("manager", "l3tm3in"), w2.ToString()); + Util.ValidateDataReturnResponseOk(r2); + long w2Id = r2.ObjectResponse["data"]["id"].Value(); + uint OriginalConcurrencyToken = r2.ObjectResponse["data"]["concurrencyToken"].Value(); + + + //PATCH + var newName = Util.Uniquify("PutConcurrencyViolationShouldFail UPDATED VIA PATCH"); + string patchJson = "[{\"value\": \"" + newName + "\",\"path\": \"/name\",\"op\": \"replace\"}]"; + ApiResponse PATCHTestResponse = await Util.PatchAsync("Widget/" + w2Id.ToString() + "/" + (OriginalConcurrencyToken - 1).ToString(), await Util.GetTokenAsync("manager", "l3tm3in"), patchJson); + Util.ValidateConcurrencyError(PATCHTestResponse); + } + + + + + + //================================================== + + }//eoc +}//eons diff --git a/Widget/WidgetLists.cs b/Widget/WidgetLists.cs new file mode 100644 index 0000000..1ae974d --- /dev/null +++ b/Widget/WidgetLists.cs @@ -0,0 +1,214 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Collections.Concurrent; + +namespace raven_integration +{ + + public class WidgetLists + { + + + + + /// + /// + /// + [Fact] + public async void WidgetPickListDefaultSortNoFilterWorks() + { + var RouteName = "Widget";//########## + + //NOW FETCH LIST WITH FILTER + ApiResponse a = await Util.GetAsync($"{RouteName}/picklist?Offset=0&Limit=999", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert contains exactly 3 records + var ItemCount = ((JArray)a.ObjectResponse["data"]).Count; + + for (int i = 0; i < ItemCount - 1; i++) + { + //Note it's necessary to replace the spaces because postgres thinks spaces go last and the string comparison thinks they go first + var firstName = a.ObjectResponse["data"][i]["name"].Value().Replace(" ",""); + var secondName = a.ObjectResponse["data"][i + 1]["name"].Value().Replace(" ",""); + int comparison = String.Compare(firstName, secondName, comparisonType: StringComparison.OrdinalIgnoreCase); + comparison.Should().BeNegative(); + } + } + + + /// + /// + /// + [Fact] + public async void WidgetPickListSortByFieldAscendingWorks() + { + + var NameStart = Util.Uniquify("WidgetPickListSortByFieldAscendingWorks"); + var RouteName = "Widget"; + + //CREATE 3 TEST OBJECTS TO TEST ORDER + long FirstInOrderId = 0; + long SecondInOrderId = 0; + long ThirdInOrderId = 0; + + dynamic d = new JObject(); + d.name = Util.Uniquify(NameStart); + d.startDate = DateTime.Now; + d.endDate = DateTime.Now.AddHours(1); + + ApiResponse a = await Util.PostAsync(RouteName, await Util.GetTokenAsync("manager", "l3tm3in"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + FirstInOrderId = a.ObjectResponse["data"]["id"].Value(); + + d = new JObject(); + d.name = Util.Uniquify(NameStart); + d.startDate = DateTime.Now.AddHours(1); + d.endDate = DateTime.Now.AddHours(2); + a = await Util.PostAsync(RouteName, await Util.GetTokenAsync("manager", "l3tm3in"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + SecondInOrderId = a.ObjectResponse["data"]["id"].Value(); + + d = new JObject(); + d.name = Util.Uniquify(NameStart); + d.startDate = DateTime.Now.AddHours(2); + d.endDate = DateTime.Now.AddHours(3); + a = await Util.PostAsync(RouteName, await Util.GetTokenAsync("manager", "l3tm3in"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + ThirdInOrderId = a.ObjectResponse["data"]["id"].Value(); + + + //CREATE FILTER + d = new JObject(); + d.name = Util.Uniquify(NameStart); + d["public"] = true; + d.listKey = "widget"; + + //FILTER IN BY NAME FOR TESTING THIS RUN ONLY + dynamic dfilter = new JArray(); + //name starts with filter to constrict to objects that this test block created only + dynamic DataFilterNameStart = new JObject(); + DataFilterNameStart.fld = "name"; + DataFilterNameStart.op = Util.OpStartsWith; + DataFilterNameStart.value = NameStart; + dfilter.Add(DataFilterNameStart); + d.filter = dfilter.ToString(); + + //SORT ORDER ################### + dynamic dsortarray = new JArray(); + dynamic dsort = new JObject(); + dsort.fld = "startdate"; + dsort.dir = "+"; + dsortarray.Add(dsort); + d.sort = dsortarray.ToString(); + + a = await Util.PostAsync("DataFilter", await Util.GetTokenAsync("manager", "l3tm3in"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + long DataFilterId = a.ObjectResponse["data"]["id"].Value(); + + //NOW FETCH LIST WITH FILTER + a = await Util.GetAsync($"{RouteName}/picklist?Offset=0&Limit=999&DataFilterId={DataFilterId.ToString()}", await Util.GetTokenAsync("manager", "l3tm3in")); + 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]["id"].Value().Should().Be(FirstInOrderId); + a.ObjectResponse["data"][1]["id"].Value().Should().Be(SecondInOrderId); + a.ObjectResponse["data"][2]["id"].Value().Should().Be(ThirdInOrderId); + + + a = await Util.DeleteAsync($"{RouteName}/" + FirstInOrderId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync($"{RouteName}/" + SecondInOrderId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + a = await Util.DeleteAsync($"{RouteName}/" + ThirdInOrderId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + //DELETE DATAFILTER + a = await Util.DeleteAsync("DataFilter/" + DataFilterId.ToString(), await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateHTTPStatusCode(a, 204); + + } + + + /// + /// Paging test + /// + [Fact] + public async void PagingShouldWorkAsExpected() + { + //Get all + ApiResponse a = await Util.GetAsync("Widget/listwidgets?Offset=2&Limit=3", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + + //assert aAll contains at least two records + ((JArray)a.ObjectResponse["data"]).Count.Should().Be(3); + + JObject jp = (JObject)a.ObjectResponse["paging"]; + jp["count"].Value().Should().BeGreaterThan(5); + jp["offset"].Value().Should().Be(2); + jp["limit"].Value().Should().Be(3); + jp["first"].Value().Should().EndWith("&pageSize=3"); + jp["previous"].Value().Should().EndWith("&pageSize=3"); + jp["next"].Value().Should().EndWith("&pageSize=3"); + jp["last"].Value().Should().EndWith("&pageSize=3"); + } + + + + + /// + /// + /// + [Fact] + public async void FilterOptionsRouteShouldWorkAsExpected() + { + //Get options with manager (english user) + ApiResponse a = await Util.GetAsync("Widget/FilterOptions", await Util.GetTokenAsync("manager", "l3tm3in")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + a.ObjectResponse["data"]["key"].Value().Should().Be("widget"); + ((JArray)a.ObjectResponse["data"]["flds"]).Count.Should().Be(10); + + int DollarAmountFieldNumber = 4; + + a.ObjectResponse["data"]["flds"][DollarAmountFieldNumber]["lt"].Value().Should().Be("Price"); + + a = await Util.GetAsync("Widget/FilterOptions", await Util.GetTokenAsync("es", "es")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + a.ObjectResponse["data"]["flds"][DollarAmountFieldNumber]["lt"].Value().Should().Be("Importe"); + + a = await Util.GetAsync("Widget/FilterOptions", await Util.GetTokenAsync("fr", "fr")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + a.ObjectResponse["data"]["flds"][DollarAmountFieldNumber]["lt"].Value().Should().Be("Montant"); + + a = await Util.GetAsync("Widget/FilterOptions", await Util.GetTokenAsync("de", "de")); + Util.ValidateDataReturnResponseOk(a); + Util.ValidateHTTPStatusCode(a, 200); + a.ObjectResponse["data"]["flds"][DollarAmountFieldNumber]["lt"].Value().Should().Be("Betrag"); + + } + + + + + + + //================================================== + + }//eoc +}//eons diff --git a/Widget/WidgetRights.cs b/Widget/WidgetRights.cs new file mode 100644 index 0000000..52794c2 --- /dev/null +++ b/Widget/WidgetRights.cs @@ -0,0 +1,261 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; +using System.Collections.Generic; +using System.Collections.Concurrent; + +namespace raven_integration +{ + // [Collection("APICOLLECTION")] + public class WidgetRights + { + + + /// + /// Test not authorized error return + /// + [Fact] + public async void ServerShouldNotAllowUnauthenticatedAccess() + { + ApiResponse a = await Util.GetAsync("Widget/list"); + Util.ValidateHTTPStatusCode(a, 401); + } + + /// + /// Test insufficient read rights error return + /// + [Fact] + public async void ServerShouldNotAllowReadUnauthorizedAccess() + { + ApiResponse a = await Util.GetAsync("Widget/listwidgets", await Util.GetTokenAsync( "OpsAdminFull")); + //2004 unauthorized + Util.ValidateErrorCodeResponse(a, 2004, 401); + } + + + + /// + /// Test insufficient create rights error return + /// + [Fact] + public async void ServerShouldNotAllowCreateUnauthorizedAccess() + { + //CREATE + dynamic d = new JObject(); + d.name = Util.Uniquify("ServerShouldNotAllowCreateUnauthorizedAccess TEST WIDGET"); + d.created = DateTime.Now.ToString(); + d.dollarAmount = 1.11m; + d.active = true; + d.roles = 0; + + //BizAdminLimited user should not be able to create a widget, only read them + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync( "BizAdminLimited"), d.ToString()); + + //2004 unauthorized + Util.ValidateErrorCodeResponse(a, 2004, 401); + } + + + + /// + /// Test owner rights to modify + /// + [Fact] + public async void ServerShouldAllowOwnerOnlyRightsUserToPatchOwn() + { + + // TECH FULL has owner only rights to widget + + //CREATE + dynamic d = new JObject(); + d.name = Util.Uniquify("ServerShouldAllowOwnerOnlyRightsUserToPatchOwn TEST WIDGET"); + d.created = DateTime.Now.ToString(); + d.dollarAmount = 1.11m; + d.active = true; + d.roles = 0; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync( "TechFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + long Id = a.ObjectResponse["data"]["id"].Value(); + uint OriginalConcurrencyToken = a.ObjectResponse["data"]["concurrencyToken"].Value(); + + //Now attempt to modify it via patch + var newName = Util.Uniquify("ServerShouldAllowOwnerOnlyRightsUserToPatchOwn - UPDATED TEST WIDGET"); + string patchJson = "[{\"value\": \"" + newName + "\",\"path\": \"/name\",\"op\": \"replace\"}]"; + a = await Util.PatchAsync("Widget/" + Id.ToString() + "/" + OriginalConcurrencyToken.ToString(), await Util.GetTokenAsync( "TechFull"), patchJson); + Util.ValidateHTTPStatusCode(a, 200); + } + + + /// + /// Test owner rights fails to modify other creator object + /// + [Fact] + public async void ServerShouldDisAllowOwnerOnlyRightsUserToPatchNonOwned() + { + // TECH FULL has owner only rights to widget + //INVENTORY FULL has full rights to widget + + //CREATE + dynamic d = new JObject(); + d.name = Util.Uniquify("ServerShouldDisAllowOwnerOnlyRightsUserToPatchNonOwned TEST WIDGET"); + d.created = DateTime.Now.ToString(); + d.dollarAmount = 1.11m; + d.active = true; + d.roles = 0; + + //create via inventory full test user + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync( "InventoryFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + long Id = a.ObjectResponse["data"]["id"].Value(); + uint OriginalConcurrencyToken = a.ObjectResponse["data"]["concurrencyToken"].Value(); + + //Now TechFullAuthToken attempt to modify it via patch + var newName = Util.Uniquify("ServerShouldDisAllowOwnerOnlyRightsUserToPatchNonOwned - UPDATED TEST WIDGETB"); + string patchJson = "[{\"value\": \"" + newName + "\",\"path\": \"/name\",\"op\": \"replace\"}]"; + a = await Util.PatchAsync("Widget/" + Id.ToString() + "/" + OriginalConcurrencyToken.ToString(), await Util.GetTokenAsync( "TechFull"), patchJson); + //2004 unauthorized expected + Util.ValidateErrorCodeResponse(a, 2004, 401); + + + } + + + + + + /// + /// Test owner rights to modify + /// + [Fact] + public async void ServerShouldAllowOwnerOnlyRightsUserToPutOwn() + { + + // TECH FULL has owner only rights to widget + + //CREATE + dynamic d = new JObject(); + d.name = Util.Uniquify("ServerShouldAllowOwnerOnlyRightsUserToPutOwn TEST WIDGET"); + d.created = DateTime.Now.ToString(); + d.dollarAmount = 1.11m; + d.active = true; + d.roles = 0; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync( "TechFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + long Id = a.ObjectResponse["data"]["id"].Value(); + uint OriginalConcurrencyToken = a.ObjectResponse["data"]["concurrencyToken"].Value(); + + //Now attempt to modify it via patch + var newName = Util.Uniquify("ServerShouldAllowOwnerOnlyRightsUserToPutOwn - UPDATED TEST WIDGET"); + d.OwnerId = 1; + d.name = newName; + d.concurrencyToken = OriginalConcurrencyToken; + + a = await Util.PutAsync("Widget/" + Id.ToString(), await Util.GetTokenAsync( "TechFull"), d.ToString()); + Util.ValidateHTTPStatusCode(a, 200); + } + + + /// + /// Test owner rights fails to modify other creator object + /// + [Fact] + public async void ServerShouldDisAllowOwnerOnlyRightsUserToPutNonOwned() + { + // TECH FULL has owner only rights to widget + //INVENTORY FULL has full rights to widget + + //CREATE + dynamic d = new JObject(); + d.name = Util.Uniquify("ServerShouldDisAllowOwnerOnlyRightsUserToPutNonOwned TEST WIDGET"); + d.created = DateTime.Now.ToString(); + d.dollarAmount = 1.11m; + d.active = true; + d.roles = 0; + + //create via inventory full test user + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync( "InventoryFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + long Id = a.ObjectResponse["data"]["id"].Value(); + + //Now TechFullAuthToken attempt to modify it via patch + var newName = Util.Uniquify("ServerShouldDisAllowOwnerOnlyRightsUserToPutNonOwned - UPDATED TEST WIDGET"); + d.name = newName; + a = await Util.PutAsync("Widget/" + Id.ToString(), await Util.GetTokenAsync( "TechFull"), d.ToString()); + //2004 unauthorized expected + Util.ValidateErrorCodeResponse(a, 2004, 401); + + + } + + + + /// + /// Test owner rights to delete + /// + [Fact] + public async void ServerShouldAllowOwnerOnlyRightsUserToDelete() + { + + // TECH FULL has owner only rights to widget + + //CREATE + dynamic d = new JObject(); + d.name = Util.Uniquify("ServerShouldAllowOwnerOnlyRightsUserToDelete TEST WIDGET"); + d.created = DateTime.Now.ToString(); + d.dollarAmount = 1.11m; + d.active = true; + d.roles = 0; + + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync( "TechFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + long Id = a.ObjectResponse["data"]["id"].Value(); + + //Now attempt to delete it + a = await Util.DeleteAsync("Widget/" + Id.ToString(), await Util.GetTokenAsync( "TechFull")); + Util.ValidateHTTPStatusCode(a, 204); + } + + + + + /// + /// Test owner rights fails to delete other creator object + /// + [Fact] + public async void ServerShouldDisAllowOwnerOnlyRightsUserToDeleteNonOwned() + { + // TECH FULL has owner only rights to widget + //INVENTORY FULL has full rights to widget + + //CREATE + dynamic d = new JObject(); + d.name = Util.Uniquify("ServerShouldDisAllowOwnerOnlyRightsUserToDeleteNonOwned TEST WIDGET"); + d.created = DateTime.Now.ToString(); + d.dollarAmount = 1.11m; + d.active = true; + d.roles = 0; + + //create via inventory full test user + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync( "InventoryFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + long Id = a.ObjectResponse["data"]["id"].Value(); + + //Now attempt delete + a = await Util.DeleteAsync("Widget/" + Id.ToString(), await Util.GetTokenAsync( "TechFull")); + //2004 unauthorized expected + Util.ValidateErrorCodeResponse(a, 2004, 401); + + + } + + + + + + //================================================== + + }//eoc +}//eons diff --git a/Widget/WidgetValidationTests.cs b/Widget/WidgetValidationTests.cs new file mode 100644 index 0000000..e3bd09b --- /dev/null +++ b/Widget/WidgetValidationTests.cs @@ -0,0 +1,232 @@ +using System; +using Xunit; +using Newtonsoft.Json.Linq; +using FluentAssertions; +using System.Collections.Generic; +using System.Collections.Concurrent; + +namespace raven_integration +{ + public class WidgetValidationTest + { + + + // /// + // /// Test business rule should be active on new + // /// + // [Fact] + // public async void BusinessRuleNewShouldBeActiveShouldWork() + // { + // //CREATE attempt with broken rules + // dynamic d = new JObject(); + // d.name = Util.Uniquify("ServerShouldDisAllowOwnerOnlyRightsUserToDeleteNonOwned TEST WIDGET"); + // d.created = DateTime.Now.ToString(); + // d.dollarAmount = 1.11m; + // d.active = false;//<--- BROKEN RULE new widget must be active = true!! + // d.roles = 0; + + // //create via inventory full test user + // ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("InventoryFull"), d.ToString()); + + // Util.ValidateErrorCodeResponse(a, 2200, 400); + // Util.ShouldContainValidationError(a, "Active", "InvalidValue"); + + // } + + + + + /// + /// Test business rule name should be unique + /// + [Fact] + public async void BusinessRuleNameMustBeUnique() + { + //CREATE attempt with broken rules + dynamic d = new JObject(); + d.name = Util.Uniquify("BusinessRuleNameMustBeUnique TEST WIDGET"); + d.created = DateTime.Now.ToString(); + d.dollarAmount = 1.11m; + d.active = true; + d.roles = 0; + + //create via inventory full test user + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("InventoryFull"), d.ToString()); + Util.ValidateDataReturnResponseOk(a); + + //Now try to create again with same name + a = await Util.PostAsync("Widget", await Util.GetTokenAsync("InventoryFull"), d.ToString()); + + //2002 in-valid expected + Util.ValidateErrorCodeResponse(a, 2200, 400); + Util.ShouldContainValidationError(a, "Name", "NotUnique"); + + } + + + + /// + /// + /// + [Fact] + public async void BusinessRuleNameRequired() + { + + dynamic d = new JObject(); + d.name = ""; + d.created = DateTime.Now.ToString(); + d.dollarAmount = 1.11m; + d.active = true; + d.roles = 0; + + //create via inventory full test user + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("InventoryFull"), d.ToString()); + + + //2002 in-valid expected + Util.ValidateErrorCodeResponse(a, 2200, 400); + Util.ShouldContainValidationError(a, "Name", "RequiredPropertyEmpty"); + + } + + /// + /// + /// + [Fact] + public async void BusinessRuleNameLengthExceeded() + { + + dynamic d = new JObject(); + d.name = new string('A', 256); ; + d.created = DateTime.Now.ToString(); + d.dollarAmount = 1.11m; + d.active = true; + d.roles = 0; + + //create via inventory full test user + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("InventoryFull"), d.ToString()); + + + //2002 in-valid expected + Util.ValidateErrorCodeResponse(a, 2200, 400); + Util.ShouldContainValidationError(a, "Name", "LengthExceeded", "255 max"); + + } + + + + + /// + /// + /// + [Fact] + public async void BusinessRuleStartDateWithoutEndDateShouldError() + { + + dynamic d = new JObject(); + d.name = Util.Uniquify("BusinessRuleStartDateWithoutEndDateShouldError TEST"); + d.created = DateTime.Now.ToString(); + d.startDate = d.created; + //NO END DATE ERRROR + d.dollarAmount = 1.11m; + d.active = true; + d.roles = 0; + + //create via inventory full test user + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("InventoryFull"), d.ToString()); + + + //2002 in-valid expected + Util.ValidateErrorCodeResponse(a, 2200, 400); + Util.ShouldContainValidationError(a, "EndDate", "RequiredPropertyEmpty"); + + } + + + + /// + /// + /// + [Fact] + public async void BusinessRuleEndDateWithoutStartDateShouldError() + { + + dynamic d = new JObject(); + d.name = Util.Uniquify("BusinessRuleEndDateWithoutStartDateShouldError TEST"); + d.created = DateTime.Now.ToString(); + d.endDate = d.created; + //NO START DATE ERRROR + d.dollarAmount = 1.11m; + d.active = true; + d.roles = 0; + + //create via inventory full test user + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("InventoryFull"), d.ToString()); + + + //2002 in-valid expected + Util.ValidateErrorCodeResponse(a, 2200, 400); + Util.ShouldContainValidationError(a, "StartDate", "RequiredPropertyEmpty"); + + } + + + /// + /// + /// + [Fact] + public async void BusinessRuleEndDateBeforeStartDateShouldError() + { + + dynamic d = new JObject(); + d.name = Util.Uniquify("BusinessRuleEndDateBeforeStartDateShouldError TEST"); + d.created = DateTime.Now.ToString(); + d.startDate = DateTime.Now.ToString(); + d.endDate = DateTime.Now.AddHours(-1).ToString(); + //NO START DATE ERRROR + d.dollarAmount = 1.11m; + d.active = true; + d.roles = 0; + + //create via inventory full test user + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("InventoryFull"), d.ToString()); + + + //2002 in-valid expected + Util.ValidateErrorCodeResponse(a, 2200, 400); + Util.ShouldContainValidationError(a, "StartDate", "StartDateMustComeBeforeEndDate"); + + } + + + + /// + /// + /// + [Fact] + public async void BusinessRuleEnumInvalidShouldError() + { + + dynamic d = new JObject(); + d.name = Util.Uniquify("BusinessRuleEnumInvalidShouldError TEST"); + d.created = DateTime.Now.ToString(); + + //NO END DATE ERRROR + d.dollarAmount = 1.11m; + d.active = true; + d.roles = 99999;//<---BAD ROLE VALUE + + //create via inventory full test user + ApiResponse a = await Util.PostAsync("Widget", await Util.GetTokenAsync("InventoryFull"), d.ToString()); + + + //2002 in-valid expected + Util.ValidateErrorCodeResponse(a, 2200, 400); + Util.ShouldContainValidationError(a, "Roles", "InvalidValue"); + + } + + //================================================== + + }//eoc +}//eons diff --git a/burntest.bat b/burntest.bat new file mode 100644 index 0000000..be4b5ff --- /dev/null +++ b/burntest.bat @@ -0,0 +1,8 @@ +SET /a VAR=0 +:HOME +SET /a VAR=VAR+1 +rem 1000 runs in local debug mode server is about 6 hours at current test pace with huge data +IF %VAR%==500 goto :End +dotnet test +goto :HOME +:End \ No newline at end of file diff --git a/raven-integration.csproj b/raven-integration.csproj new file mode 100644 index 0000000..b9922dc --- /dev/null +++ b/raven-integration.csproj @@ -0,0 +1,17 @@ + + + + netcoreapp2.1 + false + true + + + + + + + + + + + diff --git a/testdata/ayanova.data.dump.xxx.zip b/testdata/ayanova.data.dump.xxx.zip new file mode 100644 index 0000000000000000000000000000000000000000..cbf41683596a6f6348322a2931ebef6f2f206079 GIT binary patch literal 87822 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 z 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
    `>yzAB8@H{LzDuZ?$Bx)2+$P94 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 ZJ6}kR!sx~RFZ~it^_`|-jlA`T{{xPpEYbh~ literal 0 HcmV?d00001 diff --git a/testdata/test.zip b/testdata/test.zip new file mode 100644 index 0000000000000000000000000000000000000000..a1a3fc59d032a2dd198cd7ce63a400088994b4c7 GIT binary patch literal 2306 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 cztaIwO9ci100001009760001z2mk;80BJy10ssI2 literal 0 HcmV?d00001 diff --git a/util.cs b/util.cs new file mode 100644 index 0000000..61ac682 --- /dev/null +++ b/util.cs @@ -0,0 +1,404 @@ +using System; +using System.Threading.Tasks; +using System.Net.Http; +using System.Net.Http.Headers; +using Newtonsoft.Json.Linq; +using FluentAssertions; +using System.Collections.Concurrent; + +namespace raven_integration +{ + public static class Util + { + public const string OpEquality = "="; + public const string OpGreaterThan = ">"; + public const string OpGreaterThanOrEqualTo = ">="; + public const string OpLessThan = "<"; + public const string OpLessThanOrEqualTo = "<="; + public const string OpNotEqual = "!="; + public const string OpNotLike = "!%"; + public const string OpStartsWith = "%-"; + public const string OpEndsWith = "-%"; + public const string OpContains = "-%-"; + public const string OpNotContains = "!-%-"; + + private static HttpClient client { get; } = new HttpClient(); + + private static string API_BASE_URL = "http://localhost:7575/api/v8.0/"; + //private static string API_BASE_URL = "https://test.helloayanova.com/api/v8.0/"; + + public static string TEST_DATA_FOLDER = @"..\..\..\testdata\"; + + public static ConcurrentDictionary authDict = new ConcurrentDictionary();//10,32 + + private static AutoId Auto { get; } = new AutoId(0); + + public static string Uniquify(string s) + { + // return s + " " + Auto.GetNext().ToString(); + return s + " " + (Auto.GetNext() + ((DateTimeOffset)DateTime.Now).ToUnixTimeMilliseconds()).ToString(); + } + + public async static Task GetTokenAsync(string login, string password = null) + { + // Console.WriteLine($"GetTokenAsync:{login}"); + //System.Diagnostics.Trace.WriteLine($"GetTokenAsync:{login}"); + + if (password == null) + password = login; + + if (!authDict.ContainsKey(login)) + { + dynamic creds = new JObject(); + creds.login = login; + creds.password = password; + + ApiResponse a = await Util.PostAsync("Auth", null, creds.ToString()); + //Put this in when having concurrency issue during auth and old style dl token creation during login + ValidateDataReturnResponseOk(a); + + authDict[login] = a.ObjectResponse["data"]["token"].Value(); + } + return authDict[login]; + } + + + + static bool bInitialized = false; + private static void init() + { + if (bInitialized) return; + if (!System.IO.Directory.Exists(TEST_DATA_FOLDER)) + throw new ArgumentOutOfRangeException($"Test data folder {TEST_DATA_FOLDER} not found, current folder is {System.AppDomain.CurrentDomain.BaseDirectory}"); + + client.DefaultRequestHeaders.Accept.Clear(); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + bInitialized = true; + } + + public static string CleanApiRoute(string route) + { + route = route.TrimStart('/'); + return API_BASE_URL + route; + } + + public async static Task GetAsync(string route, string authToken = null, string bodyJsonData = null) + { + init(); + + var requestMessage = new HttpRequestMessage(HttpMethod.Get, CleanApiRoute(route)); + if (!string.IsNullOrWhiteSpace(authToken)) + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken); + + if (!string.IsNullOrWhiteSpace(bodyJsonData)) + requestMessage.Content = new StringContent(bodyJsonData, System.Text.Encoding.UTF8, "application/json"); + + HttpResponseMessage response = await client.SendAsync(requestMessage); + var responseAsString = await response.Content.ReadAsStringAsync(); + + return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) }; + } + + + public async static Task GetTextResultAsync(string route, string authToken = null, string bodyJsonData = null) + { + init(); + + var requestMessage = new HttpRequestMessage(HttpMethod.Get, CleanApiRoute(route)); + if (!string.IsNullOrWhiteSpace(authToken)) + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken); + + if (!string.IsNullOrWhiteSpace(bodyJsonData)) + requestMessage.Content = new StringContent(bodyJsonData, System.Text.Encoding.UTF8, "application/json"); + + HttpResponseMessage response = await client.SendAsync(requestMessage); + var responseAsString = await response.Content.ReadAsStringAsync(); + + return new ApiTextResponse() { HttpResponse = response, TextResponse = responseAsString }; + } + + public static async Task DownloadFileAsync(string route, string authToken = null) + { + + init(); + var requestMessage = new HttpRequestMessage(HttpMethod.Get, CleanApiRoute(route)); + if (!string.IsNullOrWhiteSpace(authToken)) + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken); + + HttpResponseMessage response = await client.SendAsync(requestMessage); + return response; + // if (response.IsSuccessStatusCode) + // { + // return await response.Content.ReadAsByteArrayAsync(); + // } + + // return null; + } + + + public async static Task PostAsync(string route, string authToken = null, string postJson = null) + { + init(); + + var requestMessage = new HttpRequestMessage(HttpMethod.Post, CleanApiRoute(route)); + if (!string.IsNullOrWhiteSpace(authToken)) + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken); + + if (!string.IsNullOrWhiteSpace(postJson)) + requestMessage.Content = new StringContent(postJson, System.Text.Encoding.UTF8, "application/json"); + + HttpResponseMessage response = await client.SendAsync(requestMessage); + var responseAsString = await response.Content.ReadAsStringAsync(); + + return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) }; + } + + public async static Task PostFormDataAsync(string route, MultipartFormDataContent formContent, string authToken = null) + { + init(); + + var requestMessage = new HttpRequestMessage(HttpMethod.Post, CleanApiRoute(route)); + if (!string.IsNullOrWhiteSpace(authToken)) + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken); + + requestMessage.Content = formContent; + + HttpResponseMessage response = await client.SendAsync(requestMessage); + var responseAsString = await response.Content.ReadAsStringAsync(); + + return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) }; + } + + + public async static Task PutAsync(string route, string authToken = null, string putJson = null) + { + init(); + + var requestMessage = new HttpRequestMessage(HttpMethod.Put, CleanApiRoute(route)); + if (!string.IsNullOrWhiteSpace(authToken)) + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken); + + if (!string.IsNullOrWhiteSpace(putJson)) + requestMessage.Content = new StringContent(putJson, System.Text.Encoding.UTF8, "application/json"); + + HttpResponseMessage response = await client.SendAsync(requestMessage); + var responseAsString = await response.Content.ReadAsStringAsync(); + + return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) }; + } + + + public async static Task PatchAsync(string route, string authToken = null, string patchJson = null) + { + init(); + + var method = new HttpMethod("PATCH"); + + var requestMessage = new HttpRequestMessage(method, CleanApiRoute(route)); + if (!string.IsNullOrWhiteSpace(authToken)) + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken); + + if (!string.IsNullOrWhiteSpace(patchJson)) + requestMessage.Content = new StringContent(patchJson, System.Text.Encoding.UTF8, "application/json-patch+json"); + + HttpResponseMessage response = await client.SendAsync(requestMessage); + var responseAsString = await response.Content.ReadAsStringAsync(); + + return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) }; + } + + + public async static Task DeleteAsync(string route, string authToken = null) + { + init(); + + var requestMessage = new HttpRequestMessage(HttpMethod.Delete, CleanApiRoute(route)); + if (!string.IsNullOrWhiteSpace(authToken)) + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authToken); + + HttpResponseMessage response = await client.SendAsync(requestMessage); + var responseAsString = await response.Content.ReadAsStringAsync(); + + return new ApiResponse() { HttpResponse = response, ObjectResponse = Parse(responseAsString) }; + } + + + + /// + /// + /// + /// + /// + private static JObject Parse(string jsonString) + { + if (string.IsNullOrWhiteSpace(jsonString)) + { + return null; + } + return JObject.Parse(jsonString); + } + + //https://www.newtonsoft.com/json/help/html/FromObject.htm + private static string ObjectToJsonString(object o) + { + JObject j = (JObject)JToken.FromObject(o); + return j.ToString(); + } + + public static void ValidateDataReturnResponseOk(ApiResponse a) + { + var ErrorMessage = string.Empty; + var ERR = a.ObjectResponse["error"]; + if (ERR != null) + { + var ecode = ERR["code"]; + if (ecode != null) + ErrorMessage += $"CODE: {ecode} "; + + var emsg = ERR["message"]; + if (emsg != null) + ErrorMessage += $"MESSAGE: {emsg} "; + + var etarget = ERR["target"]; + if (etarget != null) + ErrorMessage += $"TARGET: {etarget} "; + } + + a.ObjectResponse["error"].Should().BeNull("because there should not be an error on an api call, error was: {0}", ErrorMessage); + a.ObjectResponse["data"].Should().NotBeNull("A result should be returned"); + } + + public static void ValidateNoErrorInResponse(ApiResponse a) + { + a.ObjectResponse["error"].Should().BeNull("There should not be an error on an api call"); + } + + public static void ValidateHTTPStatusCode(ApiResponse a, int DesiredStatusCode) + { + ((int)a.HttpResponse.StatusCode).Should().Be(DesiredStatusCode); + } + + public static void ValidateHTTPStatusCode(ApiTextResponse t, int DesiredStatusCode) + { + ((int)t.HttpResponse.StatusCode).Should().Be(DesiredStatusCode); + } + + /// + /// validate a not found response + /// + /// + public static void ValidateResponseNotFound(ApiResponse a) + { + ((int)a.HttpResponse.StatusCode).Should().Be(404); + a.ObjectResponse["error"].Should().NotBeNull("There should be an error on the api call"); + a.ObjectResponse["error"]["code"].Value().Should().Be(2010); + } + + + /// + /// validate a concurrency error + /// + /// + public static void ValidateConcurrencyError(ApiResponse a) + { + ((int)a.HttpResponse.StatusCode).Should().Be(409); + a.ObjectResponse["error"].Should().NotBeNull("There should be an error on the api call"); + a.ObjectResponse["error"]["code"].Value().Should().Be(2005); + } + + + /// + /// validate that the call violates referential integrity + /// + /// + public static void ValidateViolatesReferentialIntegrityError(ApiResponse a) + { + ((int)a.HttpResponse.StatusCode).Should().Be(400); + a.ObjectResponse["error"].Should().NotBeNull("There should be an error on the api call"); + a.ObjectResponse["error"]["code"].Value().Should().Be(2200); + } + + + + /// + /// validate a bad ModelState response + /// + /// + public static void ValidateBadModelStateResponse(ApiResponse a, string CheckFirstTargetExists = null) + { + ((int)a.HttpResponse.StatusCode).Should().Be(400); + a.ObjectResponse["error"].Should().NotBeNull("There should be an error on the api call"); + a.ObjectResponse["error"]["code"].Value().Should().Be(2200); + a.ObjectResponse["error"]["details"].Should().NotBeNull("There should be error details on the api call"); + if (!string.IsNullOrWhiteSpace(CheckFirstTargetExists)) + { + a.ObjectResponse["error"]["details"][0]["target"].Value().Should().Be(CheckFirstTargetExists); + } + } + + + + + // public enum ValidationErrorType + // { + // RequiredPropertyEmpty = 1, + // LengthExceeded = 2, + // NotUnique = 3, + // StartDateMustComeBeforeEndDate = 4, + // InvalidValue = 5 + + // } + + /// + /// assert contains validation target and error code + /// + /// + /// + /// + public static void ShouldContainValidationError(ApiResponse a, string target, string error, string message = null) + { + a.ObjectResponse["error"]["details"].Should().NotBeNull("There should be Details on the api call"); + if (message != null) + { + a.ObjectResponse["error"]["details"].Should().Contain( + m => m["target"].Value() == target && + m["error"].Value() == error && + m["message"].Value() == message); + } + else + { + a.ObjectResponse["error"]["details"].Should().Contain(m => m["target"].Value() == target && m["error"].Value() == error); + } + } + + + /// + /// validate server exception response + /// + /// + public static void ValidateServerExceptionResponse(ApiResponse a) + { + ((int)a.HttpResponse.StatusCode).Should().Be(500); + a.ObjectResponse["error"].Should().NotBeNull("There should be an error on the api call"); + a.ObjectResponse["error"]["code"].Value().Should().Be(2002); + } + + + /// + /// Validate an expected api error code and http code response + /// + /// + /// + /// + public static void ValidateErrorCodeResponse(ApiResponse a, int apiErrorCode, int httpStatusCode) + { + ((int)a.HttpResponse.StatusCode).Should().Be(httpStatusCode); + a.ObjectResponse["error"].Should().NotBeNull("There should be an error on the api call"); + a.ObjectResponse["error"]["code"].Value().Should().Be(apiErrorCode); + } + + + + + }//eoc +}//eons