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() {
[