From d371d82a3143ca3bf4a6299fbd956e743cfe0e0a Mon Sep 17 00:00:00 2001 From: Karth Date: Mon, 24 Aug 2026 20:17:50 -0400 Subject: [PATCH 1/3] Reject party/role combinations the party does not hold (OFBIZ-12370, OFBIZ-12372, OFBIZ-12373) InvoiceRole, BillingAccountRole, and FinAccountRole all let a user submit any partyId + roleTypeId combination with no validation that the party actually holds that role. For InvoiceRole this hits the InvoiceRole/ PartyRole foreign key constraint and surfaces a raw SQL error. For BillingAccountRole and FinAccountRole, an unconditional ensurePartyRole eca instead silently fabricates a PartyRole record for the submitted roleTypeId, regardless of whether it makes sense for that party. Adds a small, reusable checkPartyRoleExists service (party component, mirroring the existing ensureNaPartyRole helper) that looks up PartyRole by (partyId, roleTypeId) and, if missing, fails with the same PartyRoleAssociationRequired message already used for this exact class of bug in createWorkEffortAndPartyAssign. Wires it as an in-validate eca on createInvoiceRole, createBillingAccountRole, and createFinAccountRole, so invalid combinations are rejected before the existing invoke-event ensurePartyRole ecas ever run. Adds regression tests: a negative case per ticket proving the party/role combination is now rejected (and, for BillingAccountRole/ FinAccountRole, that no PartyRole is fabricated), plus a new AutoAcctgBillingAccountTests suite since BillingAccountRole had no existing test coverage. Signed-off-by: Karth --- applications/accounting/servicedef/secas.xml | 20 ++++ .../AutoAcctgBillingAccountTests.groovy | 100 ++++++++++++++++++ .../AutoAcctgFinAccountTests.groovy | 40 ++++++- .../accounting/AutoAcctgInvoiceTests.groovy | 25 ++++- .../accounting/testdef/accountingtests.xml | 3 + .../minilang/party/PartySimpleMethods.xml | 13 +++ applications/party/servicedef/services.xml | 15 +++ 7 files changed, 210 insertions(+), 6 deletions(-) create mode 100644 applications/accounting/src/test/groovy/org/apache/ofbiz/accounting/accounting/AutoAcctgBillingAccountTests.groovy diff --git a/applications/accounting/servicedef/secas.xml b/applications/accounting/servicedef/secas.xml index 93bd386e5ac..b58aead3ea2 100644 --- a/applications/accounting/servicedef/secas.xml +++ b/applications/accounting/servicedef/secas.xml @@ -47,6 +47,19 @@ under the License. + + + + + + + + + + + + @@ -110,6 +123,13 @@ under the License. + + + + + + diff --git a/applications/accounting/src/test/groovy/org/apache/ofbiz/accounting/accounting/AutoAcctgBillingAccountTests.groovy b/applications/accounting/src/test/groovy/org/apache/ofbiz/accounting/accounting/AutoAcctgBillingAccountTests.groovy new file mode 100644 index 00000000000..95a276d0d1b --- /dev/null +++ b/applications/accounting/src/test/groovy/org/apache/ofbiz/accounting/accounting/AutoAcctgBillingAccountTests.groovy @@ -0,0 +1,100 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + *******************************************************************************/ +package org.apache.ofbiz.accounting.accounting + +import org.apache.ofbiz.entity.GenericValue +import org.apache.ofbiz.service.ServiceUtil +import org.apache.ofbiz.testtools.JunitJupiterTest +import org.apache.ofbiz.testtools.JupiterTestHelper +import org.junit.jupiter.api.Order +import org.junit.jupiter.api.Test + +@JunitJupiterTest +class AutoAcctgBillingAccountTests implements JupiterTestHelper { + + private String billingAccountId + + @Test + @Order(1) + void testCreateBillingAccount() { + Map serviceCtx = [ + accountLimit: 1000, + description: 'AutoAcctgBillingAccountTests billing account', + userLogin: userLogin + ] + Map serviceResult = dispatcher.runSync('createBillingAccount', serviceCtx) + assert ServiceUtil.isSuccess(serviceResult) + billingAccountId = serviceResult.billingAccountId + assert billingAccountId + + GenericValue billingAccount = from('BillingAccount').where('billingAccountId', billingAccountId).queryOne() + assert billingAccount + } + + // DEMO_COMPANY already holds the INTERNAL_ORGANIZATIO role (see AccountingTestsData.xml), so this + // combination is legitimate and must still succeed after OFBIZ-12372's validation is in place. + @Test + @Order(2) + void testCreateBillingAccountRole() { + String partyId = testParams.partyId ?: 'DEMO_COMPANY' + String roleTypeId = 'INTERNAL_ORGANIZATIO' + Map serviceCtx = [ + billingAccountId: billingAccountId, + partyId: partyId, + roleTypeId: roleTypeId, + userLogin: userLogin + ] + Map serviceResult = dispatcher.runSync('createBillingAccountRole', serviceCtx) + assert ServiceUtil.isSuccess(serviceResult) + + GenericValue billingAccountRole = from('BillingAccountRole') + .where('billingAccountId', billingAccountId, 'partyId', partyId, 'roleTypeId', roleTypeId) + .queryOne() + assert billingAccountRole + } + + // OFBIZ-12372: DEMO_COMPANY does not hold the CARRIER role (only INTERNAL_ORGANIZATIO/SUPPLIER, + // see AccountingTestsData.xml), so this combination must be rejected instead of the + // createBillingAccountRole -> ensurePartyRole eca silently fabricating a PartyRole record for a + // role the party was never given. + @Test + @Order(3) + void testCreateBillingAccountRoleRejectsPartyWithoutRole() { + String partyId = testParams.partyId ?: 'DEMO_COMPANY' + Map serviceCtx = [ + billingAccountId: billingAccountId, + partyId: partyId, + roleTypeId: 'CARRIER', + userLogin: userLogin + ] + Map serviceResult = dispatcher.runSync('createBillingAccountRole', serviceCtx) + assert ServiceUtil.isError(serviceResult) + + GenericValue billingAccountRole = from('BillingAccountRole') + .where('billingAccountId', billingAccountId, 'partyId', partyId, 'roleTypeId', 'CARRIER') + .queryOne() + assert !billingAccountRole + + GenericValue spuriousPartyRole = from('PartyRole') + .where('partyId', partyId, 'roleTypeId', 'CARRIER') + .queryOne() + assert !spuriousPartyRole + } + +} diff --git a/applications/accounting/src/test/groovy/org/apache/ofbiz/accounting/accounting/AutoAcctgFinAccountTests.groovy b/applications/accounting/src/test/groovy/org/apache/ofbiz/accounting/accounting/AutoAcctgFinAccountTests.groovy index c07e89984c6..0cfb8f2f65d 100644 --- a/applications/accounting/src/test/groovy/org/apache/ofbiz/accounting/accounting/AutoAcctgFinAccountTests.groovy +++ b/applications/accounting/src/test/groovy/org/apache/ofbiz/accounting/accounting/AutoAcctgFinAccountTests.groovy @@ -114,8 +114,38 @@ class AutoAcctgFinAccountTests implements JupiterTestHelper { assert finAccountRole } + // OFBIZ-12373: DEMO_COMPANY does not hold the CARRIER role (only INTERNAL_ORGANIZATIO/SUPPLIER, + // see AccountingTestsData.xml), so this combination must be rejected instead of the createFinAccountRole + // -> ensurePartyRole eca silently fabricating a PartyRole record for a role the party was never given. @Test @Order(5) + void testCreateFinAccountRoleRejectsPartyWithoutRole() { + String finAccountId = testParams.finAccountId ?: '1003' + String partyId = testParams.partyId ?: 'DEMO_COMPANY' + Map serviceCtx = [ + finAccountId: finAccountId, + partyId: partyId, + roleTypeId: 'CARRIER', + fromDate: UtilDateTime.nowTimestamp(), + currencyUomId: testParams.currencyUomId ?: 'USD', + userLogin: userLogin + ] + Map serviceResult = dispatcher.runSync('createFinAccountRole', serviceCtx) + assert ServiceUtil.isError(serviceResult) + + GenericValue finAccountRole = from('FinAccountRole') + .where('finAccountId', finAccountId, 'partyId', partyId, 'roleTypeId', 'CARRIER') + .queryFirst() + assert !finAccountRole + + GenericValue spuriousPartyRole = from('PartyRole') + .where('partyId', partyId, 'roleTypeId', 'CARRIER') + .queryOne() + assert !spuriousPartyRole + } + + @Test + @Order(6) void testUpdateFinAccountRole() { String finAccountId = testParams.finAccountId ?: '1004' String partyId = testParams.partyId ?: 'DEMO_COMPANY' @@ -139,7 +169,7 @@ class AutoAcctgFinAccountTests implements JupiterTestHelper { } @Test - @Order(6) + @Order(7) void testDeleteFinAccountRole() { String finAccountId = testParams.finAccountId ?: '1004' String partyId = testParams.partyId ?: 'DEMO_COMPANY' @@ -161,7 +191,7 @@ class AutoAcctgFinAccountTests implements JupiterTestHelper { } @Test - @Order(7) + @Order(8) void testCreateFinAccountTrans() { String finAccountId = testParams.finAccountId ?: '1003' String finAccountTransTypeId = testParams.finAccountTransTypeId ?: 'ADJUSTMENT' @@ -180,7 +210,7 @@ class AutoAcctgFinAccountTests implements JupiterTestHelper { } @Test - @Order(8) + @Order(9) void testCreateFinAccountStatus() { String finAccountId = testParams.finAccountId ?: '1003' String statusId = testParams.statusId ?: 'FNACT_ACTIVE' @@ -200,7 +230,7 @@ class AutoAcctgFinAccountTests implements JupiterTestHelper { } @Test - @Order(9) + @Order(10) void testCreateFinAccountAuth() { String finAccountId = testParams.finAccountId ?: '1004' String currencyUomId = testParams.currencyUomId ?: 'USD' @@ -218,7 +248,7 @@ class AutoAcctgFinAccountTests implements JupiterTestHelper { } @Test - @Order(10) + @Order(11) void testSetFinAccountTransStatus() { String finAccountTransId = testParams.finAccountTransId ?: '1010' String statusId = testParams.statusId ?: 'FINACT_TRNS_APPROVED' diff --git a/applications/accounting/src/test/groovy/org/apache/ofbiz/accounting/accounting/AutoAcctgInvoiceTests.groovy b/applications/accounting/src/test/groovy/org/apache/ofbiz/accounting/accounting/AutoAcctgInvoiceTests.groovy index ed7c870e611..8c8878f9f51 100644 --- a/applications/accounting/src/test/groovy/org/apache/ofbiz/accounting/accounting/AutoAcctgInvoiceTests.groovy +++ b/applications/accounting/src/test/groovy/org/apache/ofbiz/accounting/accounting/AutoAcctgInvoiceTests.groovy @@ -217,8 +217,31 @@ class AutoAcctgInvoiceTests implements JupiterTestHelper { assert invoiceRole } + // OFBIZ-12370: DEMO_COMPANY does not hold the CARRIER role (only INTERNAL_ORGANIZATIO/SUPPLIER, + // see AccountingTestsData.xml), so this combination must be rejected instead of hitting the + // InvoiceRole/PartyRole foreign key constraint with a raw SQL error. @Test @Order(11) + void testCreateInvoiceRoleRejectsPartyWithoutRole() { + Map serviceCtx = [ + invoiceId: testParams.invoiceId ?: '1006', + partyId: testParams.partyId ?: 'DEMO_COMPANY', + roleTypeId: 'CARRIER', + userLogin: userLogin + ] + Map serviceResult = dispatcher.runSync('createInvoiceRole', serviceCtx) + assert ServiceUtil.isError(serviceResult) + + GenericValue invoiceRole = from('InvoiceRole') + .where('invoiceId', serviceCtx.invoiceId, + 'partyId', serviceCtx.partyId, + 'roleTypeId', 'CARRIER') + .queryOne() + assert !invoiceRole + } + + @Test + @Order(12) void testCreateInvoiceTerm() { Map serviceCtx = [ invoiceId: testParams.invoiceId ?: '1006', @@ -239,7 +262,7 @@ class AutoAcctgInvoiceTests implements JupiterTestHelper { } @Test - @Order(12) + @Order(13) void testCancelInvoice() { Map serviceCtx = [ invoiceId: testParams.invoiceId ?: '1007', diff --git a/applications/accounting/testdef/accountingtests.xml b/applications/accounting/testdef/accountingtests.xml index a106d6ee2b1..a2218476893 100644 --- a/applications/accounting/testdef/accountingtests.xml +++ b/applications/accounting/testdef/accountingtests.xml @@ -42,6 +42,9 @@ + + + diff --git a/applications/party/minilang/party/PartySimpleMethods.xml b/applications/party/minilang/party/PartySimpleMethods.xml index e8af48afe79..8dddaebeae0 100644 --- a/applications/party/minilang/party/PartySimpleMethods.xml +++ b/applications/party/minilang/party/PartySimpleMethods.xml @@ -85,6 +85,19 @@ under the License. + + + + + + + + + + + + + diff --git a/applications/party/servicedef/services.xml b/applications/party/servicedef/services.xml index 70a03622299..2de7a6b0077 100644 --- a/applications/party/servicedef/services.xml +++ b/applications/party/servicedef/services.xml @@ -349,6 +349,21 @@ under the License. + + Reject the request with a friendly error if the party does not already hold the given role, + instead of letting a role-based association be created against a party/role combination that doesn't exist. + + + + + + + + + + + Ensure that the party indicate by partyIdFrom is in the roleTypeIdFrom specifc role. If roleTypeIdFrom isn't present use _NA_ From ebf3b58ff52ab5c99a1406bfc1d62a6c677d9bf0 Mon Sep 17 00:00:00 2001 From: Karth Date: Wed, 26 Aug 2026 06:26:31 -0400 Subject: [PATCH 2/3] Reject party/role combinations the party does not hold for BudgetRole (OFBIZ-12371) Same bug shape already fixed for InvoiceRole/BillingAccountRole/ FinAccountRole: createBudgetRole's unconditional ensurePartyRole invoke eca silently fabricates a PartyRole record for whatever roleTypeId is submitted, with no validation that the party actually holds (or should hold) that role. Wires the existing checkPartyRoleExists service (added for the other three role types) as an in-validate eca on createBudgetRole, so invalid combinations are rejected before the invoke-event ensurePartyRole eca ever runs. Adds a positive-case and negative-case regression test to AutoAcctgBudgetTests, following the same pattern used for the other three role types. Depends on the checkPartyRoleExists service already added for OFBIZ-12370/12372/12373 (open as apache/ofbiz-framework#1727) - this change is a no-op without it. Signed-off-by: Karth --- applications/accounting/servicedef/secas.xml | 7 +++ .../accounting/AutoAcctgBudgetTests.groovy | 52 +++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/applications/accounting/servicedef/secas.xml b/applications/accounting/servicedef/secas.xml index b58aead3ea2..706f73ce3d4 100644 --- a/applications/accounting/servicedef/secas.xml +++ b/applications/accounting/servicedef/secas.xml @@ -118,6 +118,13 @@ under the License. + + + + + + diff --git a/applications/accounting/src/test/groovy/org/apache/ofbiz/accounting/accounting/AutoAcctgBudgetTests.groovy b/applications/accounting/src/test/groovy/org/apache/ofbiz/accounting/accounting/AutoAcctgBudgetTests.groovy index 44dc27f990b..d87c09d5e95 100644 --- a/applications/accounting/src/test/groovy/org/apache/ofbiz/accounting/accounting/AutoAcctgBudgetTests.groovy +++ b/applications/accounting/src/test/groovy/org/apache/ofbiz/accounting/accounting/AutoAcctgBudgetTests.groovy @@ -62,4 +62,56 @@ class AutoAcctgBudgetTests implements JupiterTestHelper { assert budgetStatuses[0].statusId == statusId } + // DEMO_COMPANY already holds the INTERNAL_ORGANIZATIO role (see AccountingTestsData.xml), so this + // combination is legitimate and must still succeed after OFBIZ-12371's validation is in place. + @Test + @Order(3) + void testCreateBudgetRole() { + String budgetId = testParams.budgetId ?: '9999' + String partyId = testParams.partyId ?: 'DEMO_COMPANY' + String roleTypeId = 'INTERNAL_ORGANIZATIO' + Map serviceCtx = [ + budgetId: budgetId, + partyId: partyId, + roleTypeId: roleTypeId, + userLogin: userLogin + ] + Map serviceResult = dispatcher.runSync('createBudgetRole', serviceCtx) + assert ServiceUtil.isSuccess(serviceResult) + + GenericValue budgetRole = from('BudgetRole') + .where('budgetId', budgetId, 'partyId', partyId, 'roleTypeId', roleTypeId) + .queryOne() + assert budgetRole + } + + // OFBIZ-12371: DEMO_COMPANY does not hold the CARRIER role (only INTERNAL_ORGANIZATIO/SUPPLIER, + // see AccountingTestsData.xml), so this combination must be rejected instead of the + // createBudgetRole -> ensurePartyRole eca silently fabricating a PartyRole record for a role + // the party was never given. + @Test + @Order(4) + void testCreateBudgetRoleRejectsPartyWithoutRole() { + String budgetId = testParams.budgetId ?: '9999' + String partyId = testParams.partyId ?: 'DEMO_COMPANY' + Map serviceCtx = [ + budgetId: budgetId, + partyId: partyId, + roleTypeId: 'CARRIER', + userLogin: userLogin + ] + Map serviceResult = dispatcher.runSync('createBudgetRole', serviceCtx) + assert ServiceUtil.isError(serviceResult) + + GenericValue budgetRole = from('BudgetRole') + .where('budgetId', budgetId, 'partyId', partyId, 'roleTypeId', 'CARRIER') + .queryOne() + assert !budgetRole + + GenericValue spuriousPartyRole = from('PartyRole') + .where('partyId', partyId, 'roleTypeId', 'CARRIER') + .queryOne() + assert !spuriousPartyRole + } + } From a83fec3c4840660bd6b716f3c0dc70c3da5ced7e Mon Sep 17 00:00:00 2001 From: Karth Date: Tue, 15 Sep 2026 07:45:51 -0400 Subject: [PATCH 3/3] Reject party/role combinations the party does not hold for TimesheetRole and RequirementRole createTimesheetRole and createRequirementRole have the identical unvalidated-PartyRole gap already fixed for TaxAuthority (#1692), Invoice/BillingAccount/FinAccount roles (#1727), and BudgetRole (#1736): a free-choice RoleType dropdown feeding straight into an unconditional ensurePartyRole invoke-eca, so any party/role combination is silently accepted and a spurious PartyRole is fabricated even when the party doesn't legitimately hold that role. Both AddTimesheetRole (TimesheetForms.xml) and AddRequirementRole (RequirementForms.xml) submit directly to their respective create service with no other caller anywhere in the codebase (verified via a search across XML/Groovy/Java, not just the immediate eca/form pair), so the fix mirrors the existing pattern exactly: an in-validate eca calling the already-built checkPartyRoleExists service before the invoke-eca that runs ensurePartyRole. Two other services flagged during this pass - createContentRole and createQuoteRole - have the same free-choice dropdown on their "assign role" forms, but are NOT included here: both are also the mechanism their own components use internally to legitimately grant a brand-new role a party doesn't hold yet (createContentRole via ContentManagementServices.java's updateSiteRoles/addRoleToUser bootstrapping site access; createQuoteRole via QuoteServicesScript.groovy assigning REQ_TAKER/INTERNAL_ORGANIZATIO when a quote is created, and copying CustRequestParty roles onto a converted quote). A blanket in-validate eca on either service would reject those legitimate internal grants, not just the vulnerable direct-form path. Fixing those two needs a different approach (validating at the form-submission event level, not the service level) and is left for separate follow-up rather than risking a regression here. Verified: applications/workeffort/servicedef/secas.xml, applications/order/servicedef/secas.xml, and applications/workeffort/testdef/workefforttests.xml all validate against their real XSD schemas (xmllint --schema). Added TimesheetRoleTests (new) and two tests to the existing OrderRequirementTests, both following the established pattern: a negative case using DemoCustomer + CARRIER (a role its seeded PartyRole set - BILL_TO_CUSTOMER/CONTACT/CUSTOMER/ END_USER_CUSTOMER/PLACING_CUSTOMER/SHIP_TO_CUSTOMER - provably does not include) and a positive case using a role it already holds. `./gradlew compileTestGroovy checkstyleMain checkstyleTest codenarcMain codenarcTest` all BUILD SUCCESSFUL. No running OFBiz+DB instance available in this environment to execute the Groovy tests end-to-end - disclosed honestly, same limitation as prior PRs in this series. Co-authored-by: Claude Sonnet 5 Signed-off-by: Karth --- applications/order/servicedef/secas.xml | 7 +++ .../order/test/OrderRequirementTests.groovy | 33 ++++++++++ applications/workeffort/servicedef/secas.xml | 7 +++ .../workeffort/test/TimesheetRoleTests.groovy | 63 +++++++++++++++++++ .../workeffort/testdef/workefforttests.xml | 3 + 5 files changed, 113 insertions(+) create mode 100644 applications/workeffort/src/test/groovy/org/apache/ofbiz/workeffort/workeffort/test/TimesheetRoleTests.groovy diff --git a/applications/order/servicedef/secas.xml b/applications/order/servicedef/secas.xml index 29ee400cb2e..9576f05a070 100644 --- a/applications/order/servicedef/secas.xml +++ b/applications/order/servicedef/secas.xml @@ -515,6 +515,13 @@ under the License. + + + + + + diff --git a/applications/order/src/test/groovy/org/apache/ofbiz/order/order/test/OrderRequirementTests.groovy b/applications/order/src/test/groovy/org/apache/ofbiz/order/order/test/OrderRequirementTests.groovy index 7e73bff3f5a..17942f3a9cd 100644 --- a/applications/order/src/test/groovy/org/apache/ofbiz/order/order/test/OrderRequirementTests.groovy +++ b/applications/order/src/test/groovy/org/apache/ofbiz/order/order/test/OrderRequirementTests.groovy @@ -132,4 +132,37 @@ class OrderRequirementTests implements JupiterTestHelper { assert ServiceUtil.isSuccess(serviceResult) } + // createRequirementRole's in-validate eca rejects a party/role combination the party does not + // already hold, before it reaches ensurePartyRole (which would otherwise silently create a + // spurious PartyRole). DemoCustomer's seeded PartyRole set (OrderDemoData.xml) is exactly + // {BILL_TO_CUSTOMER, CONTACT, CUSTOMER, END_USER_CUSTOMER, PLACING_CUSTOMER, SHIP_TO_CUSTOMER} -- + // CARRIER is provably not among them. + @Test + @Order(8) + void testCreateRequirementRole_rejectsRoleThePartyDoesNotHold() { + String requirementId = testParams.requirementId ?: '1000' + Map serviceCtx = [ + requirementId: requirementId, + partyId: 'DemoCustomer', + roleTypeId: 'CARRIER', + userLogin: userLogin + ] + Map serviceResult = dispatcher.runSync('createRequirementRole', serviceCtx) + assert ServiceUtil.isError(serviceResult) + } + + @Test + @Order(9) + void testCreateRequirementRole_allowsRoleThePartyAlreadyHolds() { + String requirementId = testParams.requirementId ?: '1000' + Map serviceCtx = [ + requirementId: requirementId, + partyId: 'DemoCustomer', + roleTypeId: 'CUSTOMER', + userLogin: userLogin + ] + Map serviceResult = dispatcher.runSync('createRequirementRole', serviceCtx) + assert ServiceUtil.isSuccess(serviceResult) + } + } diff --git a/applications/workeffort/servicedef/secas.xml b/applications/workeffort/servicedef/secas.xml index 80e08c3fcbf..8a711f11f15 100644 --- a/applications/workeffort/servicedef/secas.xml +++ b/applications/workeffort/servicedef/secas.xml @@ -79,6 +79,13 @@ under the License. + + + + + + diff --git a/applications/workeffort/src/test/groovy/org/apache/ofbiz/workeffort/workeffort/test/TimesheetRoleTests.groovy b/applications/workeffort/src/test/groovy/org/apache/ofbiz/workeffort/workeffort/test/TimesheetRoleTests.groovy new file mode 100644 index 00000000000..9d94ee00ed7 --- /dev/null +++ b/applications/workeffort/src/test/groovy/org/apache/ofbiz/workeffort/workeffort/test/TimesheetRoleTests.groovy @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.ofbiz.workeffort.workeffort.test + +import org.apache.ofbiz.service.ServiceUtil +import org.apache.ofbiz.testtools.JunitJupiterTest +import org.apache.ofbiz.testtools.JupiterTestHelper +import org.junit.jupiter.api.Order +import org.junit.jupiter.api.Test + +@JunitJupiterTest +class TimesheetRoleTests implements JupiterTestHelper { + + // createTimesheetRole's in-validate eca rejects a party/role combination the party does not + // already hold, before it reaches ensurePartyRole (which would otherwise silently create a + // spurious PartyRole). DemoCustomer's seeded PartyRole set (OrderDemoData.xml) is exactly + // {BILL_TO_CUSTOMER, CONTACT, CUSTOMER, END_USER_CUSTOMER, PLACING_CUSTOMER, SHIP_TO_CUSTOMER} -- + // CARRIER is provably not among them. + @Test + @Order(1) + void testCreateTimesheetRole_rejectsRoleThePartyDoesNotHold() { + String timesheetId = testParams.timesheetId ?: 'TestTimesheet-1' + Map serviceCtx = [ + timesheetId: timesheetId, + partyId: 'DemoCustomer', + roleTypeId: 'CARRIER', + userLogin: userLogin + ] + Map serviceResult = dispatcher.runSync('createTimesheetRole', serviceCtx) + assert ServiceUtil.isError(serviceResult) + } + + @Test + @Order(2) + void testCreateTimesheetRole_allowsRoleThePartyAlreadyHolds() { + String timesheetId = testParams.timesheetId ?: 'TestTimesheet-1' + Map serviceCtx = [ + timesheetId: timesheetId, + partyId: 'DemoCustomer', + roleTypeId: 'CUSTOMER', + userLogin: userLogin + ] + Map serviceResult = dispatcher.runSync('createTimesheetRole', serviceCtx) + assert ServiceUtil.isSuccess(serviceResult) + } + +} diff --git a/applications/workeffort/testdef/workefforttests.xml b/applications/workeffort/testdef/workefforttests.xml index 27e705a9ce4..df3e7dd7ae1 100644 --- a/applications/workeffort/testdef/workefforttests.xml +++ b/applications/workeffort/testdef/workefforttests.xml @@ -26,4 +26,7 @@ + + +