diff --git a/.github/AL-Go-Settings.json b/.github/AL-Go-Settings.json
index d8828d5a876..9e192b255ea 100644
--- a/.github/AL-Go-Settings.json
+++ b/.github/AL-Go-Settings.json
@@ -15,6 +15,7 @@
"repoVersion": "30.0",
"memoryLimit": "16G",
"numberOfTenantsForTesting": 4,
+ "enableCleanTestCodeunitExecution": true,
"maxTestAppReruns": 2,
"failOn": "newWarning",
"workspaceCompilation": {
diff --git a/src/Apps/W1/ExpenseAgent/app/src/Travel Request/Codeunits/TravelRequestApproval.Codeunit.al b/src/Apps/W1/ExpenseAgent/app/src/Travel Request/Codeunits/TravelRequestApproval.Codeunit.al
index b8ee5762dc7..8b290df2331 100644
--- a/src/Apps/W1/ExpenseAgent/app/src/Travel Request/Codeunits/TravelRequestApproval.Codeunit.al
+++ b/src/Apps/W1/ExpenseAgent/app/src/Travel Request/Codeunits/TravelRequestApproval.Codeunit.al
@@ -6,7 +6,6 @@ namespace Microsoft.ExpenseAgent;
using Microsoft.Finance.SpendRequest;
using System.Telemetry;
-using System.Text;
codeunit 7133 "Travel Request Approval"
{
@@ -318,13 +317,17 @@ codeunit 7133 "Travel Request Approval"
var
ExpenseApprovalSetup: Record "Expense Approval Setup";
ExpenseAgentSetup: Record "Expense Agent Setup";
- SelectionFilterManagement: Codeunit SelectionFilterManagement;
- RecRef: RecordRef;
+ AssignedFilter: TextBuilder;
begin
ExpenseApprovalSetup.SetCurrentKey("Approver No.");
ExpenseApprovalSetup.SetRange("Approver No.", ApproverExpenseUserNo);
- RecRef.GetTable(ExpenseApprovalSetup);
- RequestedForFilter := SelectionFilterManagement.GetSelectionFilter(RecRef, ExpenseApprovalSetup.FieldNo("Expense User No."));
+ ExpenseApprovalSetup.SetLoadFields("Expense User No.");
+ // Ranges between setup rows could include expense users without any approval setup.
+ if ExpenseApprovalSetup.FindSet() then
+ repeat
+ AppendSubmitterFilter(AssignedFilter, ExpenseApprovalSetup."Expense User No.");
+ until ExpenseApprovalSetup.Next() = 0;
+ RequestedForFilter := AssignedFilter.ToText();
ExpenseAgentSetup.GetRecordOnce();
if ExpenseAgentSetup."Default Approver No." = ApproverExpenseUserNo then
@@ -337,7 +340,6 @@ codeunit 7133 "Travel Request Approval"
local procedure AppendDefaultSubmitters(var RequestedForFilter: Text)
var
ExpenseUser: Record "Expense User";
- SelectionFilterManagement: Codeunit SelectionFilterManagement;
DefaultFilter: TextBuilder;
begin
if RequestedForFilter <> '' then
@@ -348,14 +350,23 @@ codeunit 7133 "Travel Request Approval"
ExpenseUser.SetLoadFields("No.");
if ExpenseUser.FindSet() then
repeat
- if DefaultFilter.Length > 0 then
- DefaultFilter.Append('|');
- DefaultFilter.Append(SelectionFilterManagement.AddQuotes(ExpenseUser."No."));
+ AppendSubmitterFilter(DefaultFilter, ExpenseUser."No.");
until ExpenseUser.Next() = 0;
RequestedForFilter := DefaultFilter.ToText();
end;
+ local procedure AppendSubmitterFilter(var SubmitterFilter: TextBuilder; ExpenseUserNo: Code[20])
+ var
+ ExpenseUserFilter: Record "Expense User";
+ begin
+ // Serialize an exact value so filter operators in user numbers remain literal.
+ ExpenseUserFilter.SetRange("No.", ExpenseUserNo);
+ if SubmitterFilter.Length > 0 then
+ SubmitterFilter.Append('|');
+ SubmitterFilter.Append(ExpenseUserFilter.GetFilter("No."));
+ end;
+
var
FeatureTelemetry: Codeunit "Feature Telemetry";
ExpenseActivityLogMgt: Codeunit "Expense Activity Log Mgt.";
diff --git a/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseActivityLogAPITest.Codeunit.al b/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseActivityLogAPITest.Codeunit.al
index 90781c7634d..f0ec4e76f55 100644
--- a/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseActivityLogAPITest.Codeunit.al
+++ b/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseActivityLogAPITest.Codeunit.al
@@ -22,7 +22,6 @@ codeunit 148343 "Expense Activity Log API Test"
LibraryGraphMgt: Codeunit "Library - Graph Mgt";
LibraryTestInitialize: Codeunit "Library - Test Initialize";
LibrarySetupStorage: Codeunit "Library - Setup Storage";
- APITestAuthHelper: Codeunit "Expense API Test Auth Helper";
IsInitialized: Boolean;
ServiceNameTok: Label 'expenseActivityLogEntries', Locked = true;
ExpenseReportsServiceNameTok: Label 'expenseReports', Locked = true;
@@ -476,11 +475,14 @@ codeunit 148343 "Expense Activity Log API Test"
RequestBody.WriteTo(RequestText);
ReportURL := LibraryGraphMgt.CreateTargetURLWithSubpage(
Format(SubmitterExpenseUser.SystemId), Page::"Expense Users API", ExpenseUsersServiceNameTok, ExpenseReportsServiceNameTok);
- ReportURL += '(' + LibraryGraphMgt.StripBrackets(Format(ExpenseReportHeader.SystemId)) + ')';
+ ReportURL := LibraryGraphMgt.AppendPathToTargetURL(
+ ReportURL, '(' + LibraryGraphMgt.StripBrackets(Format(ExpenseReportHeader.SystemId)) + ')');
Commit();
// [WHEN] The standard submission action is requested through the submitting user.
- LibraryGraphMgt.PostToWebServiceAndCheckResponseCode(ReportURL + '/' + SubmitWithCommentActionTok, RequestText, ResponseText, 200);
+ Clear(ResponseText);
+ LibraryGraphMgt.PostToWebServiceAndCheckResponseCode(
+ LibraryGraphMgt.AppendPathToTargetURL(ReportURL, '/' + SubmitWithCommentActionTok), RequestText, ResponseText, 200);
// [THEN] Exactly one structured snapshot is visible without a new callback or capability.
ExpenseActivityLogEntry.SetRange("Subject System ID", ExpenseReportHeader.SystemId);
@@ -488,7 +490,8 @@ codeunit 148343 "Expense Activity Log API Test"
Assert.RecordCount(ExpenseActivityLogEntry, 1);
ExpenseActivityLogEntry.FindFirst();
Clear(ResponseText);
- LibraryGraphMgt.GetFromWebServiceAndCheckResponseCode(ResponseText, ReportURL + '/' + ServiceNameTok, 200);
+ LibraryGraphMgt.GetFromWebServiceAndCheckResponseCode(
+ ResponseText, LibraryGraphMgt.AppendPathToTargetURL(ReportURL, '/' + ServiceNameTok), 200);
VerifyPolicyCategoryCode(ResponseText, ExpenseCategory.Code);
ResponseText := LowerCase(ResponseText);
Assert.AreNotEqual(0, StrPos(ResponseText, '"eventtype":"policyevaluated"'), 'The event type must identify the policy snapshot.');
@@ -502,7 +505,8 @@ codeunit 148343 "Expense Activity Log API Test"
HistoryURL := LibraryGraphMgt.CreateTargetURLWithSubpage(
Format(SubmitterExpenseUser.SystemId), Page::"Expense Users API", ExpenseUsersServiceNameTok, ServiceNameTok);
Clear(ResponseText);
- LibraryGraphMgt.GetFromWebServiceAndCheckResponseCode(ResponseText, HistoryURL + '?$filter=historyActorRole eq ''Submitter''', 200);
+ LibraryGraphMgt.GetFromWebServiceAndCheckResponseCode(
+ ResponseText, LibraryGraphMgt.AppendQueryParameterToTargetURL(HistoryURL, '$filter=historyActorRole eq ''Submitter'''), 200);
// [THEN] The real submitter sees the agent snapshot in the complete timeline.
Assert.AreNotEqual(0, StrPos(LowerCase(ResponseText), LowerCase(LibraryGraphMgt.StripBrackets(Format(ExpenseActivityLogEntry.SystemId)))), 'The submitter timeline must contain the snapshot.');
@@ -511,7 +515,8 @@ codeunit 148343 "Expense Activity Log API Test"
HistoryURL := LibraryGraphMgt.CreateTargetURLWithSubpage(
Format(ApproverExpenseUser.SystemId), Page::"Expense Users API", ExpenseUsersServiceNameTok, ServiceNameTok);
Clear(ResponseText);
- LibraryGraphMgt.GetFromWebServiceAndCheckResponseCode(ResponseText, HistoryURL + '?$filter=historyActorRole eq ''Approver''', 200);
+ LibraryGraphMgt.GetFromWebServiceAndCheckResponseCode(
+ ResponseText, LibraryGraphMgt.AppendQueryParameterToTargetURL(HistoryURL, '$filter=historyActorRole eq ''Approver'''), 200);
// [THEN] The agent event must not grant approver participation.
Assert.AreEqual(0, StrPos(LowerCase(ResponseText), LowerCase(LibraryGraphMgt.StripBrackets(Format(ExpenseReportHeader.SystemId)))), 'The agent snapshot must not grant approver history access.');
@@ -803,7 +808,9 @@ codeunit 148343 "Expense Activity Log API Test"
if IsInitialized then
exit;
- BindSubscription(APITestAuthHelper);
+ LibraryGraphMgt.SetAuthenticationProvider(
+ Enum::"API Test Authentication"::"Microsoft Test Environment");
+
LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Expense Activity Log API Test");
LibraryERMCountryData.UpdateGeneralLedgerSetup();
if not ExpenseAgentSetup.Get() then begin
diff --git a/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseAgentSetupAPITest.Codeunit.al b/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseAgentSetupAPITest.Codeunit.al
index 50bb3595b39..c5201316389 100644
--- a/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseAgentSetupAPITest.Codeunit.al
+++ b/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseAgentSetupAPITest.Codeunit.al
@@ -64,6 +64,9 @@ codeunit 148333 "Expense Agent Setup API Test"
if IsInitialized then
exit;
+ LibraryGraphMgt.SetAuthenticationProvider(
+ Enum::"API Test Authentication"::"Microsoft Test Environment");
+
LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Expense Agent Setup API Test");
LibraryExpense.SetupNumberSeriesInExpenseMgmt();
IsInitialized := true;
diff --git a/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseCapabilitiesAPITest.Codeunit.al b/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseCapabilitiesAPITest.Codeunit.al
index 4436c873d54..6dceff107b7 100644
--- a/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseCapabilitiesAPITest.Codeunit.al
+++ b/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseCapabilitiesAPITest.Codeunit.al
@@ -18,7 +18,6 @@ codeunit 148318 "Expense Capabilities API Test"
LibraryExpenseAgent: Codeunit "Library - Expense Agent";
LibraryGraphMgt: Codeunit "Library - Graph Mgt";
LibraryTestInitialize: Codeunit "Library - Test Initialize";
- APITestAuthHelper: Codeunit "Expense API Test Auth Helper";
IsInitialized: Boolean;
ServiceNameTok: Label 'expenseCapabilities', Locked = true;
ActivityLogCapabilityNameTok: Label 'activityLog', Locked = true;
@@ -104,8 +103,6 @@ codeunit 148318 "Expense Capabilities API Test"
TargetURL: Text;
ResponseText: Text;
begin
- // Excluded in BCApps CI until authenticated OData execution in a dedicated test company
- // with disabled isolation is available; then remove this method's DisabledTest.json entry.
// [SCENARIO] Travel requests are advertised when the supporting APIs are installed.
Initialize();
@@ -248,7 +245,9 @@ codeunit 148318 "Expense Capabilities API Test"
if IsInitialized then
exit;
- BindSubscription(APITestAuthHelper);
+ LibraryGraphMgt.SetAuthenticationProvider(
+ Enum::"API Test Authentication"::"Microsoft Test Environment");
+
LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Expense Capabilities API Test");
IsInitialized := true;
Commit();
diff --git a/src/Apps/W1/ExpenseAgent/test/src/API/ExpensePerDiemLocationsTest.Codeunit.al b/src/Apps/W1/ExpenseAgent/test/src/API/ExpensePerDiemLocationsTest.Codeunit.al
index 40fdf025b82..88d251c4260 100644
--- a/src/Apps/W1/ExpenseAgent/test/src/API/ExpensePerDiemLocationsTest.Codeunit.al
+++ b/src/Apps/W1/ExpenseAgent/test/src/API/ExpensePerDiemLocationsTest.Codeunit.al
@@ -17,7 +17,6 @@ codeunit 148349 "Expense PerDiem Locations Test"
LibraryUtility: Codeunit "Library - Utility";
LibraryGraphMgt: Codeunit "Library - Graph Mgt";
LibraryTestInitialize: Codeunit "Library - Test Initialize";
- APITestAuthHelper: Codeunit "Expense API Test Auth Helper";
IsInitialized: Boolean;
[Test]
@@ -82,11 +81,14 @@ codeunit 148349 "Expense PerDiem Locations Test"
local procedure Initialize()
begin
+ LibraryGraphMgt.SetLicenseSafeWorkDate();
LibraryTestInitialize.OnTestInitialize(Codeunit::"Expense PerDiem Locations Test");
if IsInitialized then
exit;
- BindSubscription(APITestAuthHelper);
+ LibraryGraphMgt.SetAuthenticationProvider(
+ Enum::"API Test Authentication"::"Microsoft Test Environment");
+
LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Expense PerDiem Locations Test");
IsInitialized := true;
Commit();
diff --git a/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseProjectsAPITest.Codeunit.al b/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseProjectsAPITest.Codeunit.al
index 45ed9ae6d25..63e7903018f 100644
--- a/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseProjectsAPITest.Codeunit.al
+++ b/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseProjectsAPITest.Codeunit.al
@@ -24,7 +24,6 @@ codeunit 148331 "Expense Projects API Test"
LibraryHumanResource: Codeunit "Library - Human Resource";
LibraryGraphMgt: Codeunit "Library - Graph Mgt";
LibraryTestInitialize: Codeunit "Library - Test Initialize";
- APITestAuthHelper: Codeunit "Expense API Test Auth Helper";
IsInitialized: Boolean;
ExpenseUsersServiceNameTok: Label 'expenseUsers', Locked = true;
ProjectsServiceNameTok: Label 'expenseProjects', Locked = true;
@@ -723,7 +722,9 @@ codeunit 148331 "Expense Projects API Test"
if IsInitialized then
exit;
- BindSubscription(APITestAuthHelper);
+ LibraryGraphMgt.SetAuthenticationProvider(
+ Enum::"API Test Authentication"::"Microsoft Test Environment");
+
LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Expense Projects API Test");
if not ExpenseAgentSetup.Get() then begin
ExpenseAgentSetup.Init();
diff --git a/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseUsersAPITest.Codeunit.al b/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseUsersAPITest.Codeunit.al
index f80c8eb6e80..1837a52d566 100644
--- a/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseUsersAPITest.Codeunit.al
+++ b/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseUsersAPITest.Codeunit.al
@@ -21,7 +21,6 @@ codeunit 148315 "Expense Users API Test"
LibraryUtility: Codeunit "Library - Utility";
LibraryGraphMgt: Codeunit "Library - Graph Mgt";
LibraryTestInitialize: Codeunit "Library - Test Initialize";
- APITestAuthHelper: Codeunit "Expense API Test Auth Helper";
IsInitialized: Boolean;
ServiceNameTok: Label 'expenseUsers', Locked = true;
EmployeesServiceNameTok: Label 'employees', Locked = true;
@@ -125,7 +124,9 @@ codeunit 148315 "Expense Users API Test"
if IsInitialized then
exit;
- BindSubscription(APITestAuthHelper);
+ LibraryGraphMgt.SetAuthenticationProvider(
+ Enum::"API Test Authentication"::"Microsoft Test Environment");
+
LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Expense Users API Test");
if not ExpenseAgentSetup.Get() then begin
ExpenseAgentSetup.Init();
diff --git a/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseVATSpecAPITest.Codeunit.al b/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseVATSpecAPITest.Codeunit.al
index 7c30f7723fa..a5bfe729b28 100644
--- a/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseVATSpecAPITest.Codeunit.al
+++ b/src/Apps/W1/ExpenseAgent/test/src/API/ExpenseVATSpecAPITest.Codeunit.al
@@ -11,6 +11,7 @@ codeunit 148348 "Expense VAT Spec. API Test"
{
Subtype = Test;
TestType = IntegrationTest;
+ RequiredTestIsolation = Disabled;
TestPermissions = Disabled;
var
@@ -19,7 +20,6 @@ codeunit 148348 "Expense VAT Spec. API Test"
LibraryERM: Codeunit "Library - ERM";
LibraryGraphMgt: Codeunit "Library - Graph Mgt";
LibraryTestInitialize: Codeunit "Library - Test Initialize";
- APITestAuthHelper: Codeunit "Expense API Test Auth Helper";
IsInitialized: Boolean;
ServiceNameTok: Label 'expenseVATSpecifications', Locked = true;
@@ -89,7 +89,9 @@ codeunit 148348 "Expense VAT Spec. API Test"
if IsInitialized then
exit;
- BindSubscription(APITestAuthHelper);
+ LibraryGraphMgt.SetAuthenticationProvider(
+ Enum::"API Test Authentication"::"Microsoft Test Environment");
+
LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Expense VAT Spec. API Test");
LibraryExpense.SetupNumberSeriesInExpenseMgmt();
LibraryERM.CreateVATBusinessPostingGroup(VATBusinessPostingGroup);
diff --git a/src/Apps/W1/ExpenseAgent/test/src/API/PoliciesToEvaluateAPITest.Codeunit.al b/src/Apps/W1/ExpenseAgent/test/src/API/PoliciesToEvaluateAPITest.Codeunit.al
index ef512122f3d..a382c57ee52 100644
--- a/src/Apps/W1/ExpenseAgent/test/src/API/PoliciesToEvaluateAPITest.Codeunit.al
+++ b/src/Apps/W1/ExpenseAgent/test/src/API/PoliciesToEvaluateAPITest.Codeunit.al
@@ -18,7 +18,6 @@ codeunit 148345 "Policies To Evaluate API Test"
LibraryExpense: Codeunit "Library - Expense";
LibraryGraphMgt: Codeunit "Library - Graph Mgt";
LibraryTestInitialize: Codeunit "Library - Test Initialize";
- APITestAuthHelper: Codeunit "Expense API Test Auth Helper";
IsInitialized: Boolean;
ExpenseReportLinesServiceNameTok: Label 'expenseReportLines', Locked = true;
@@ -117,7 +116,9 @@ codeunit 148345 "Policies To Evaluate API Test"
if IsInitialized then
exit;
- BindSubscription(APITestAuthHelper);
+ LibraryGraphMgt.SetAuthenticationProvider(
+ Enum::"API Test Authentication"::"Microsoft Test Environment");
+
LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Policies To Evaluate API Test");
LibraryERMCountryData.CreateVATData();
LibraryERMCountryData.UpdateGeneralPostingSetup();
diff --git a/src/Apps/W1/ExpenseAgent/test/src/API/PolicyEvaluationsAPITest.Codeunit.al b/src/Apps/W1/ExpenseAgent/test/src/API/PolicyEvaluationsAPITest.Codeunit.al
index 97488f4294c..be07a540b99 100644
--- a/src/Apps/W1/ExpenseAgent/test/src/API/PolicyEvaluationsAPITest.Codeunit.al
+++ b/src/Apps/W1/ExpenseAgent/test/src/API/PolicyEvaluationsAPITest.Codeunit.al
@@ -18,7 +18,6 @@ codeunit 148344 "Policy Evaluations API Test"
LibraryExpense: Codeunit "Library - Expense";
LibraryGraphMgt: Codeunit "Library - Graph Mgt";
LibraryTestInitialize: Codeunit "Library - Test Initialize";
- APITestAuthHelper: Codeunit "Expense API Test Auth Helper";
IsInitialized: Boolean;
ServiceNameTok: Label 'expensePolicyEvaluations', Locked = true;
BadRequestResponseErr: Label 'Response code is 400', Locked = true;
@@ -121,7 +120,9 @@ codeunit 148344 "Policy Evaluations API Test"
if IsInitialized then
exit;
- BindSubscription(APITestAuthHelper);
+ LibraryGraphMgt.SetAuthenticationProvider(
+ Enum::"API Test Authentication"::"Microsoft Test Environment");
+
LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Policy Evaluations API Test");
LibraryERMCountryData.CreateVATData();
LibraryERMCountryData.UpdateGeneralPostingSetup();
diff --git a/src/Apps/W1/ExpenseAgent/test/src/API/TravelRequestsAPITest.Codeunit.al b/src/Apps/W1/ExpenseAgent/test/src/API/TravelRequestsAPITest.Codeunit.al
index 2134bd14296..d54e4b2515b 100644
--- a/src/Apps/W1/ExpenseAgent/test/src/API/TravelRequestsAPITest.Codeunit.al
+++ b/src/Apps/W1/ExpenseAgent/test/src/API/TravelRequestsAPITest.Codeunit.al
@@ -10,12 +10,6 @@ using Microsoft.Finance.GeneralLedger.Setup;
using Microsoft.Finance.SpendRequest;
using Microsoft.HumanResources.Employee;
-// These HTTP tests remain excluded in Expense_Agent_Tests.DisabledTest.json until the
-// authentication prerequisite provides an authenticated OData endpoint and a dedicated
-// test company with committed fixtures and disabled test isolation.
-// In-process employee filtering, traveler mapping/navigation, lifecycle, date, and scope coverage
-// in "Spend Request Test", and restrictive role coverage in "Expense Permissions Test", remain enabled.
-// Only the HTTP scenarios are excluded.
codeunit 148347 "Travel Requests API Test"
{
Subtype = Test;
@@ -31,7 +25,6 @@ codeunit 148347 "Travel Requests API Test"
LibraryHumanResource: Codeunit "Library - Human Resource";
LibraryRandom: Codeunit "Library - Random";
LibraryTestInitialize: Codeunit "Library - Test Initialize";
- APITestAuthHelper: Codeunit "Expense API Test Auth Helper";
IsInitialized: Boolean;
ExpenseUsersServiceNameTok: Label 'expenseUsers', Locked = true;
#if not CLEAN30
@@ -1291,7 +1284,9 @@ codeunit 148347 "Travel Requests API Test"
if IsInitialized then
exit;
- BindSubscription(APITestAuthHelper);
+ LibraryGraphMgt.SetAuthenticationProvider(
+ Enum::"API Test Authentication"::"Microsoft Test Environment");
+
LibraryTestInitialize.OnBeforeTestSuiteInitialize(Codeunit::"Travel Requests API Test");
if not ExpenseAgentSetup.Get() then begin
ExpenseAgentSetup.Init();
diff --git a/src/Apps/W1/ExpenseAgent/test/src/ExpensePermissionsTest.Codeunit.al b/src/Apps/W1/ExpenseAgent/test/src/ExpensePermissionsTest.Codeunit.al
index aa05633221e..89ecec0c706 100644
--- a/src/Apps/W1/ExpenseAgent/test/src/ExpensePermissionsTest.Codeunit.al
+++ b/src/Apps/W1/ExpenseAgent/test/src/ExpensePermissionsTest.Codeunit.al
@@ -32,6 +32,9 @@ codeunit 148338 "Expense Permissions Test"
AutomationPermissionSetTok: Label 'Exp. Auto Test', Locked = true;
D365BasicPermissionSetTok: Label 'D365 BASIC', Locked = true;
ExpenseAgentPermissionSetTok: Label 'Expense Agent', Locked = true;
+ DetailOnlyPermissionSetTok: Label 'Exp. Detail Test', Locked = true;
+ HeaderModifyPermissionErr: Label 'TableData %1 %2 IndirectModify', Comment = '%1 = Spend Request table ID, %2 = Spend Request table caption', Locked = true;
+ RequestMustBeOpenErr: Label 'The %1 %2 must have the status %3.', Comment = '%1 = document type description, %2 = document number, %3 = Open status';
ExpenseAgentAppIdTok: Label '66efe10c-8033-403b-a86d-77c0887178ba', Locked = true;
ExpenseMgmtAdminPermissionSetTok: Label 'Expense Mgmt. Admin', Locked = true;
SecurityPermissionSetTok: Label 'SECURITY', Locked = true;
@@ -63,15 +66,100 @@ codeunit 148338 "Expense Permissions Test"
[Test]
procedure D365BasicCanUpdateTravelRequestDetailsIndirectly()
begin
+ // [SCENARIO 646383] D365 BASIC applies successive detail amount deltas without direct header or detail writes.
+ Initialize();
VerifyTravelRequestDetailUpdateIndirectly(D365BasicPermissionSetTok);
end;
[Test]
procedure ExpenseAgentCanUpdateTravelRequestDetailsIndirectly()
begin
+ // [SCENARIO 646383] Expense Agent applies successive detail amount deltas without direct header or detail writes.
+ Initialize();
VerifyTravelRequestDetailUpdateIndirectly(ExpenseAgentPermissionSetTok);
end;
+ [Test]
+ procedure DetailAmountUpdateFailsWithoutHeaderModifyPermission()
+ var
+ SpendRequest: Record "Spend Request";
+ SpendRequestDetail: Record "Spend Request Detail";
+ TravelRequestSubform: TestPage "Travel Request Subform";
+ SpendRequestCanWrite: Boolean;
+ SpendRequestDetailCanWrite: Boolean;
+ PermissionErrorCode: Text;
+ PermissionErrorText: Text;
+ begin
+ // [SCENARIO 646383] A caller with only header read and indirect detail modify cannot update the header through an amount change.
+ Initialize();
+
+ // [GIVEN] An open request "R" with a committed detail "D" worth 10.
+ LibraryExpense.CreateSpendRequest(SpendRequest);
+ LibraryExpense.CreateSpendRequestDetail(SpendRequestDetail, SpendRequest."No.", 10);
+ VerifyTravelRequestAmounts(SpendRequest, SpendRequestDetail, 10);
+ Commit();
+ LibraryLowerPermissions.StartLoggingNAVPermissions();
+ LibraryLowerPermissions.SetExactPermissionSet(DetailOnlyPermissionSetTok);
+ SpendRequestCanWrite := SpendRequest.WritePermission();
+ SpendRequestDetailCanWrite := SpendRequestDetail.WritePermission();
+ TravelRequestSubform.OpenEdit();
+ TravelRequestSubform.GoToRecord(SpendRequestDetail);
+
+ // [WHEN] The caller changes the amount through the public page.
+ asserterror TravelRequestSubform.Amount.SetValue(20);
+ PermissionErrorCode := GetLastErrorCode();
+ PermissionErrorText := GetLastErrorText();
+ TravelRequestSubform.Close();
+ RestoreFullPermissions();
+ LibraryLowerPermissions.StopLoggingNAVPermissions();
+
+ // [THEN] The page reports the specific header modify denial and neither record changes.
+ VerifyCapturedPageError(PermissionErrorCode, PermissionErrorText,
+ StrSubstNo(HeaderModifyPermissionErr, Database::"Spend Request", SpendRequest.TableCaption()));
+ Assert.IsFalse(SpendRequestCanWrite, 'The caller must not have direct request write permission.');
+ Assert.IsFalse(SpendRequestDetailCanWrite, 'The caller must not have direct detail write permission.');
+ VerifyTravelRequestAmounts(SpendRequest, SpendRequestDetail, 10);
+ end;
+
+ [Test]
+ procedure ReleasedTravelRequestDetailAmountCannotBeUpdatedIndirectly()
+ var
+ SpendRequest: Record "Spend Request";
+ SpendRequestDetail: Record "Spend Request Detail";
+ TravelRequestSubform: TestPage "Travel Request Subform";
+ ValidationErrorCode: Text;
+ ValidationErrorText: Text;
+ begin
+ // [SCENARIO 646383] Indirect header modification does not bypass the Open status guard.
+ Initialize();
+
+ // [GIVEN] A released request "R" with a committed detail "D" worth 10.
+ LibraryExpense.CreateSpendRequest(SpendRequest);
+ LibraryExpense.CreateSpendRequestDetail(SpendRequestDetail, SpendRequest."No.", 10);
+ VerifyTravelRequestAmounts(SpendRequest, SpendRequestDetail, 10);
+ LibraryExpense.SetSpendRequestStatus(SpendRequest, SpendRequest.Status::Released);
+ Commit();
+ LibraryLowerPermissions.StartLoggingNAVPermissions();
+ LibraryLowerPermissions.SetExactPermissionSet(ExpenseAgentPermissionSetTok);
+ TravelRequestSubform.OpenEdit();
+ TravelRequestSubform.GoToRecord(SpendRequestDetail);
+
+ // [WHEN] The caller changes the amount through the public page.
+ asserterror TravelRequestSubform.Amount.SetValue(20);
+ ValidationErrorCode := GetLastErrorCode();
+ ValidationErrorText := GetLastErrorText();
+ TravelRequestSubform.Close();
+ RestoreFullPermissions();
+ LibraryLowerPermissions.StopLoggingNAVPermissions();
+
+ // [THEN] The status guard rejects the change and the released request retains both amounts.
+ VerifyCapturedPageError(ValidationErrorCode, ValidationErrorText,
+ StrSubstNo(RequestMustBeOpenErr, SpendRequest.GetDocumentTypeDescription(), SpendRequest."No.", SpendRequest.Status::Open));
+ VerifyTravelRequestAmounts(SpendRequest, SpendRequestDetail, 10);
+ SpendRequest.Get(SpendRequest."No.");
+ Assert.AreEqual(SpendRequest.Status::Released, SpendRequest.Status, 'The denied amount change must preserve the Released status.');
+ end;
+
[Test]
procedure D365BasicCanInsertActivityIndirectly()
begin
@@ -139,20 +227,29 @@ codeunit 148338 "Expense Permissions Test"
ExpenseUser: Record "Expense User";
Approver: Record "Expense User";
TravelRequestApproval: Codeunit "Travel Request Approval";
+ ExpenseUserCanRead: Boolean;
+ PermissionErrorCode: Text;
+ PermissionErrorText: Text;
begin
// [SCENARIO] An employee-only caller cannot approve requests without access to Expense User data.
Initialize();
CreateTravelRequestApprovalScenario(SpendRequest, ExpenseUser, Approver);
+ // Preserve the fixture for the post-denial checks when asserterror rolls back.
Commit();
LibraryLowerPermissions.StartLoggingNAVPermissions();
- SetCallerPermissions(EmployeeOnlyPermissionSetTok, ExpenseUser);
+ LibraryLowerPermissions.SetExactPermissionSet(EmployeeOnlyPermissionSetTok);
+ ExpenseUserCanRead := ExpenseUser.ReadPermission();
asserterror TravelRequestApproval.Approve(SpendRequest, Approver."No.");
- Assert.ExpectedErrorCode('DB:ClientReadDenied');
- Assert.ExpectedError(ExpenseUser.TableCaption());
+ // Capture the denial before permission cleanup can change the last-error state.
+ PermissionErrorCode := GetLastErrorCode();
+ PermissionErrorText := GetLastErrorText();
RestoreFullPermissions();
LibraryLowerPermissions.StopLoggingNAVPermissions();
+ Assert.AreEqual('DB:ClientReadDenied', PermissionErrorCode, 'Approval must fail because Expense User read access is denied.');
+ Assert.ExpectedMessage(ExpenseUser.TableCaption(), PermissionErrorText);
+ Assert.IsFalse(ExpenseUserCanRead, 'The caller must not have direct access to Expense User.');
SpendRequest.Get(SpendRequest."No.");
Assert.AreEqual(SpendRequest.Status::Released, SpendRequest.Status, 'A denied approval must preserve the request status.');
ExpenseReportHeader.SetRange("Spend Request No.", SpendRequest."No.");
@@ -563,8 +660,7 @@ codeunit 148338 "Expense Permissions Test"
SpendRequestDetail: Record "Spend Request Detail";
TravelRequestSubform: TestPage "Travel Request Subform";
begin
- // [SCENARIO] Editing a detail through its page can update both the line and its header total.
- Initialize();
+ // [GIVEN] An open request "R" with a detail "D" worth 10.
LibraryExpense.CreateSpendRequest(SpendRequest);
LibraryExpense.CreateSpendRequestDetail(SpendRequestDetail, SpendRequest."No.", 10);
@@ -583,10 +679,41 @@ codeunit 148338 "Expense Permissions Test"
LibraryLowerPermissions.StopLoggingNAVPermissions();
// [THEN] Both the line change and the base table's header update are persisted.
+ VerifyTravelRequestAmounts(SpendRequest, SpendRequestDetail, 20);
+
+ // [GIVEN] The same exact role still has no direct writes after the first update.
+ LibraryLowerPermissions.StartLoggingNAVPermissions();
+ LibraryLowerPermissions.SetExactPermissionSet(PermissionSetId);
+ Assert.IsFalse(SpendRequest.WritePermission(), 'The caller must not have direct request write permission.');
+ Assert.IsFalse(SpendRequestDetail.WritePermission(), 'The caller must not have direct detail write permission.');
+
+ // [WHEN] The persisted detail amount is increased again from 20 to 30.
+ Clear(TravelRequestSubform);
+ TravelRequestSubform.OpenEdit();
+ TravelRequestSubform.GoToRecord(SpendRequestDetail);
+ TravelRequestSubform.Amount.SetValue(30);
+ TravelRequestSubform.Close();
+ RestoreFullPermissions();
+ LibraryLowerPermissions.StopLoggingNAVPermissions();
+
+ // [THEN] The second update applies only its delta, not the entire new amount.
+ VerifyTravelRequestAmounts(SpendRequest, SpendRequestDetail, 30);
+ end;
+
+ local procedure VerifyTravelRequestAmounts(var SpendRequest: Record "Spend Request"; var SpendRequestDetail: Record "Spend Request Detail"; ExpectedAmount: Decimal)
+ begin
SpendRequestDetail.Get(SpendRequestDetail."Spend Request No.", SpendRequestDetail."Line No.");
SpendRequest.Get(SpendRequest."No.");
- Assert.AreEqual(20, SpendRequestDetail."Expected Amount", 'The detail amount must be updated through indirect permissions.');
- Assert.AreEqual(20, SpendRequest."Total Expected Amount (LCY)", 'The detail update must also update the header total.');
+ Assert.AreEqual(ExpectedAmount, SpendRequestDetail."Expected Amount", 'The detail amount must match the page operation.');
+ Assert.AreEqual(ExpectedAmount, SpendRequestDetail."Expected Amount (LCY)", 'The LCY detail amount must match the page operation.');
+ Assert.AreEqual(ExpectedAmount, SpendRequest."Total Expected Amount", 'The request amount must reflect the detail delta exactly once.');
+ Assert.AreEqual(ExpectedAmount, SpendRequest."Total Expected Amount (LCY)", 'The LCY request amount must reflect the detail delta exactly once.');
+ end;
+
+ local procedure VerifyCapturedPageError(ErrorCode: Text; ErrorText: Text; ExpectedErrorText: Text)
+ begin
+ Assert.AreEqual('TestValidation', ErrorCode, 'The amount field must fail through the TestPage validation wrapper.');
+ Assert.ExpectedMessage(ExpectedErrorText, ErrorText);
end;
local procedure VerifyCompanyEmailSynchronization(PermissionSetId: Code[20])
diff --git a/src/Apps/W1/ExpenseAgent/test/src/Helper/ExpenseAPITestAuthHelper.Codeunit.al b/src/Apps/W1/ExpenseAgent/test/src/Helper/ExpenseAPITestAuthHelper.Codeunit.al
deleted file mode 100644
index 4e34344be7a..00000000000
--- a/src/Apps/W1/ExpenseAgent/test/src/Helper/ExpenseAPITestAuthHelper.Codeunit.al
+++ /dev/null
@@ -1,61 +0,0 @@
-// ------------------------------------------------------------------------------------------------
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Licensed under the MIT License. See License.txt in the project root for license information.
-// ------------------------------------------------------------------------------------------------
-namespace Microsoft.Test.ExpenseAgent;
-
-using System.Azure.KeyVault;
-using System.Environment;
-using System.Integration;
-using System.Security.AccessControl;
-
-///
-/// Test-only authentication helper for the API test codeunits in this app.
-///
-codeunit 148332 "Expense API Test Auth Helper"
-{
- Access = Internal;
- EventSubscriberInstance = Manual;
-
- var
- NavServerUserPasswordKeyTok: Label 'NavServerUserPassword', Locked = true;
-
- [EventSubscriber(ObjectType::Codeunit, Codeunit::"Library - Graph Mgt", OnAfterInitializeWebRequestWithURL, '', false, false)]
- local procedure OnAfterInitializeWebRequest(var HttpWebRequestMgt: Codeunit "Http Web Request Mgt.")
- var
- Password: SecretText;
- begin
- if not ShouldInjectBasicAuth() then
- exit;
- if not TryGetPassword(Password) then
- exit;
- HttpWebRequestMgt.AddBasicAuthentication(UserId(), Password);
- end;
-
- local procedure ShouldInjectBasicAuth(): Boolean
- var
- User: Record User;
- EnvironmentInfo: Codeunit "Environment Information";
- begin
- if EnvironmentInfo.IsSaaSInfrastructure() then
- exit(false);
-
- // If the current user authenticates via Windows
- if not User.Get(UserSecurityId()) then
- exit(false);
- if User."Windows Security ID" <> '' then
- exit(false);
-
- // Inject Basic auth when using username password authentication.
- exit(true);
- end;
-
- [TryFunction]
- local procedure TryGetPassword(var Password: SecretText)
- var
- AzureKeyVault: Codeunit "Azure Key Vault";
- begin
- AzureKeyVault.GetAzureKeyVaultSecret(NavServerUserPasswordKeyTok, Password);
- end;
-}
-
diff --git a/src/Apps/W1/ExpenseAgent/test/src/Permissions/ExpDetailTest.PermissionSet.al b/src/Apps/W1/ExpenseAgent/test/src/Permissions/ExpDetailTest.PermissionSet.al
new file mode 100644
index 00000000000..9b2f1c33517
--- /dev/null
+++ b/src/Apps/W1/ExpenseAgent/test/src/Permissions/ExpDetailTest.PermissionSet.al
@@ -0,0 +1,20 @@
+// ------------------------------------------------------------------------------------------------
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+// ------------------------------------------------------------------------------------------------
+namespace Microsoft.Test.ExpenseAgent;
+
+using Microsoft.Finance.SpendRequest;
+using System.Security.AccessControl;
+
+permissionset 148303 "Exp. Detail Test"
+{
+ Access = Internal;
+ Assignable = false;
+ IncludedPermissionSets = "D365 READ";
+
+ // Keep the header read-only, while allowing the page to modify a detail indirectly.
+ Permissions = tabledata "Spend Request Detail" = m,
+ codeunit Assert = X,
+ codeunit "Library - Lower Permissions" = X;
+}
diff --git a/src/Apps/W1/ExpenseAgent/test/src/SpendRequestTest.Codeunit.al b/src/Apps/W1/ExpenseAgent/test/src/SpendRequestTest.Codeunit.al
index b18c4701d05..c93fa9338d1 100644
--- a/src/Apps/W1/ExpenseAgent/test/src/SpendRequestTest.Codeunit.al
+++ b/src/Apps/W1/ExpenseAgent/test/src/SpendRequestTest.Codeunit.al
@@ -62,6 +62,8 @@ codeunit 148339 "Spend Request Test"
UnassignedTravelRequestHiddenMsg: Label 'The approver should not see a travel request assigned to another approver.';
DefaultTravelRequestVisibleMsg: Label 'The default approver should see travel requests without an assigned approver.';
ApproverWithoutRequestsMsg: Label 'An approver without assigned travel requests should receive an empty result.';
+ ApproverFilterExpectedRequestErr: Label '%1 Requested For filter: %2. Expected request: %3.', Comment = '%1 = assertion message, %2 = Requested For filter, %3 = expected request number';
+ ApproverFilterUnexpectedRequestErr: Label '%1 Requested For filter: %2. Expected request: %3. Unauthorized request: %4.', Comment = '%1 = assertion message, %2 = Requested For filter, %3 = expected request number, %4 = unauthorized request number';
SpendReqNoSetMsg: Label 'The Spend Request No. should be assigned to the expense report line.';
HeaderSpendReqNoSetMsg: Label 'The Spend Request No. should be assigned to the expense report header.';
HeaderCloseFlagMsg: Label 'The header should store the confirmed close flag.';
@@ -1877,21 +1879,108 @@ codeunit 148339 "Spend Request Test"
procedure ApproverFilterReturnsDefaultApproverTravelRequests()
begin
// [SCENARIO] The default approver sees requests without an explicit approval assignment.
- VerifyDefaultApproverFilter('', '');
+ Initialize();
+
+ VerifyDefaultApproverFilter('', '', true);
end;
[Test]
procedure DefaultApproverFilterQuotesWildcardUserNo()
begin
// [SCENARIO] A literal wildcard user number must not expose another approver's requests.
- VerifyDefaultApproverFilter('*', 'TR-OTHER');
+ Initialize();
+
+ VerifyDefaultApproverFilter('*', 'TR-OTHER', true);
end;
[Test]
procedure DefaultApproverFilterQuotesPipeUserNo()
begin
// [SCENARIO] A pipe in a user number must not become an OR filter for other users.
- VerifyDefaultApproverFilter('TR-A|TR-B', 'TR-A');
+ Initialize();
+
+ VerifyDefaultApproverFilter('TR-A|TR-B', 'TR-A', true);
+ end;
+
+ [Test]
+ procedure ApproverFilterQuotesExplicitWildcardUserNo()
+ begin
+ // [SCENARIO] An explicitly assigned literal wildcard user must not expose another approver's requests.
+ Initialize();
+
+ VerifyDefaultApproverFilter('*', 'TR-OTHER', false);
+ end;
+
+ [Test]
+ procedure ApproverFilterQuotesExplicitPipeUserNo()
+ begin
+ // [SCENARIO] An explicitly assigned user with a pipe in its number must not expose another approver's requests.
+ Initialize();
+
+ VerifyDefaultApproverFilter('TR-A|TR-B', 'TR-A', false);
+ end;
+
+ [Test]
+ procedure ApproverFilterDoesNotIncludeUnassignedUserBetweenAssignedUsers()
+ var
+ FirstTravelRequest: Record "Spend Request";
+ LastTravelRequest: Record "Spend Request";
+ UnassignedTravelRequest: Record "Spend Request";
+ FilteredTravelRequest: Record "Spend Request";
+ FirstExpenseUser: Record "Expense User";
+ LastExpenseUser: Record "Expense User";
+ UnassignedExpenseUser: Record "Expense User";
+ AssignedApprover: Record "Expense User";
+ DefaultApprover: Record "Expense User";
+ ExpenseApprovalSetup: Record "Expense Approval Setup";
+ TravelRequestApproval: Codeunit "Travel Request Approval";
+ begin
+ // [SCENARIO] Adjacent approval setups must not expose a user between them who belongs to another default approver.
+ Initialize();
+
+ // [GIVEN] Approver "A" and default approver "B", with their user numbers outside the tested range.
+ ExpenseApprovalSetup.DeleteAll();
+ LibraryExpense.UpdateEnableAgentInAgentSetup(true);
+ CreateApprover(AssignedApprover);
+ AssignedApprover.Rename('FILTER-APP-A');
+ CreateApprover(DefaultApprover);
+ DefaultApprover.Rename('FILTER-APP-B');
+ SetDefaultApprover(DefaultApprover."No.");
+
+ // [GIVEN] Released requests for "TR-A" and "TR-C" explicitly assigned to "A".
+ CreateReleasableSpendRequest(FirstTravelRequest, FirstExpenseUser);
+ FirstExpenseUser.Rename('TR-A');
+ FirstTravelRequest.Get(FirstTravelRequest."No.");
+ LibraryExpense.SetSpendRequestStatus(FirstTravelRequest, FirstTravelRequest.Status::Released);
+ ExpenseApprovalSetup.Get(FirstExpenseUser."No.");
+ ExpenseApprovalSetup.Validate("Approver No.", AssignedApprover."No.");
+ ExpenseApprovalSetup.Modify(true);
+ CreateReleasableSpendRequest(LastTravelRequest, LastExpenseUser);
+ LastExpenseUser.Rename('TR-C');
+ LastTravelRequest.Get(LastTravelRequest."No.");
+ LibraryExpense.SetSpendRequestStatus(LastTravelRequest, LastTravelRequest.Status::Released);
+ ExpenseApprovalSetup.Get(LastExpenseUser."No.");
+ ExpenseApprovalSetup.Validate("Approver No.", AssignedApprover."No.");
+ ExpenseApprovalSetup.Modify(true);
+
+ // [GIVEN] A released request for "TR-B" with no setup, so it belongs to default approver "B".
+ CreateReleasableSpendRequest(UnassignedTravelRequest, UnassignedExpenseUser);
+ UnassignedExpenseUser.Rename('TR-B');
+ UnassignedTravelRequest.Get(UnassignedTravelRequest."No.");
+ LibraryExpense.SetSpendRequestStatus(UnassignedTravelRequest, UnassignedTravelRequest.Status::Released);
+ ExpenseApprovalSetup.Get(UnassignedExpenseUser."No.");
+ ExpenseApprovalSetup.Delete(true);
+ VerifyApproverFilterRangeGapSetup(
+ FirstTravelRequest, LastTravelRequest, UnassignedTravelRequest, AssignedApprover."No.", DefaultApprover."No.");
+
+ // [WHEN] "A"'s filter is applied to pending travel requests.
+ FilteredTravelRequest.SetRange("Document Type", FilteredTravelRequest."Document Type"::"Travel Request");
+ FilteredTravelRequest.SetRange(Status, FilteredTravelRequest.Status::Released);
+ TravelRequestApproval.ApplyApproverFilter(FilteredTravelRequest, AssignedApprover.SystemId);
+
+ // [THEN] The requests for "TR-A" and "TR-C" are visible, but the request for "TR-B" is hidden.
+ VerifyApproverFilterRangeGapResults(
+ FilteredTravelRequest, FirstTravelRequest."No.", LastTravelRequest."No.", UnassignedTravelRequest."No.");
end;
[Test]
@@ -3173,7 +3262,7 @@ codeunit 148339 "Spend Request Test"
ExpenseAgentSetup.Modify(true);
end;
- local procedure VerifyDefaultApproverFilter(DefaultExpenseUserNo: Code[20]; OtherExpenseUserNo: Code[20])
+ local procedure VerifyDefaultApproverFilter(DefaultExpenseUserNo: Code[20]; OtherExpenseUserNo: Code[20]; UseDefaultFallback: Boolean)
var
DefaultTravelRequest: Record "Spend Request";
OtherTravelRequest: Record "Spend Request";
@@ -3182,11 +3271,13 @@ codeunit 148339 "Spend Request Test"
OtherExpenseUser: Record "Expense User";
DefaultApprover: Record "Expense User";
OtherApprover: Record "Expense User";
+ ExpenseApprovalSetup: Record "Expense Approval Setup";
TravelRequestApproval: Codeunit "Travel Request Approval";
+ ExpectedApproverNo: Code[20];
begin
- Initialize();
-
- // [GIVEN] A default approver, an unassigned request, and a request assigned to another approver.
+ // [GIVEN] Default approver "A", a request for "U", and another request assigned to approver "B".
+ // Initialize deletes expense users without their OnDelete trigger, leaving approval setups behind.
+ ExpenseApprovalSetup.DeleteAll();
LibraryExpense.UpdateEnableAgentInAgentSetup(true);
CreateApprover(DefaultApprover);
SetDefaultApprover(DefaultApprover."No.");
@@ -3194,28 +3285,93 @@ codeunit 148339 "Spend Request Test"
if DefaultExpenseUserNo <> '' then begin
DefaultExpenseUser.Rename(DefaultExpenseUserNo);
DefaultTravelRequest.Get(DefaultTravelRequest."No.");
- DefaultTravelRequest.TestField("Requested For", DefaultExpenseUserNo);
+ Assert.AreEqual(DefaultExpenseUserNo, DefaultTravelRequest."Requested For", 'The request must retain the literal renamed user number.');
end;
LibraryExpense.SetSpendRequestStatus(DefaultTravelRequest, DefaultTravelRequest.Status::Released);
CreateReleasableSpendRequest(OtherTravelRequest, OtherExpenseUser);
if OtherExpenseUserNo <> '' then begin
OtherExpenseUser.Rename(OtherExpenseUserNo);
OtherTravelRequest.Get(OtherTravelRequest."No.");
- OtherTravelRequest.TestField("Requested For", OtherExpenseUserNo);
+ Assert.AreEqual(OtherExpenseUserNo, OtherTravelRequest."Requested For", 'The other request must retain the literal renamed user number.');
end;
CreateApproverForExpenseUser(OtherApprover, OtherExpenseUser);
LibraryExpense.SetSpendRequestStatus(OtherTravelRequest, OtherTravelRequest.Status::Released);
+ // [GIVEN] "U" is either explicitly assigned to "A" or has no setup and must use the default fallback.
+ // Both setting the default approver and inserting expense users create explicit assignments.
+ ExpenseApprovalSetup.Get(DefaultExpenseUser."No.");
+ Assert.AreEqual(DefaultApprover."No.", ExpenseApprovalSetup."Approver No.", 'The initial fixture must exercise the explicit-assignment path.');
+ ExpectedApproverNo := DefaultApprover."No.";
+ if UseDefaultFallback then begin
+ ExpenseApprovalSetup.Delete(true);
+ ExpectedApproverNo := '';
+ end;
+ DefaultExpenseUser.CalcFields("Approver No.");
+ Assert.AreEqual(ExpectedApproverNo, DefaultExpenseUser."Approver No.", 'The fixture must select the intended explicit-assignment or default-fallback path.');
+ ExpenseApprovalSetup.Get(OtherExpenseUser."No.");
+ Assert.AreEqual(OtherApprover."No.", ExpenseApprovalSetup."Approver No.", 'The other request must remain explicitly assigned to another approver.');
+
// [WHEN] The default approver's filter is applied to pending travel requests.
FilteredTravelRequest.SetRange("Document Type", FilteredTravelRequest."Document Type"::"Travel Request");
FilteredTravelRequest.SetRange(Status, FilteredTravelRequest.Status::Released);
TravelRequestApproval.ApplyApproverFilter(FilteredTravelRequest, DefaultApprover.SystemId);
- // [THEN] Only the literal unassigned user's request is visible, not the other approver's request.
+ // [THEN] Only "U"'s literal request is visible, not the request assigned to "B".
FilteredTravelRequest.SetRange("No.", DefaultTravelRequest."No.");
- Assert.IsFalse(FilteredTravelRequest.IsEmpty(), DefaultTravelRequestVisibleMsg);
+ Assert.IsFalse(
+ FilteredTravelRequest.IsEmpty(),
+ StrSubstNo(ApproverFilterExpectedRequestErr,
+ DefaultTravelRequestVisibleMsg, FilteredTravelRequest.GetFilter("Requested For"), DefaultTravelRequest."No."));
FilteredTravelRequest.SetRange("No.", OtherTravelRequest."No.");
- Assert.IsTrue(FilteredTravelRequest.IsEmpty(), UnassignedTravelRequestHiddenMsg);
+ Assert.IsTrue(
+ FilteredTravelRequest.IsEmpty(),
+ StrSubstNo(ApproverFilterUnexpectedRequestErr,
+ UnassignedTravelRequestHiddenMsg, FilteredTravelRequest.GetFilter("Requested For"),
+ DefaultTravelRequest."No.", OtherTravelRequest."No."));
+ end;
+
+ local procedure VerifyApproverFilterRangeGapSetup(FirstTravelRequest: Record "Spend Request"; LastTravelRequest: Record "Spend Request"; UnassignedTravelRequest: Record "Spend Request"; AssignedApproverNo: Code[20]; DefaultApproverNo: Code[20])
+ var
+ ExpenseApprovalSetup: Record "Expense Approval Setup";
+ ExpenseAgentSetup: Record "Expense Agent Setup";
+ ExpenseUser: Record "Expense User";
+ begin
+ Assert.AreEqual('TR-A', FirstTravelRequest."Requested For", 'The first request must use the lower range endpoint.');
+ Assert.AreEqual('TR-C', LastTravelRequest."Requested For", 'The last request must use the upper range endpoint.');
+ Assert.AreEqual('TR-B', UnassignedTravelRequest."Requested For", 'The unassigned request must lie between the assigned users.');
+ ExpenseApprovalSetup.Get(FirstTravelRequest."Requested For");
+ Assert.AreEqual(AssignedApproverNo, ExpenseApprovalSetup."Approver No.", 'The first user must be explicitly assigned.');
+ ExpenseApprovalSetup.Get(LastTravelRequest."Requested For");
+ Assert.AreEqual(AssignedApproverNo, ExpenseApprovalSetup."Approver No.", 'The last user must have the same explicit approver.');
+ Assert.IsFalse(ExpenseApprovalSetup.Get(UnassignedTravelRequest."Requested For"), 'The middle user must have no approval setup row.');
+ ExpenseUser.Get(UnassignedTravelRequest."Requested For");
+ ExpenseUser.CalcFields("Approver No.");
+ Assert.AreEqual('', ExpenseUser."Approver No.", 'The middle user must use the default approver fallback.');
+ ExpenseAgentSetup.Get();
+ Assert.AreEqual(DefaultApproverNo, ExpenseAgentSetup."Default Approver No.", 'The middle user must belong to the other default approver.');
+ Assert.AreNotEqual(AssignedApproverNo, DefaultApproverNo, 'The explicit and default approvers must differ.');
+ ExpenseApprovalSetup.SetRange("Expense User No.", FirstTravelRequest."Requested For", LastTravelRequest."Requested For");
+ Assert.AreEqual(2, ExpenseApprovalSetup.Count(), 'Only the two assigned setup rows may exist in the tested range.');
+ end;
+
+ local procedure VerifyApproverFilterRangeGapResults(var FilteredTravelRequest: Record "Spend Request"; FirstTravelRequestNo: Code[20]; LastTravelRequestNo: Code[20]; UnassignedTravelRequestNo: Code[20])
+ begin
+ FilteredTravelRequest.SetRange("No.", FirstTravelRequestNo);
+ Assert.IsFalse(
+ FilteredTravelRequest.IsEmpty(),
+ StrSubstNo(ApproverFilterExpectedRequestErr,
+ AssignedTravelRequestVisibleMsg, FilteredTravelRequest.GetFilter("Requested For"), FirstTravelRequestNo));
+ FilteredTravelRequest.SetRange("No.", LastTravelRequestNo);
+ Assert.IsFalse(
+ FilteredTravelRequest.IsEmpty(),
+ StrSubstNo(ApproverFilterExpectedRequestErr,
+ AssignedTravelRequestVisibleMsg, FilteredTravelRequest.GetFilter("Requested For"), LastTravelRequestNo));
+ FilteredTravelRequest.SetRange("No.", UnassignedTravelRequestNo);
+ Assert.IsTrue(
+ FilteredTravelRequest.IsEmpty(),
+ StrSubstNo(ApproverFilterUnexpectedRequestErr,
+ UnassignedTravelRequestHiddenMsg, FilteredTravelRequest.GetFilter("Requested For"),
+ FirstTravelRequestNo, UnassignedTravelRequestNo));
end;
local procedure VerifyPostedTravelRequestHistory(SpendRequest: Record "Spend Request"; ExpectedPostedExpenseReportHeader: Record "Posted Expense Report Header")
diff --git a/src/DisabledTests/Expense_Agent_Tests/Expense_Agent_Tests.DisabledTest.json b/src/DisabledTests/Expense_Agent_Tests/Expense_Agent_Tests.DisabledTest.json
index b23129476de..02c2392f10b 100644
--- a/src/DisabledTests/Expense_Agent_Tests/Expense_Agent_Tests.DisabledTest.json
+++ b/src/DisabledTests/Expense_Agent_Tests/Expense_Agent_Tests.DisabledTest.json
@@ -1,254 +1,4 @@
[
- {
- "codeunitId": 148315,
- "codeunitName": "Expense Users API Test",
- "method": "EmployeesAPICanFilterExpenseUsers"
- },
- {
- "codeunitId": 148315,
- "codeunitName": "Expense Users API Test",
- "method": "UnlinkedExpenseUserIsHiddenFromAPI"
- },
- {
- "codeunitId": 148347,
- "codeunitName": "Travel Requests API Test",
- "method": "TravelersAPIMapsEmployeeNumberToExpenseUser"
- },
- {
- "codeunitId": 148347,
- "codeunitName": "Travel Requests API Test",
- "method": "TravelersAPIRejectsEmployeeWithoutExpenseUser"
- },
- {
- "codeunitId": 148347,
- "codeunitName": "Travel Requests API Test",
- "method": "CreateExpenseReportActionRecreatesDeletedReport"
- },
- {
- "codeunitId": 148347,
- "codeunitName": "Travel Requests API Test",
- "method": "ApproveTravelRequestActionCreatesExpenseReport"
- },
- {
- "codeunitId": 148347,
- "codeunitName": "Travel Requests API Test",
- "method": "TravelRequestsAPINormalizesCurrency"
- },
- {
- "codeunitId": 148347,
- "codeunitName": "Travel Requests API Test",
- "method": "TravelRequestDetailsAPINormalizesCurrency"
- },
- {
- "codeunitId": 148347,
- "codeunitName": "Travel Requests API Test",
- "method": "TravelRequestsAPIPreservesAndUpdatesDates"
- },
- {
- "codeunitId": 148347,
- "codeunitName": "Travel Requests API Test",
- "method": "TravelRequestsAreScopedByEmployeeNumber"
- },
- {
- "codeunitId": 148347,
- "codeunitName": "Travel Requests API Test",
- "method": "ExpenseReportAPIExposesLinkedTravelRequest"
- },
- {
- "codeunitId": 148347,
- "codeunitName": "Travel Requests API Test",
- "method": "TravelRequestDetailsAPIExposesTypeAndCategory"
- },
- {
- "codeunitId": 148347,
- "codeunitName": "Travel Requests API Test",
- "method": "ApproverViewReturnsOnlyAssignedTravelRequests"
- },
- {
- "codeunitId": 148347,
- "codeunitName": "Travel Requests API Test",
- "method": "TravelRequestsAPIAllowsOwnerOnInsertAndUnchangedPatch"
- },
- {
- "codeunitId": 148347,
- "codeunitName": "Travel Requests API Test",
- "method": "TravelRequestsAPIRejectsLifecycleFieldChanges"
- },
- {
- "codeunitId": 148347,
- "codeunitName": "Travel Requests API Test",
- "method": "TravelRequestsAPIRejectsRequestedForChange"
- },
- {
- "codeunitId": 148347,
- "codeunitName": "Travel Requests API Test",
- "method": "LegacySpendRequestsAPIAllowsUnchangedOwner"
- },
- {
- "codeunitId": 148347,
- "codeunitName": "Travel Requests API Test",
- "method": "LegacySpendRequestsAPIRejectsTravelRequestOwnerChange"
- },
- {
- "codeunitId": 148318,
- "codeunitName": "Expense Capabilities API Test",
- "method": "CapabilitiesProjectsEnabledViaAPI"
- },
- {
- "codeunitId": 148318,
- "codeunitName": "Expense Capabilities API Test",
- "method": "CapabilitiesProjectsDisabledViaAPI"
- },
- {
- "codeunitId": 148318,
- "codeunitName": "Expense Capabilities API Test",
- "method": "CapabilitiesConsolidatedProjectsFollowsProjectFieldsViaAPI"
- },
- {
- "codeunitId": 148318,
- "codeunitName": "Expense Capabilities API Test",
- "method": "ActivityLogCapabilityEnabledViaAPI"
- },
- {
- "codeunitId": 148318,
- "codeunitName": "Expense Capabilities API Test",
- "method": "TravelRequestCapabilityEnabledViaAPI"
- },
- {
- "codeunitId": 148343,
- "codeunitName": "Expense Activity Log API Test",
- "method": "ActivityLogEntryIsExposedThroughReadOnlyAPI"
- },
- {
- "codeunitId": 148343,
- "codeunitName": "Expense Activity Log API Test",
- "method": "ActivityLogAPIRejectsWrites"
- },
- {
- "codeunitId": 148343,
- "codeunitName": "Expense Activity Log API Test",
- "method": "NonSnapshotActivityDoesNotExposeCurrencies"
- },
- {
- "codeunitId": 148343,
- "codeunitName": "Expense Activity Log API Test",
- "method": "UnscopedActivityAPIHidesEntries"
- },
- {
- "codeunitId": 148343,
- "codeunitName": "Expense Activity Log API Test",
- "method": "SubmitterHistoryReturnsCompleteTimelinesForSubmittedReports"
- },
- {
- "codeunitId": 148343,
- "codeunitName": "Expense Activity Log API Test",
- "method": "ApproverHistoryReturnsCompleteTimelinesForActedOnReports"
- },
- {
- "codeunitId": 148343,
- "codeunitName": "Expense Activity Log API Test",
- "method": "E2EActivityLogScenario"
- },
- {
- "codeunitId": 148333,
- "codeunitName": "Expense Agent Setup API Test",
- "method": "EmailAddressFieldIsExposedThroughAPI"
- },
- {
- "codeunitId": 148349,
- "codeunitName": "Expense PerDiem Locations Test",
- "method": "PerDiemLocationsReturnsLinkedLocationViaAPI"
- },
- {
- "codeunitId": 148349,
- "codeunitName": "Expense PerDiem Locations Test",
- "method": "PerDiemLocationsExcludesNonPerDiemCategoryViaAPI"
- },
- {
- "codeunitId": 148331,
- "codeunitName": "Expense Projects API Test",
- "method": "AssignedByPersonResponsibleReturnsWholeProjectViaAPI"
- },
- {
- "codeunitId": 148331,
- "codeunitName": "Expense Projects API Test",
- "method": "AssignedByProjectManagerReturnsWholeProjectViaAPI"
- },
- {
- "codeunitId": 148331,
- "codeunitName": "Expense Projects API Test",
- "method": "AssignedByProjectLevelResourceReturnsWholeProjectViaAPI"
- },
- {
- "codeunitId": 148331,
- "codeunitName": "Expense Projects API Test",
- "method": "AssignedByTaskLevelResourceReturnsOnlyThatTaskViaAPI"
- },
- {
- "codeunitId": 148331,
- "codeunitName": "Expense Projects API Test",
- "method": "AssignedByPlanningLineReturnsOnlyThatTaskViaAPI"
- },
- {
- "codeunitId": 148331,
- "codeunitName": "Expense Projects API Test",
- "method": "WholeProjectSignalSupersedesTaskLevelViaAPI"
- },
- {
- "codeunitId": 148331,
- "codeunitName": "Expense Projects API Test",
- "method": "OverlappingSignalsForSameResourceAreDeduplicatedViaAPI"
- },
- {
- "codeunitId": 148331,
- "codeunitName": "Expense Projects API Test",
- "method": "CompletedProjectExcludedEvenWhenAssignedViaAPI"
- },
- {
- "codeunitId": 148331,
- "codeunitName": "Expense Projects API Test",
- "method": "EmptyResourceUserMatchesNothingViaAPI"
- },
- {
- "codeunitId": 148331,
- "codeunitName": "Expense Projects API Test",
- "method": "BlankUserIdDoesNotMatchBlankProjectManagerViaAPI"
- },
- {
- "codeunitId": 148331,
- "codeunitName": "Expense Projects API Test",
- "method": "ProjectManagerMatchWithBlankResourceReturnsProjectViaAPI"
- },
- {
- "codeunitId": 148331,
- "codeunitName": "Expense Projects API Test",
- "method": "TasklessProjectExcludedViaAPI"
- },
- {
- "codeunitId": 148331,
- "codeunitName": "Expense Projects API Test",
- "method": "WholeProjectAndTaskLevelAcrossProjectsNoLeakageViaAPI"
- },
- {
- "codeunitId": 148331,
- "codeunitName": "Expense Projects API Test",
- "method": "AllProjectsVisibilityReturnsAllProjectsViaAPI"
- },
- {
- "codeunitId": 148331,
- "codeunitName": "Expense Projects API Test",
- "method": "AllProjectsVisibilityIncludesActiveStatusesAndPostingTasksViaAPI"
- },
- {
- "codeunitId": 148331,
- "codeunitName": "Expense Projects API Test",
- "method": "AssignedProjectsRequiresScopeFilterViaAPI"
- },
- {
- "codeunitId": 148331,
- "codeunitName": "Expense Projects API Test",
- "method": "AssignedProjectsBlockedWhenProjectFieldsDisabledViaAPI"
- },
{
"codeunitId": 148305,
"codeunitName": "Expense Test",
@@ -293,10 +43,5 @@
"codeunitId": 148319,
"codeunitName": "Cancel Posted Exp. Report Test",
"method": "CancelMultiLineReportGeneratesNewTransactionNoForReversedLines"
- },
- {
- "codeunitId": 148348,
- "codeunitName": "Expense VAT Spec. API Test",
- "method": "AgentVATSpecificationIsInsertedThroughAPI"
}
]
diff --git a/src/DisabledTests/_Exclude_APIV1__Tests/_Exclude_APIV1__Tests.DisabledTest.json b/src/DisabledTests/_Exclude_APIV1__Tests/_Exclude_APIV1__Tests.DisabledTest.json
index ad692000a56..a4a9415f3f1 100644
--- a/src/DisabledTests/_Exclude_APIV1__Tests/_Exclude_APIV1__Tests.DisabledTest.json
+++ b/src/DisabledTests/_Exclude_APIV1__Tests/_Exclude_APIV1__Tests.DisabledTest.json
@@ -1453,5 +1453,680 @@
"codeunitId": 139745,
"codeunitName": "APIV1 - Journal Lines E2E",
"method": "TestCreateJournalLineWithOverridenDefaultDimension"
+ },
+ {
+ "codeunitId": 139702,
+ "codeunitName": "APIV1 - Customers E2E",
+ "method": "TestGetSimpleCustomer"
+ },
+ {
+ "codeunitId": 139702,
+ "codeunitName": "APIV1 - Customers E2E",
+ "method": "TestGetCustomerWithComplexType"
+ },
+ {
+ "codeunitId": 139702,
+ "codeunitName": "APIV1 - Customers E2E",
+ "method": "TestGetCustomerWithAddressAndSpecialCharacters"
+ },
+ {
+ "codeunitId": 139702,
+ "codeunitName": "APIV1 - Customers E2E",
+ "method": "TestCreateDetailedCustomer"
+ },
+ {
+ "codeunitId": 139702,
+ "codeunitName": "APIV1 - Customers E2E",
+ "method": "TestCreateCustomerWithComplexType"
+ },
+ {
+ "codeunitId": 139702,
+ "codeunitName": "APIV1 - Customers E2E",
+ "method": "TestCreateCustomerWithTemplate"
+ },
+ {
+ "codeunitId": 139702,
+ "codeunitName": "APIV1 - Customers E2E",
+ "method": "TestDeleteCustomer"
+ },
+ {
+ "codeunitId": 139702,
+ "codeunitName": "APIV1 - Customers E2E",
+ "method": "TestModifyCustomer"
+ },
+ {
+ "codeunitId": 139702,
+ "codeunitName": "APIV1 - Customers E2E",
+ "method": "TestModifyCustomerWithComplexType"
+ },
+ {
+ "codeunitId": 139702,
+ "codeunitName": "APIV1 - Customers E2E",
+ "method": "TestRemoveComplexTypeFromCustomer"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionWithDimensionCodeOnCustomer"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionWithDimensionCodeOnVendor"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionWithDimensionCodeOnItem"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionOnCustomer"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionOnVendor"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionOnItem"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithoutDimensionOnCustomer"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithoutDimensionOnVendor"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithoutDimensionOnItem"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithMismatchingDimensionsOnCustomer"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithMismatchingDimensionsOnVendor"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithMismatchingDimensionsOnItem"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithBlockedDimensionOnCustomer"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithBlockedDimensionOnVendor"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithBlockedDimensionOnItem"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithBlockedDimensionValueOnCustomer"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithBlockedDimensionValueOnVendor"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithBlockedDimensionValueOnItem"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestDeleteDefaultDimensionOnCustomer"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestDeleteDefaultDimensionOnVendor"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestDeleteDefaultDimensionOnItem"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestGetDefaultDimensionOnCustomer"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestGetDefaultDimensionOnVendor"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestGetDefaultDimensionOnItem"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionOnCustomer"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionOnVendor"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionOnItem"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionFailsWithBlockedValueOnCustomer"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionFailsWithBlockedValueOnVendor"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionFailsWithBlockedValueOnItem"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionFailsWhenChangingDimensionCodeOnCustomer"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionFailsWhenChangingDimensionCodeOnVendor"
+ },
+ {
+ "codeunitId": 139732,
+ "codeunitName": "APIV1 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionFailsWhenChangingDimensionCodeOnItem"
+ },
+ {
+ "codeunitId": 139725,
+ "codeunitName": "APIV1 - Dimension Lines E2E",
+ "method": "TestCreateDimensionLine"
+ },
+ {
+ "codeunitId": 139725,
+ "codeunitName": "APIV1 - Dimension Lines E2E",
+ "method": "TestCreateDimensionLineFailsWithoutParentId"
+ },
+ {
+ "codeunitId": 139725,
+ "codeunitName": "APIV1 - Dimension Lines E2E",
+ "method": "TestCreateDoesntWorkWithAlreadyExistingCode"
+ },
+ {
+ "codeunitId": 139725,
+ "codeunitName": "APIV1 - Dimension Lines E2E",
+ "method": "TestGetDimensionLinesOfJournalLines"
+ },
+ {
+ "codeunitId": 139725,
+ "codeunitName": "APIV1 - Dimension Lines E2E",
+ "method": "TestGetDimensionLinesFailsWithoutFilter"
+ },
+ {
+ "codeunitId": 139725,
+ "codeunitName": "APIV1 - Dimension Lines E2E",
+ "method": "TestGetDimensionLinesFailsWithNonSupportedEntity"
+ },
+ {
+ "codeunitId": 139725,
+ "codeunitName": "APIV1 - Dimension Lines E2E",
+ "method": "TestModifyDimensionLine"
+ },
+ {
+ "codeunitId": 139725,
+ "codeunitName": "APIV1 - Dimension Lines E2E",
+ "method": "TestModifyOfDimensionCodeDoesntWork"
+ },
+ {
+ "codeunitId": 139725,
+ "codeunitName": "APIV1 - Dimension Lines E2E",
+ "method": "TestDeleteDimensionLine"
+ },
+ {
+ "codeunitId": 139739,
+ "codeunitName": "APIV1 - Item Variants E2E",
+ "method": "TestCreateItemVariantWithItemId"
+ },
+ {
+ "codeunitId": 139739,
+ "codeunitName": "APIV1 - Item Variants E2E",
+ "method": "TestCreateItemVariantWithNonExistingItemId"
+ },
+ {
+ "codeunitId": 139739,
+ "codeunitName": "APIV1 - Item Variants E2E",
+ "method": "TestCreateItemVariantWithItemNumber"
+ },
+ {
+ "codeunitId": 139739,
+ "codeunitName": "APIV1 - Item Variants E2E",
+ "method": "TestCreateItemVariantWithNonExistingItemNumber"
+ },
+ {
+ "codeunitId": 139739,
+ "codeunitName": "APIV1 - Item Variants E2E",
+ "method": "TestCreateItemVariantWithItemNumberAndItemId"
+ },
+ {
+ "codeunitId": 139739,
+ "codeunitName": "APIV1 - Item Variants E2E",
+ "method": "TestCreateItemVariantWithMismatchingItemNumberAndItemId"
+ },
+ {
+ "codeunitId": 139739,
+ "codeunitName": "APIV1 - Item Variants E2E",
+ "method": "TestCreateItemVariantWithExistingItemNumberAndCode"
+ },
+ {
+ "codeunitId": 139739,
+ "codeunitName": "APIV1 - Item Variants E2E",
+ "method": "TestGetItemVariant"
+ },
+ {
+ "codeunitId": 139739,
+ "codeunitName": "APIV1 - Item Variants E2E",
+ "method": "TestModifyItemVariant"
+ },
+ {
+ "codeunitId": 139739,
+ "codeunitName": "APIV1 - Item Variants E2E",
+ "method": "TestModifyItemVariantItemId"
+ },
+ {
+ "codeunitId": 139739,
+ "codeunitName": "APIV1 - Item Variants E2E",
+ "method": "TestModifyItemVariantItemNumber"
+ },
+ {
+ "codeunitId": 139739,
+ "codeunitName": "APIV1 - Item Variants E2E",
+ "method": "TestDeleteItemVariant"
+ },
+ {
+ "codeunitId": 139739,
+ "codeunitName": "APIV1 - Item Variants E2E",
+ "method": "TestDeleteInUse"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestCreateSimpleItem"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestCreateItemWithInventoryAmount"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestCreateItemWithCategoryId"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestCreateItemWithCategoryCode"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestCreateItemWithComplexType"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestCreateItemWithTemplate"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestCreateItemWithTemplateIgnoresFieldsSet"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestGetSimpleItem"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestSuccessfulTemplateSelection"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestSuccessfulTemplateSelectionWithSelectionCriteria"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestTempcodeCannotbeblankOrInvalidValue"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestGetItemWithCategory"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestGetItemWithComplexType"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestModifyItem"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestModifyItemInventory"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestModifyItemWithComplexType"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestRemoveComplexTypeFromItemUsingNull"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestRemoveComplexTypeFromItemUsingBlankValue"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestDeleteItem"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestPostItemWithUoMIdCreatesItemUoM"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestPatchItemWithUoMIdCreatesItemUoM"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestPatchItemWithCategoryCode"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestPatchItemWithCategoryId"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestPostItemWithUoMComplexTypeCreatesItemUoM"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestPatchItemWithUoMComplexTypeCreatesItemUoM"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestUoMSync"
+ },
+ {
+ "codeunitId": 139700,
+ "codeunitName": "APIV1 - Items E2E",
+ "method": "TestUoMSyncErrors"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestGetPictureValueCustomer"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestGetPictureMetadataCustomer"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestGetPictureValueCustomerWithoutPicture"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestGetPictureMetadataCustomerWithoutPicture"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestPictureUploadCustomer"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestPictureReplaceCustomer"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestPictureDeleteCustomer"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestGetPictureValueVendor"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestGetPictureMetadataVendor"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestGetPictureValueVendorWithoutPicture"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestGetPictureMetadataVendorWithoutPicture"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestPictureUploadVendor"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestPictureReplaceVendor"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestPictureDeleteVendor"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestGetPictureValueEmployee"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestGetPictureMetadataEmployee"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestGetPictureValueEmployeeWithoutPicture"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestGetPictureMetadataEmployeeWithoutPicture"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestPictureUploadEmployee"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestPictureReplaceEmployee"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestPictureDeleteEmployee"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestGetPictureValueItem"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestGetPictureMetadataItem"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestGetPictureValueItemWithoutPicture"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestGetPictureMetadatItemWithoutPicture"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestPictureUploadItem"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestPictureReplaceItem"
+ },
+ {
+ "codeunitId": 139742,
+ "codeunitName": "APIV1 - Pictures E2E",
+ "method": "TestPictureDeleteItem"
+ },
+ {
+ "codeunitId": 139726,
+ "codeunitName": "APIV1 - Tax Areas E2E",
+ "method": "TestVerifyIDandLastDateModified"
+ },
+ {
+ "codeunitId": 139726,
+ "codeunitName": "APIV1 - Tax Areas E2E",
+ "method": "TestGetTaxAreas"
+ },
+ {
+ "codeunitId": 139726,
+ "codeunitName": "APIV1 - Tax Areas E2E",
+ "method": "TestGetTaxArea"
+ },
+ {
+ "codeunitId": 139726,
+ "codeunitName": "APIV1 - Tax Areas E2E",
+ "method": "TestCreateTaxArea"
+ },
+ {
+ "codeunitId": 139726,
+ "codeunitName": "APIV1 - Tax Areas E2E",
+ "method": "TestModifyTaxArea"
+ },
+ {
+ "codeunitId": 139726,
+ "codeunitName": "APIV1 - Tax Areas E2E",
+ "method": "TestDeleteTaxArea"
+ },
+ {
+ "codeunitId": 139703,
+ "codeunitName": "APIV1 - Vendors E2E",
+ "method": "TestGetSimpleVendor"
+ },
+ {
+ "codeunitId": 139703,
+ "codeunitName": "APIV1 - Vendors E2E",
+ "method": "TestGetVendorWithComplexType"
+ },
+ {
+ "codeunitId": 139703,
+ "codeunitName": "APIV1 - Vendors E2E",
+ "method": "TestCreateSimpleVendor"
+ },
+ {
+ "codeunitId": 139703,
+ "codeunitName": "APIV1 - Vendors E2E",
+ "method": "TestCreateVendorWithComplexType"
+ },
+ {
+ "codeunitId": 139703,
+ "codeunitName": "APIV1 - Vendors E2E",
+ "method": "TestCreateVendorWithTemplate"
+ },
+ {
+ "codeunitId": 139703,
+ "codeunitName": "APIV1 - Vendors E2E",
+ "method": "TestModifyVendor"
+ },
+ {
+ "codeunitId": 139703,
+ "codeunitName": "APIV1 - Vendors E2E",
+ "method": "TestVendorModifyWithComplexTypes"
+ },
+ {
+ "codeunitId": 139703,
+ "codeunitName": "APIV1 - Vendors E2E",
+ "method": "TestRemoveComplexTypeFromVendor"
+ },
+ {
+ "codeunitId": 139703,
+ "codeunitName": "APIV1 - Vendors E2E",
+ "method": "TestDeleteVendor"
}
]
diff --git a/src/DisabledTests/_Exclude_APIV2__Tests/_Exclude_APIV2__Tests.DisabledTest.json b/src/DisabledTests/_Exclude_APIV2__Tests/_Exclude_APIV2__Tests.DisabledTest.json
index 2a73423912e..66e7f95e2e0 100644
--- a/src/DisabledTests/_Exclude_APIV2__Tests/_Exclude_APIV2__Tests.DisabledTest.json
+++ b/src/DisabledTests/_Exclude_APIV2__Tests/_Exclude_APIV2__Tests.DisabledTest.json
@@ -3583,5 +3583,295 @@
"codeunitId": 139928,
"method": "TestExpandPdfDocument",
"codeunitName": "APIV2 - Purch. Ret. Shpts. E2E"
+ },
+ {
+ "codeunitId": 139802,
+ "codeunitName": "APIV2 - Customers E2E",
+ "method": "TestGetSimpleCustomer"
+ },
+ {
+ "codeunitId": 139802,
+ "codeunitName": "APIV2 - Customers E2E",
+ "method": "TestGetCustomerWithAddressAndSpecialCharacters"
+ },
+ {
+ "codeunitId": 139802,
+ "codeunitName": "APIV2 - Customers E2E",
+ "method": "TestCreateDetailedCustomer"
+ },
+ {
+ "codeunitId": 139802,
+ "codeunitName": "APIV2 - Customers E2E",
+ "method": "TestCreateCustomerWithTemplate"
+ },
+ {
+ "codeunitId": 139802,
+ "codeunitName": "APIV2 - Customers E2E",
+ "method": "TestDeleteCustomer"
+ },
+ {
+ "codeunitId": 139802,
+ "codeunitName": "APIV2 - Customers E2E",
+ "method": "TestModifyCustomer"
+ },
+ {
+ "codeunitId": 139802,
+ "codeunitName": "APIV2 - Customers E2E",
+ "method": "TestModifyCustomerWithAddress"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionWithDimensionCodeOnCustomer"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionWithDimensionCodeOnVendor"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionWithDimensionCodeOnItem"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionWithDimensionCodeOnEmployee"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionOnCustomer"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionOnVendor"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionOnItem"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionOnEmployee"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithMismatchingDimensionsOnCustomer"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithMismatchingDimensionsOnVendor"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithMismatchingDimensionsOnItem"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithMismatchingDimensionsOnEmployee"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithBlockedDimensionOnCustomer"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithBlockedDimensionOnVendor"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithBlockedDimensionOnItem"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithBlockedDimensionOnEmployee"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithBlockedDimensionValueOnCustomer"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithBlockedDimensionValueOnVendor"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithBlockedDimensionValueOnItem"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestCreateDefaultDimensionFailsWithBlockedDimensionValueOnEmployee"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestDeleteDefaultDimensionOnCustomer"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestDeleteDefaultDimensionOnVendor"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestDeleteDefaultDimensionOnItem"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestDeleteDefaultDimensionOnEmployee"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestGetDefaultDimensionOnCustomer"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestGetDefaultDimensionOnVendor"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestGetDefaultDimensionOnItem"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestGetDefaultDimensionOnEmployee"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionOnCustomer"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionOnVendor"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionOnItem"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionOnEmployee"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionFailsWithBlockedValueOnCustomer"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionFailsWithBlockedValueOnVendor"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionFailsWithBlockedValueOnItem"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionFailsWithBlockedValueOnEmployee"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionFailsWhenChangingDimensionCodeOnCustomer"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionFailsWhenChangingDimensionCodeOnVendor"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionFailsWhenChangingDimensionCodeOnItem"
+ },
+ {
+ "codeunitId": 139832,
+ "codeunitName": "APIV2 - Default Dimensions E2E",
+ "method": "TestPatchDefaultDimensionFailsWhenChangingDimensionCodeOnEmployee"
+ },
+ {
+ "codeunitId": 139826,
+ "codeunitName": "APIV2 - Tax Areas E2E",
+ "method": "TestVerifyIDandLastDateModified"
+ },
+ {
+ "codeunitId": 139826,
+ "codeunitName": "APIV2 - Tax Areas E2E",
+ "method": "TestGetTaxAreas"
+ },
+ {
+ "codeunitId": 139826,
+ "codeunitName": "APIV2 - Tax Areas E2E",
+ "method": "TestGetTaxArea"
+ },
+ {
+ "codeunitId": 139826,
+ "codeunitName": "APIV2 - Tax Areas E2E",
+ "method": "TestCreateTaxArea"
+ },
+ {
+ "codeunitId": 139826,
+ "codeunitName": "APIV2 - Tax Areas E2E",
+ "method": "TestModifyTaxArea"
+ },
+ {
+ "codeunitId": 139826,
+ "codeunitName": "APIV2 - Tax Areas E2E",
+ "method": "TestDeleteTaxArea"
+ },
+ {
+ "codeunitId": 139803,
+ "codeunitName": "APIV2 - Vendors E2E",
+ "method": "TestGetSimpleVendor"
+ },
+ {
+ "codeunitId": 139803,
+ "codeunitName": "APIV2 - Vendors E2E",
+ "method": "TestCreateSimpleVendor"
+ },
+ {
+ "codeunitId": 139803,
+ "codeunitName": "APIV2 - Vendors E2E",
+ "method": "TestCreateVendorWithTemplate"
+ },
+ {
+ "codeunitId": 139803,
+ "codeunitName": "APIV2 - Vendors E2E",
+ "method": "TestModifyVendor"
+ },
+ {
+ "codeunitId": 139803,
+ "codeunitName": "APIV2 - Vendors E2E",
+ "method": "TestDeleteVendor"
}
]
diff --git a/src/Layers/W1/Tests/ERM/SpendRequestTests.Codeunit.al b/src/Layers/W1/Tests/ERM/SpendRequestTests.Codeunit.al
index 11f5d08109a..ea889fe6e72 100644
--- a/src/Layers/W1/Tests/ERM/SpendRequestTests.Codeunit.al
+++ b/src/Layers/W1/Tests/ERM/SpendRequestTests.Codeunit.al
@@ -341,6 +341,37 @@ codeunit 134242 "Spend Request Tests"
'Deleting detail should reduce header total.');
end;
+ [Test]
+ procedure DeleteDetailLineDecrementsHeaderTotalExactlyOnce()
+ var
+ SpendRequest: Record "Spend Request";
+ FirstSpendRequestDetail: Record "Spend Request Detail";
+ RemainingSpendRequestDetail: Record "Spend Request Detail";
+ begin
+ // [SCENARIO 646383] Real detail deletion applies each LCY delta exactly once without removing the other detail.
+ Initialize();
+
+ // [GIVEN] An open LCY request "R" initially has zero total and two details "D1" and "D2" worth 10 and 30.
+ CreateSpendRequest(SpendRequest);
+ VerifyDetailDeletionTotals(SpendRequest."No.", 0, 0);
+ CreateSpendRequestDetail(FirstSpendRequestDetail, SpendRequest."No.", 10);
+ CreateSpendRequestDetail(RemainingSpendRequestDetail, SpendRequest."No.", 30);
+ VerifyDetailDeletionTotals(SpendRequest."No.", 40, 2);
+
+ // [WHEN] Deleting "D1" runs the production table trigger, not a page action or a permission test.
+ FirstSpendRequestDetail.Delete(true);
+
+ // [THEN] "D2" survives and both its amount and the header total are 30.
+ RemainingSpendRequestDetail.Get(RemainingSpendRequestDetail."Spend Request No.", RemainingSpendRequestDetail."Line No.");
+ VerifyDetailDeletionTotals(SpendRequest."No.", 30, 1);
+
+ // [WHEN] The remaining detail "D2" is deleted through its production trigger.
+ RemainingSpendRequestDetail.Delete(true);
+
+ // [THEN] No details remain and the header total is exactly zero.
+ VerifyDetailDeletionTotals(SpendRequest."No.", 0, 0);
+ end;
+
[Test]
[Scope('OnPrem')]
procedure CannotInsertDetailOnReleasedRequest()
@@ -827,4 +858,21 @@ codeunit 134242 "Spend Request Tests"
// [THEN] An error is raised.
Assert.ExpectedError('must have the status ' + Format(SpendRequest.Status::Open));
end;
+
+ local procedure VerifyDetailDeletionTotals(SpendRequestNo: Code[20]; ExpectedAmount: Decimal; ExpectedLineCount: Integer)
+ var
+ SpendRequest: Record "Spend Request";
+ SpendRequestDetail: Record "Spend Request Detail";
+ begin
+ SpendRequest.Get(SpendRequestNo);
+ Assert.AreEqual(SpendRequest.Status::Open, SpendRequest.Status, 'Detail deletion must preserve the Open status.');
+ Assert.AreEqual('', SpendRequest."Currency Code", 'The request must use LCY.');
+ Assert.AreEqual(ExpectedAmount, SpendRequest."Total Expected Amount", 'The header amount must reflect each deleted detail exactly once.');
+ Assert.AreEqual(ExpectedAmount, SpendRequest."Total Expected Amount (LCY)", 'The LCY header amount must reflect each deleted detail exactly once.');
+ SpendRequestDetail.SetRange("Spend Request No.", SpendRequestNo);
+ Assert.AreEqual(ExpectedLineCount, SpendRequestDetail.Count(), 'Only the intended details must be deleted.');
+ SpendRequestDetail.CalcSums("Expected Amount", "Expected Amount (LCY)");
+ Assert.AreEqual(ExpectedAmount, SpendRequestDetail."Expected Amount", 'The remaining detail amounts must be unchanged.');
+ Assert.AreEqual(ExpectedAmount, SpendRequestDetail."Expected Amount (LCY)", 'The remaining LCY detail amounts must be unchanged.');
+ end;
}
\ No newline at end of file
diff --git a/src/Layers/W1/Tests/TestLibraries/LibraryGraphMgt.Codeunit.al b/src/Layers/W1/Tests/TestLibraries/LibraryGraphMgt.Codeunit.al
index 38b8c0f8bf8..bc37687e68c 100644
--- a/src/Layers/W1/Tests/TestLibraries/LibraryGraphMgt.Codeunit.al
+++ b/src/Layers/W1/Tests/TestLibraries/LibraryGraphMgt.Codeunit.al
@@ -31,6 +31,12 @@ codeunit 130618 "Library - Graph Mgt"
AuthenticationProviderResolved := true;
end;
+ /// Sets the work date to November 15 in the current year to stay within test-license date limits.
+ procedure SetLicenseSafeWorkDate()
+ begin
+ WorkDate := DMY2Date(15, 11, Date2DMY(Today, 3));
+ end;
+
procedure EnsureWebServiceExist(ServiceNameTxt: Text[240]; PageNumber: Integer)
var
WebService: Record "Web Service";