This is an automated email from the ASF dual-hosted git repository.

ashishvijaywargiya pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-plugins.git


The following commit(s) were added to refs/heads/trunk by this push:
     new fcb6e3a26 REST API Support for Running and Monitoring OFBiz Test 
Cases(example component sample code) (#370)
fcb6e3a26 is described below

commit fcb6e3a26eeafd2ec7118a64914756c3ff2e0193
Author: Ashish Vijaywargiya <[email protected]>
AuthorDate: Thu Aug 20 17:01:32 2026 +0530

    REST API Support for Running and Monitoring OFBiz Test Cases(example 
component sample code) (#370)
    
    Pushing the sample code from plugins->example component, this will help
    others to see the patterns like how we can enable rest support for other
    component's test cases.
    
    ofbiz-framework PR:
    https://github.com/apache/ofbiz-framework/pull/1690
---
 example/api/example.rest.xml                       | 18 ++++
 example/servicedef/services.xml                    | 31 +++++++
 .../ofbiz/example/ExampleTestRunServices.groovy    | 41 +++++++++
 .../test/jupiter/ExampleJupiterTests.groovy        | 99 +++++++++++++++++++---
 4 files changed, 176 insertions(+), 13 deletions(-)

diff --git a/example/api/example.rest.xml b/example/api/example.rest.xml
index 50f60ae02..0a5a72b3b 100644
--- a/example/api/example.rest.xml
+++ b/example/api/example.rest.xml
@@ -50,4 +50,22 @@ under the License.
             <service name="findExampleById"/>
         </operation>
     </resource>
+
+    <resource name="TestRunResource"
+              path="testruns"
+              displayName="Test Run"
+              description="Trigger and poll example component test runs only - 
componentName is not accepted, permanently scoped to the example component"
+              primaryPermission="TESTEXEC"
+              mainAction="ADMIN">
+
+        <operation verb="post" description="Trigger a test suite run (scoped 
to the example component)"
+                   consumes="application/json">
+            <service name="runExampleTestSuite"/>
+        </operation>
+
+        <operation verb="get" description="Get a test run's status (scoped to 
the example component)"
+                   path="{runId}">
+            <service name="getExampleTestRunStatus"/>
+        </operation>
+    </resource>
 </api>
\ No newline at end of file
diff --git a/example/servicedef/services.xml b/example/servicedef/services.xml
index bd0d25c77..b52bbcca0 100644
--- a/example/servicedef/services.xml
+++ b/example/servicedef/services.xml
@@ -146,6 +146,37 @@ under the License.
         <attribute name="successMessage" mode="IN" type="String" 
optional="true"></attribute>
     </service>
 
+    <!-- Test Run Services (component-scoped wrapper around 
framework/testtools) -->
+    <service name="runExampleTestSuite" engine="groovy" auth="true"
+             
location="component://example/src/main/groovy/org/apache/ofbiz/example/ExampleTestRunServices.groovy"
+             invoke="runExampleTestSuite">
+        <description>Kicks off a testdef test-suite run asynchronously, scoped 
to the example
+            component only - see TestRunServices.runScopedTestSuite. Gated by 
the test.api.enabled
+            config flag and the TESTEXEC_ADMIN permission (checked inside the 
underlying service).
+            componentName is deliberately not an attribute here - the 
component is fixed, not
+            caller-suppliable. testMethodName optionally scopes the run to one
+            @Test/@ParameterizedTest method within the class testCaseName 
resolves to - requires
+            testCaseName, and only applies when it resolves to a 
jupiter-test-suite.</description>
+        <attribute name="suiteName" type="String" mode="IN" optional="false"/>
+        <attribute name="testCaseName" type="String" mode="IN" 
optional="true"/>
+        <attribute name="testMethodName" type="String" mode="IN" 
optional="true"/>
+        <attribute name="testParams" type="Map" mode="IN" optional="true"/>
+        <attribute name="runId" type="String" mode="OUT" optional="true"/>
+    </service>
+
+    <service name="getExampleTestRunStatus" engine="groovy" auth="true"
+             
location="component://example/src/main/groovy/org/apache/ofbiz/example/ExampleTestRunServices.groovy"
+             invoke="getExampleTestRunStatus">
+        <description>Reads a runExampleTestSuite-triggered run's status, 
scoped to the example
+            component only - a runId belonging to another component's run is 
reported as
+            not-found, not a distinguishable "wrong component" error. See
+            TestRunServices.getScopedTestRunStatus.</description>
+        <attribute name="runId" type="String" mode="IN" optional="false"/>
+        <attribute name="status" type="String" mode="OUT" optional="true"/>
+        <attribute name="componentName" type="String" mode="OUT" 
optional="true"/>
+        <attribute name="resultSummary" type="Map" mode="OUT" optional="true"/>
+    </service>
+
     <!-- ExampleType services -->
     <service name="createExampleType" default-entity-name="ExampleType" 
engine="entity-auto" invoke="create" auth="true">
         <description>Create a record of ExampleType</description>
diff --git 
a/example/src/main/groovy/org/apache/ofbiz/example/ExampleTestRunServices.groovy
 
b/example/src/main/groovy/org/apache/ofbiz/example/ExampleTestRunServices.groovy
new file mode 100644
index 000000000..015a31784
--- /dev/null
+++ 
b/example/src/main/groovy/org/apache/ofbiz/example/ExampleTestRunServices.groovy
@@ -0,0 +1,41 @@
+/*
+ * 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.example
+
+import org.apache.ofbiz.testtools.TestRunServices
+
+// Component-scoped wrapper around TestRunServices' 
runTestSuite/getTestRunStatus, forced to this
+// component ("example") only - a caller can never trigger or poll another 
component's tests
+// through these two services, regardless of what componentName (if any) they 
supply.
+//
+// Meant to double as the copy-paste template for any other component adopting 
the same pattern:
+// copy this script, rename it and its two methods, and change the 'example' 
literal below - no
+// changes to TestRunServices itself are needed.
+
+// Not a script-level variable: top-level locals in a Groovy service script 
aren't visible inside
+// the def methods below (they belong to the generated run() method, not the 
class), so the fixed
+// component name is a literal in each method instead.
+
+Map runExampleTestSuite() {
+    return TestRunServices.runScopedTestSuite(dctx, parameters, 'example')
+}
+
+Map getExampleTestRunStatus() {
+    return TestRunServices.getScopedTestRunStatus(dctx, parameters, 'example')
+}
diff --git 
a/example/src/test/groovy/org/apache/ofbiz/example/test/jupiter/ExampleJupiterTests.groovy
 
b/example/src/test/groovy/org/apache/ofbiz/example/test/jupiter/ExampleJupiterTests.groovy
index 26573969e..315546b19 100644
--- 
a/example/src/test/groovy/org/apache/ofbiz/example/test/jupiter/ExampleJupiterTests.groovy
+++ 
b/example/src/test/groovy/org/apache/ofbiz/example/test/jupiter/ExampleJupiterTests.groovy
@@ -51,18 +51,7 @@ class ExampleJupiterTests implements JupiterTestHelper {
     @Test
     @Order(1)
     void shouldCreateExample() {
-        GenericValue userLogin = delegator.findOne('UserLogin', [userLoginId: 
'system'], false)
-        Map<String, Object> result = dispatcher.runSync('createExample', [
-                exampleTypeId: 'CONTRIVED',
-                exampleName: 'Test Example - Integration',
-                statusId: 'EXST_IN_DESIGN',
-                userLogin: userLogin
-        ])
-        assert ServiceUtil.isSuccess(result)
-
-        GenericValue example = from('Example').where('exampleId', 
result.exampleId).queryOne()
-        assert example != null
-        assert example.exampleTypeId == 'CONTRIVED'
+        createAndAssertExample('Test Example - Integration')
     }
 
     @ParameterizedTest(name = '[{index}] exampleTypeId={0}')
@@ -75,10 +64,15 @@ class ExampleJupiterTests implements JupiterTestHelper {
     ])
     void shouldCreateExampleAcrossTypes(String exampleTypeId) {
         GenericValue userLogin = delegator.findOne('UserLogin', [userLoginId: 
'system'], false)
+        // exampleTypeId is deliberately left CSV-driven, not 
testParams-driven - that's the one
+        // value this method exists to vary across invocations. statusId isn't 
varied by the CSV
+        // source, so it's the one field here that can take a caller override 
the same way
+        // shouldCreateExample/shouldCreateExampleWithParams do.
+        String statusId = testParams.statusId ?: 'EXST_IN_DESIGN'
         Map<String, Object> result = dispatcher.runSync('createExample', [
                 exampleTypeId: exampleTypeId,
                 exampleName: 'Test Example - ' + exampleTypeId,
-                statusId: 'EXST_IN_DESIGN',
+                statusId: statusId,
                 userLogin: userLogin
         ])
         assert ServiceUtil.isSuccess(result)
@@ -121,6 +115,85 @@ class ExampleJupiterTests implements JupiterTestHelper {
         }
     }
 
+    @Test
+    @Order(5)
+    void shouldCreateExampleWithParams() {
+        createAndAssertExample('Test Example - Default')
+    }
+
+    @Test
+    @Order(6)
+    void shouldUpdateExample() {
+        GenericValue userLogin = delegator.findOne('UserLogin', [userLoginId: 
'system'], false)
+        // Initial-state fields reuse the same testParams keys 
shouldCreateExample uses - both tests
+        // create a fresh Example the same way, so sharing key names here is 
harmless (each test's
+        // record is independent). The post-update target fields use their 
own, distinct keys
+        // (updatedExampleName/updatedStatusId) - those can't share names with 
the initial-state keys
+        // above, or one testParams map couldn't set a different "before" and 
"after" value in the
+        // same call.
+        String exampleTypeId = testParams.exampleTypeId ?: 'CONTRIVED'
+        String exampleName = testParams.exampleName ?: 'Test Example - Before 
Update'
+        String statusId = testParams.statusId ?: 'EXST_IN_DESIGN'
+
+        Map<String, Object> createResult = dispatcher.runSync('createExample', 
[
+                exampleTypeId: exampleTypeId,
+                exampleName: exampleName,
+                statusId: statusId,
+                userLogin: userLogin
+        ])
+        assert ServiceUtil.isSuccess(createResult)
+        String exampleId = createResult.exampleId
+
+        String updatedExampleName = testParams.updatedExampleName ?: 'Test 
Example - After Update'
+        String updatedStatusId = testParams.updatedStatusId ?: 'EXST_APPROVED'
+
+        Map<String, Object> updateResult = dispatcher.runSync('updateExample', 
[
+                exampleId: exampleId,
+                exampleName: updatedExampleName,
+                statusId: updatedStatusId,
+                userLogin: userLogin
+        ])
+        assert ServiceUtil.isSuccess(updateResult)
+        assert updateResult.oldStatusId == statusId
+
+        GenericValue example = from('Example').where('exampleId', 
exampleId).queryOne()
+        assert example != null
+        assert example.exampleTypeId == exampleTypeId
+        assert example.exampleName == updatedExampleName
+        assert example.statusId == updatedStatusId
+    }
+
+    /**
+     * Creates an Example from testParams-driven 
exampleTypeId/exampleName/statusId (each falling
+     * back to a default) and asserts the createExample service succeeded and 
that the persisted
+     * record matches the resolved values - the exact create+assert sequence 
shouldCreateExample()
+     * and shouldCreateExampleWithParams() both perform, differing only in 
exampleName's default
+     * text, factored out here to remove that duplication.
+     * @param defaultExampleName the exampleName to use when testParams 
doesn't override it
+     * @return the created, already-asserted Example
+     */
+        private GenericValue createAndAssertExample(String defaultExampleName) 
{
+        GenericValue userLogin = delegator.findOne('UserLogin', [userLoginId: 
'system'], false)
+        String exampleTypeId = testParams.exampleTypeId ?: 'CONTRIVED'
+        String exampleName = testParams.exampleName ?: defaultExampleName
+        String statusId = testParams.statusId ?: 'EXST_IN_DESIGN'
+
+        Map<String, Object> result = dispatcher.runSync('createExample', [
+                exampleTypeId: exampleTypeId,
+                exampleName: exampleName,
+                statusId: statusId,
+                userLogin: userLogin
+        ])
+        assert ServiceUtil.isSuccess(result)
+
+        GenericValue example = from('Example').where('exampleId', 
result.exampleId).queryOne()
+        assert example != null
+        Assertions.assertEquals(exampleTypeId, example.exampleTypeId)
+        Assertions.assertEquals(exampleName, example.exampleName)
+        Assertions.assertEquals(statusId, example.statusId)
+        example
+    }
+
     @SuppressWarnings('UnusedPrivateMethod')
     private static List<Arguments> exampleCreationCases() {
         [

Reply via email to