219 lines
10 KiB
C#
219 lines
10 KiB
C#
using System;
|
|
using Xunit;
|
|
using Newtonsoft.Json.Linq;
|
|
using FluentAssertions;
|
|
|
|
namespace raven_integration
|
|
{
|
|
|
|
/*
|
|
DataList tests — three layers:
|
|
|
|
1. SavedFilterCrud (LIVE — runs now)
|
|
Exercises POST/GET/PUT/DELETE on /data-list-filter plus the /list endpoint.
|
|
Uses WorkOrderDataList as the list key since work orders always have seeded data.
|
|
|
|
2. FilterAndSort stubs (SKIPPED — need real payloads from browser)
|
|
To fill these in:
|
|
a) Open the AyaNova UI, navigate to the Work Orders grid
|
|
b) Apply a filter (e.g. "Notes contains fox"), open browser DevTools -> Network
|
|
c) Find the POST to /data-list — copy the full request body
|
|
d) Use that body as the starting point for the test payload here
|
|
See Util.BuildDataListRequestEx and Util.BuildSimpleFilterDataListViewColumn for helpers.
|
|
|
|
3. Rights stub (SKIPPED — needs a user with no list rights)
|
|
Verify that a user without the WorkOrder list role gets 403.
|
|
|
|
------
|
|
Date filter note: Server accounts for user's time zone offset when filtering by
|
|
date range. All seeded users share the same tz offset (TIME_ZONE_ADJUSTMENT in util.cs).
|
|
Use ToOffsetAdjustedUniversalTime() extension when building date filter values.
|
|
*/
|
|
|
|
public class DataListOperations
|
|
{
|
|
|
|
// -----------------------------------------------------------------------
|
|
// 1. SAVED FILTER CRUD
|
|
// -----------------------------------------------------------------------
|
|
|
|
/// <summary>
|
|
/// Create, retrieve, list, update, and delete a saved filter.
|
|
/// Also verifies public/private visibility rules:
|
|
/// - a public filter IS visible to other users
|
|
/// - a private filter is NOT visible to other users
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task SavedFilterCRUD()
|
|
{
|
|
var token = await Util.GetTokenAsync("BizAdmin");
|
|
const string ListKey = "WorkOrderDataList";
|
|
|
|
// CREATE a public saved filter
|
|
// UserId=0 — the server sets the actual user id from the auth token
|
|
var filterName = Util.Uniquify("Test Saved Filter");
|
|
var payload = $$"""
|
|
{"id":0,"concurrency":0,"userId":0,"name":"{{filterName}}","public":true,"defaultFilter":false,"listKey":"{{ListKey}}","filter":null}
|
|
""";
|
|
|
|
ApiResponse a = await Util.PostAsync("data-list-filter", token, payload);
|
|
Util.ValidateDataReturnResponseOk(a);
|
|
long Id = a.ObjectResponse["data"]["id"].Value<long>();
|
|
Id.Should().BeGreaterThan(0);
|
|
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(filterName);
|
|
a.ObjectResponse["data"]["public"].Value<bool>().Should().BeTrue();
|
|
|
|
// GET by id
|
|
a = await Util.GetAsync($"data-list-filter/{Id}", token);
|
|
Util.ValidateDataReturnResponseOk(a);
|
|
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(filterName);
|
|
a.ObjectResponse["data"]["listKey"].Value<string>().Should().Be(ListKey);
|
|
var concurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
|
|
|
// A different user should see a PUBLIC filter
|
|
var otherToken = await Util.GetTokenAsync("SubContractorRestricted");
|
|
a = await Util.GetAsync($"data-list-filter/{Id}", otherToken);
|
|
Util.ValidateDataReturnResponseOk(a);
|
|
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(filterName);
|
|
|
|
// LIST — filter should appear in the list for its key
|
|
a = await Util.GetAsync($"data-list-filter/list?ListKey={ListKey}", token);
|
|
Util.ValidateDataReturnResponseOk(a);
|
|
((JArray)a.ObjectResponse["data"]).Should().Contain(
|
|
z => z["id"].Value<long>() == Id,
|
|
"the saved filter should appear in the list for its key");
|
|
|
|
// PUT — make it private and rename
|
|
var updatedName = Util.Uniquify("Updated Saved Filter");
|
|
var putPayload = $$"""
|
|
{"id":{{Id}},"concurrency":{{concurrency}},"userId":0,"name":"{{updatedName}}","public":false,"defaultFilter":false,"listKey":"{{ListKey}}","filter":null}
|
|
""";
|
|
a = await Util.PutAsync("data-list-filter", token, putPayload);
|
|
Util.ValidateHTTPStatusCode(a, 200);
|
|
|
|
// Verify the update
|
|
a = await Util.GetAsync($"data-list-filter/{Id}", token);
|
|
Util.ValidateDataReturnResponseOk(a);
|
|
a.ObjectResponse["data"]["name"].Value<string>().Should().Be(updatedName);
|
|
a.ObjectResponse["data"]["public"].Value<bool>().Should().BeFalse();
|
|
var newConcurrency = a.ObjectResponse["data"]["concurrency"].Value<uint>();
|
|
newConcurrency.Should().NotBe(concurrency, "concurrency should have been bumped");
|
|
|
|
// The other user should NO LONGER see the now-private filter
|
|
a = await Util.GetAsync($"data-list-filter/{Id}", otherToken);
|
|
Util.ValidateResponseNotFound(a);
|
|
|
|
// DELETE
|
|
a = await Util.DeleteAsync($"data-list-filter/{Id}", token);
|
|
Util.ValidateHTTPStatusCode(a, 204);
|
|
|
|
// Confirm deleted
|
|
a = await Util.GetAsync($"data-list-filter/{Id}", token);
|
|
Util.ValidateResponseNotFound(a);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// A saved filter with DefaultFilter=true must be rejected — only the server
|
|
/// can create default filters internally.
|
|
/// </summary>
|
|
[Fact]
|
|
public async Task SavedFilter_DefaultFilterCannotBeCreatedByClient()
|
|
{
|
|
var token = await Util.GetTokenAsync("BizAdmin");
|
|
var payload = """
|
|
{"id":0,"concurrency":0,"userId":0,"name":"Should Fail","public":false,"defaultFilter":true,"listKey":"WorkOrderDataList","filter":null}
|
|
""";
|
|
ApiResponse a = await Util.PostAsync("data-list-filter", token, payload);
|
|
Util.ValidateHTTPStatusCode(a, 400);
|
|
a.ObjectResponse["error"].Should().NotBeNull();
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// 2. FILTER AND SORT — stubs awaiting real payloads from the browser
|
|
// -----------------------------------------------------------------------
|
|
//
|
|
// How to capture a real payload:
|
|
// 1. Run the server locally (dotnet run from raven/server/AyaNova/)
|
|
// 2. Open the UI, navigate to Work Orders grid
|
|
// 3. Apply a filter through the UI (e.g. Notes "contains" something)
|
|
// 4. DevTools > Network — find the POST to /api/v8/data-list
|
|
// 5. Copy the full request body JSON
|
|
// 6. Use Util.BuildDataListRequestEx() or paste directly as a raw string
|
|
//
|
|
// Field keys for WorkOrderDataList (see WorkOrderDataList.cs in server):
|
|
// "WorkOrderSerialNumber" integer (is the row id field)
|
|
// "Customer" string
|
|
// "WorkOrderServiceDate" datetime
|
|
// "WorkOrderCloseByDate" datetime
|
|
// "WorkOrderStatus" pick list / string
|
|
// "Project" string
|
|
// "WorkOrderAge" integer
|
|
//
|
|
// Operator constants are on Util: OpContains, OpStartsWith, OpEndsWith,
|
|
// OpEquality, OpNotEqual, OpGreaterThan, OpLessThan, etc.
|
|
//
|
|
// Sort direction string: "+" ascending, "-" descending
|
|
//
|
|
// NOTE on date filters: use DateTime.Now.ToOffsetAdjustedUniversalTime()
|
|
// (extension in util.cs) to avoid DST/timezone mismatch failures.
|
|
// -----------------------------------------------------------------------
|
|
|
|
[Fact(Skip = "TODO: Capture real list payload from browser — see comment above")]
|
|
public void WorkOrderList_StringContainsFilterWorks()
|
|
{
|
|
// Example shape once confirmed via browser:
|
|
//
|
|
// var token = await Util.GetTokenAsync("BizAdmin");
|
|
// dynamic dListView = new JArray();
|
|
// dListView.Add(Util.BuildSimpleFilterDataListViewColumn("Customer", Util.OpContains, "Acme"));
|
|
// string body = Util.BuildDataListRequestEx(dListView, limit: 50, offset: 0, dataListKey: "WorkOrderDataList");
|
|
// ApiResponse a = await Util.PostAsync("data-list", token, body);
|
|
// Util.ValidateDataReturnResponseOk(a);
|
|
// var rows = (JArray)a.ObjectResponse["data"];
|
|
// rows.Should().NotBeEmpty();
|
|
// // each row should have "Acme" somewhere in the Customer column value
|
|
}
|
|
|
|
[Fact(Skip = "TODO: Capture real list payload from browser")]
|
|
public void WorkOrderList_StringStartsWithFilterWorks() { }
|
|
|
|
[Fact(Skip = "TODO: Capture real list payload from browser")]
|
|
public void WorkOrderList_StringEqualsFilterWorks() { }
|
|
|
|
[Fact(Skip = "TODO: Capture real list payload from browser")]
|
|
public void WorkOrderList_StringNotEqualFilterWorks() { }
|
|
|
|
[Fact(Skip = "TODO: Capture real list payload from browser")]
|
|
public void WorkOrderList_DateRangeFilterWorks() { }
|
|
|
|
[Fact(Skip = "TODO: Capture real list payload from browser")]
|
|
public void WorkOrderList_NullFieldFilterWorks() { }
|
|
|
|
[Fact(Skip = "TODO: Capture real list payload from browser")]
|
|
public void WorkOrderList_MultiConditionAndFilterWorks() { }
|
|
|
|
[Fact(Skip = "TODO: Capture real list payload from browser")]
|
|
public void WorkOrderList_SortAscendingWorks() { }
|
|
|
|
[Fact(Skip = "TODO: Capture real list payload from browser")]
|
|
public void WorkOrderList_SortDescendingWorks() { }
|
|
|
|
[Fact(Skip = "TODO: Capture real list payload from browser")]
|
|
public void WorkOrderList_PaginationOffsetAndLimitWork() { }
|
|
|
|
[Fact(Skip = "TODO: Capture real list payload from browser")]
|
|
public void WorkOrderList_ReturnFormatMatchesExpectedShape() { }
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// 3. RIGHTS
|
|
// -----------------------------------------------------------------------
|
|
|
|
[Fact(Skip = "TODO: Identify a seeded user with no WorkOrder list rights and use their login")]
|
|
public void WorkOrderList_UserWithoutListRightsGets403() { }
|
|
|
|
}//eoc
|
|
}//eons
|