e4ef25c5 by Ean Schuessler

Convert McpTestSuite to Spock with @Shared ExecutionContext and update test infrastructure

1 parent 077ceb78
...@@ -39,9 +39,6 @@ dependencies { ...@@ -39,9 +39,6 @@ dependencies {
39 // Test dependencies 39 // Test dependencies
40 testImplementation project(':framework') 40 testImplementation project(':framework')
41 testImplementation project(':framework').configurations.testImplementation.allDependencies 41 testImplementation project(':framework').configurations.testImplementation.allDependencies
42 testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.2'
43 testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.8.2'
44 testImplementation 'org.junit.platform:junit-platform-suite:1.8.2'
45 } 42 }
46 43
47 // by default the Java plugin runs test on build, change to not do that (only run test if explicit task) 44 // by default the Java plugin runs test on build, change to not do that (only run test if explicit task)
...@@ -57,7 +54,7 @@ test { ...@@ -57,7 +54,7 @@ test {
57 dependsOn cleanTest 54 dependsOn cleanTest
58 55
59 systemProperty 'moqui.runtime', moquiDir.absolutePath + '/runtime' 56 systemProperty 'moqui.runtime', moquiDir.absolutePath + '/runtime'
60 systemProperty 'moqui.conf', 'MoquiConf.xml' 57 systemProperty 'moqui.conf', 'conf/MoquiDevConf.xml'
61 systemProperty 'moqui.init.static', 'true' 58 systemProperty 'moqui.init.static', 'true'
62 maxHeapSize = "512M" 59 maxHeapSize = "512M"
63 60
...@@ -80,4 +77,4 @@ task copyDependencies { doLast { ...@@ -80,4 +77,4 @@ task copyDependencies { doLast {
80 into file(projectDir.absolutePath + '/lib') } 77 into file(projectDir.absolutePath + '/lib') }
81 } } 78 } }
82 copyDependencies.dependsOn cleanLib 79 copyDependencies.dependsOn cleanLib
83 jar.dependsOn copyDependencies
...\ No newline at end of file ...\ No newline at end of file
80 jar.dependsOn copyDependencies
......
...@@ -33,6 +33,5 @@ ...@@ -33,6 +33,5 @@
33 </webapp> 33 </webapp>
34 </webapp-list> 34 </webapp-list>
35 35
36 36 <depends-on name="SimpleScreens" version="2.2.0"/>
37
38 </component> 37 </component>
......
...@@ -45,25 +45,6 @@ ...@@ -45,25 +45,6 @@
45 <!-- MCP Test Screen --> 45 <!-- MCP Test Screen -->
46 <moqui.security.ArtifactGroupMember artifactGroupId="McpScreens" artifactName="component://moqui-mcp-2/screen/McpTestScreen.xml" artifactTypeEnumId="AT_XML_SCREEN"/> 46 <moqui.security.ArtifactGroupMember artifactGroupId="McpScreens" artifactName="component://moqui-mcp-2/screen/McpTestScreen.xml" artifactTypeEnumId="AT_XML_SCREEN"/>
47 47
48 <!-- Common Screen Access Patterns -->
49 <moqui.security.ArtifactGroupMember artifactGroupId="McpScreens" artifactName="apps/order/*" artifactTypeEnumId="AT_XML_SCREEN"/>
50 <moqui.security.ArtifactGroupMember artifactGroupId="McpScreens" artifactName="apps/party/*" artifactTypeEnumId="AT_XML_SCREEN"/>
51 <moqui.security.ArtifactGroupMember artifactGroupId="McpScreens" artifactName="apps/invoice/*" artifactTypeEnumId="AT_XML_SCREEN"/>
52 <moqui.security.ArtifactGroupMember artifactGroupId="McpScreens" artifactName="apps/product/*" artifactTypeEnumId="AT_XML_SCREEN"/>
53 <moqui.security.ArtifactGroupMember artifactGroupId="McpScreens" artifactName="apps/ledger/*" artifactTypeEnumId="AT_XML_SCREEN"/>
54 <moqui.security.ArtifactGroupMember artifactGroupId="McpScreens" artifactName="apps/marketing/*" artifactTypeEnumId="AT_XML_SCREEN"/>
55 <moqui.security.ArtifactGroupMember artifactGroupId="McpScreens" artifactName="apps/sales/*" artifactTypeEnumId="AT_XML_SCREEN"/>
56 <moqui.security.ArtifactGroupMember artifactGroupId="McpScreens" artifactName="apps/manufacturing/*" artifactTypeEnumId="AT_XML_SCREEN"/>
57 <moqui.security.ArtifactGroupMember artifactGroupId="McpScreens" artifactName="apps/warehouse/*" artifactTypeEnumId="AT_XML_SCREEN"/>
58 <moqui.security.ArtifactGroupMember artifactGroupId="McpScreens" artifactName="apps/humanresource/*" artifactTypeEnumId="AT_XML_SCREEN"/>
59 <moqui.security.ArtifactGroupMember artifactGroupId="McpScreens" artifactName="apps/project/*" artifactTypeEnumId="AT_XML_SCREEN"/>
60
61 <!-- Specific Business Screens for Testing -->
62 <moqui.security.ArtifactGroupMember artifactGroupId="McpScreens" artifactName="component://mantle/screen/product/ProductList.xml" artifactTypeEnumId="AT_XML_SCREEN"/>
63 <moqui.security.ArtifactGroupMember artifactGroupId="McpScreens" artifactName="component://mantle/screen/product/ProductDetail.xml" artifactTypeEnumId="AT_XML_SCREEN"/>
64 <moqui.security.ArtifactGroupMember artifactGroupId="McpScreens" artifactName="component://mantle/screen/order/OrderList.xml" artifactTypeEnumId="AT_XML_SCREEN"/>
65 <moqui.security.ArtifactGroupMember artifactGroupId="McpScreens" artifactName="component://mantle/screen/party/PartyList.xml" artifactTypeEnumId="AT_XML_SCREEN"/>
66
67 <!-- Essential Business Services --> 48 <!-- Essential Business Services -->
68 <moqui.security.ArtifactGroupMember artifactGroupId="McpBusinessServices" artifactName="mantle.order.OrderServices.create#Order" artifactTypeEnumId="AT_SERVICE"/> 49 <moqui.security.ArtifactGroupMember artifactGroupId="McpBusinessServices" artifactName="mantle.order.OrderServices.create#Order" artifactTypeEnumId="AT_SERVICE"/>
69 <moqui.security.ArtifactGroupMember artifactGroupId="McpBusinessServices" artifactName="mantle.party.PartyServices.find#Party" artifactTypeEnumId="AT_SERVICE"/> 50 <moqui.security.ArtifactGroupMember artifactGroupId="McpBusinessServices" artifactName="mantle.party.PartyServices.find#Party" artifactTypeEnumId="AT_SERVICE"/>
...@@ -113,9 +94,9 @@ ...@@ -113,9 +94,9 @@
113 <!-- MCP Artifact Authz --> 94 <!-- MCP Artifact Authz -->
114 <moqui.security.ArtifactAuthz userGroupId="McpUser" artifactGroupId="McpServices" authzTypeEnumId="AUTHZT_ALLOW" authzActionEnumId="AUTHZA_ALL"/> 95 <moqui.security.ArtifactAuthz userGroupId="McpUser" artifactGroupId="McpServices" authzTypeEnumId="AUTHZT_ALLOW" authzActionEnumId="AUTHZA_ALL"/>
115 <moqui.security.ArtifactAuthz userGroupId="McpUser" artifactGroupId="McpRestPaths" authzTypeEnumId="AUTHZT_ALLOW" authzActionEnumId="AUTHZA_ALL"/> 96 <moqui.security.ArtifactAuthz userGroupId="McpUser" artifactGroupId="McpRestPaths" authzTypeEnumId="AUTHZT_ALLOW" authzActionEnumId="AUTHZA_ALL"/>
97 <moqui.security.ArtifactAuthz userGroupId="McpUser" artifactGroupId="McpScreens" authzTypeEnumId="AUTHZT_ALLOW" authzActionEnumId="AUTHZA_VIEW"/>
116 <!-- 98 <!--
117 <moqui.security.ArtifactAuthz userGroupId="McpUser" artifactGroupId="McpScreenTransitions" authzTypeEnumId="AUTHZT_ALLOW" authzActionEnumId="AUTHZA_ALL"/> 99 <moqui.security.ArtifactAuthz userGroupId="McpUser" artifactGroupId="McpScreenTransitions" authzTypeEnumId="AUTHZT_ALLOW" authzActionEnumId="AUTHZA_ALL"/>
118 <moqui.security.ArtifactAuthz userGroupId="McpUser" artifactGroupId="McpScreens" authzTypeEnumId="AUTHZT_ALLOW" authzActionEnumId="AUTHZA_VIEW"/>
119 <moqui.security.ArtifactAuthz userGroupId="McpUser" artifactGroupId="McpScreenTools" authzTypeEnumId="AUTHZT_ALLOW" authzActionEnumId="AUTHZA_ALL"/> 100 <moqui.security.ArtifactAuthz userGroupId="McpUser" artifactGroupId="McpScreenTools" authzTypeEnumId="AUTHZT_ALLOW" authzActionEnumId="AUTHZA_ALL"/>
120 --> 101 -->
121 102
...@@ -129,7 +110,7 @@ ...@@ -129,7 +110,7 @@
129 <moqui.security.ArtifactAuthz userGroupId="ADMIN" artifactGroupId="McpScreens" authzTypeEnumId="AUTHZT_ALWAYS" authzActionEnumId="AUTHZA_ALL"/> 110 <moqui.security.ArtifactAuthz userGroupId="ADMIN" artifactGroupId="McpScreens" authzTypeEnumId="AUTHZT_ALWAYS" authzActionEnumId="AUTHZA_ALL"/>
130 <moqui.security.ArtifactAuthz userGroupId="ADMIN" artifactGroupId="McpScreenTools" authzTypeEnumId="AUTHZT_ALWAYS" authzActionEnumId="AUTHZA_ALL"/> 111 <moqui.security.ArtifactAuthz userGroupId="ADMIN" artifactGroupId="McpScreenTools" authzTypeEnumId="AUTHZT_ALWAYS" authzActionEnumId="AUTHZA_ALL"/>
131 <!-- Explicit permission for screen execution service --> 112 <!-- Explicit permission for screen execution service -->
132 <moqui.security.ArtifactAuthz userGroupId="ADMIN" artifactGroupId="McpServices" artifactName="McpServices.execute#ScreenAsMcpTool" authzTypeEnumId="AUTHZT_ALWAYS" authzActionEnumId="AUTHZA_ALL"/> 113 <!-- <moqui.security.ArtifactAuthz userGroupId="ADMIN" artifactGroupId="McpServices" artifactName="McpServices.execute#ScreenAsMcpTool" authzTypeEnumId="AUTHZT_ALWAYS" authzActionEnumId="AUTHZA_ALL"/> -->
133 114
134 <!-- MCP Business Group Authz --> 115 <!-- MCP Business Group Authz -->
135 <moqui.security.ArtifactAuthz userGroupId="MCP_BUSINESS" artifactGroupId="McpServices" authzTypeEnumId="AUTHZT_ALLOW" authzActionEnumId="AUTHZA_ALL"/> 116 <moqui.security.ArtifactAuthz userGroupId="MCP_BUSINESS" artifactGroupId="McpServices" authzTypeEnumId="AUTHZT_ALLOW" authzActionEnumId="AUTHZA_ALL"/>
...@@ -155,4 +136,4 @@ ...@@ -155,4 +136,4 @@
155 <moqui.security.UserGroupMember userGroupId="MCP_BUSINESS" userId="ORG_ZIZI_JD" fromDate="2025-01-01 00:00:00.000"/> 136 <moqui.security.UserGroupMember userGroupId="MCP_BUSINESS" userId="ORG_ZIZI_JD" fromDate="2025-01-01 00:00:00.000"/>
156 <moqui.security.UserGroupMember userGroupId="MCP_BUSINESS" userId="ORG_ZIZI_BD" fromDate="2025-01-01 00:00:00.000"/> 137 <moqui.security.UserGroupMember userGroupId="MCP_BUSINESS" userId="ORG_ZIZI_BD" fromDate="2025-01-01 00:00:00.000"/>
157 --> 138 -->
158 </entity-facade-xml>
...\ No newline at end of file ...\ No newline at end of file
139 </entity-facade-xml>
......
...@@ -74,13 +74,13 @@ class CustomScreenTestImpl implements McpScreenTest { ...@@ -74,13 +74,13 @@ class CustomScreenTestImpl implements McpScreenTest {
74 * instead of the framework's version that has null contextPath issues 74 * instead of the framework's version that has null contextPath issues
75 */ 75 */
76 protected WebFacade createWebFacade(ExecutionContextFactoryImpl ecfi, Map<String, Object> parameters, 76 protected WebFacade createWebFacade(ExecutionContextFactoryImpl ecfi, Map<String, Object> parameters,
77 Map<String, Object> sessionAttributes, String requestMethod) { 77 Map<String, Object> sessionAttributes, String requestMethod, String screenPath) {
78 if (logger.isDebugEnabled()) { 78 if (logger.isDebugEnabled()) {
79 logger.debug("CustomScreenTestImpl.createWebFacade() called with parameters: ${parameters?.keySet()}, sessionAttributes: ${sessionAttributes?.keySet()}, requestMethod: ${requestMethod}") 79 logger.debug("CustomScreenTestImpl.createWebFacade() called with parameters: ${parameters?.keySet()}, sessionAttributes: ${sessionAttributes?.keySet()}, requestMethod: ${requestMethod}, screenPath: ${screenPath}")
80 } 80 }
81 81
82 // Use our MCP component's WebFacadeStub which properly handles null contextPath 82 // Use our MCP component's WebFacadeStub which properly handles null contextPath
83 return new org.moqui.mcp.WebFacadeStub(ecfi, parameters, sessionAttributes, requestMethod) 83 return new org.moqui.mcp.WebFacadeStub(ecfi, parameters, sessionAttributes, requestMethod, screenPath)
84 } 84 }
85 85
86 @Override 86 @Override
...@@ -222,8 +222,22 @@ class CustomScreenTestImpl implements McpScreenTest { ...@@ -222,8 +222,22 @@ class CustomScreenTestImpl implements McpScreenTest {
222 @Override void run() { 222 @Override void run() {
223 try { 223 try {
224 ExecutionContextImpl threadEci = ecfi.getEci() 224 ExecutionContextImpl threadEci = ecfi.getEci()
225 if (loginSubject != null) threadEci.userFacade.internalLoginSubject(loginSubject) 225 if (loginSubject != null) {
226 else if (username != null && !username.isEmpty()) threadEci.userFacade.internalLoginUser(username) 226 logger.info("CustomScreenTestRender: Login subject for ${username}")
227 threadEci.userFacade.internalLoginSubject(loginSubject)
228 } else if (username != null && !username.isEmpty()) {
229 logger.info("CustomScreenTestRender: Login user ${username}")
230 threadEci.userFacade.internalLoginUser(username)
231 } else {
232 logger.warn("CustomScreenTestRender: No user to login!")
233 }
234
235 if (threadEci.userFacade.userId) {
236 logger.info("CustomScreenTestRender: Logged in as ${threadEci.userFacade.username} (${threadEci.userFacade.userId})")
237 } else {
238 logger.warn("CustomScreenTestRender: Failed to login, userId is null")
239 }
240
227 if (authzDisabled) threadEci.artifactExecutionFacade.disableAuthz() 241 if (authzDisabled) threadEci.artifactExecutionFacade.disableAuthz()
228 // as this is used for server-side transition calls don't do tarpit checks 242 // as this is used for server-side transition calls don't do tarpit checks
229 threadEci.artifactExecutionFacade.disableTarpit() 243 threadEci.artifactExecutionFacade.disableTarpit()
...@@ -253,7 +267,7 @@ class CustomScreenTestImpl implements McpScreenTest { ...@@ -253,7 +267,7 @@ class CustomScreenTestImpl implements McpScreenTest {
253 ContextStack cs = eci.getContext() 267 ContextStack cs = eci.getContext()
254 cs.push() 268 cs.push()
255 // create the WebFacadeStub using our custom method 269 // create the WebFacadeStub using our custom method
256 org.moqui.mcp.WebFacadeStub wfs = (org.moqui.mcp.WebFacadeStub) csti.createWebFacade(csti.ecfi, stri.parameters, csti.sessionAttributes, stri.requestMethod) 270 org.moqui.mcp.WebFacadeStub wfs = (org.moqui.mcp.WebFacadeStub) csti.createWebFacade(csti.ecfi, stri.parameters, csti.sessionAttributes, stri.requestMethod, stri.screenPath)
257 // set stub on eci, will also put parameters in the context 271 // set stub on eci, will also put parameters in the context
258 eci.setWebFacade(wfs) 272 eci.setWebFacade(wfs)
259 // make the ScreenRender 273 // make the ScreenRender
...@@ -277,15 +291,18 @@ class CustomScreenTestImpl implements McpScreenTest { ...@@ -277,15 +291,18 @@ class CustomScreenTestImpl implements McpScreenTest {
277 291
278 // do the render 292 // do the render
279 try { 293 try {
294 logger.info("Starting render for ${stri.screenPath} with root ${csti.rootScreenLocation}")
280 screenRender.render(wfs.getRequest(), wfs.getResponse()) 295 screenRender.render(wfs.getRequest(), wfs.getResponse())
281 // get the response text from the WebFacadeStub 296 // get the response text from the WebFacadeStub
282 stri.outputString = wfs.getResponseText() 297 stri.outputString = wfs.getResponseText()
283 stri.jsonObj = wfs.getResponseJsonObj() 298 stri.jsonObj = wfs.getResponseJsonObj()
299 logger.info("Render finished. Output length: ${stri.outputString?.length()}, JSON: ${stri.jsonObj != null}")
284 } catch (Throwable t) { 300 } catch (Throwable t) {
285 String errMsg = "Exception in render of ${stri.screenPath}: ${t.toString()}" 301 String errMsg = "Exception in render of ${stri.screenPath}: ${t.toString()}"
286 logger.warn(errMsg, t) 302 logger.warn(errMsg, t)
287 stri.errorMessages.add(errMsg) 303 stri.errorMessages.add(errMsg)
288 csti.errorCount++ 304 csti.errorCount++
305 if (stri.outputString == null) stri.outputString = "RENDER_EXCEPTION: " + errMsg
289 } 306 }
290 // calc renderTime 307 // calc renderTime
291 stri.renderTime = System.currentTimeMillis() - startTime 308 stri.renderTime = System.currentTimeMillis() - startTime
...@@ -339,4 +356,4 @@ class CustomScreenTestImpl implements McpScreenTest { ...@@ -339,4 +356,4 @@ class CustomScreenTestImpl implements McpScreenTest {
339 return outputString.matches(regex) 356 return outputString.matches(regex)
340 } 357 }
341 } 358 }
342 }
...\ No newline at end of file ...\ No newline at end of file
359 }
......
...@@ -275,7 +275,25 @@ class WebFacadeStub implements WebFacade { ...@@ -275,7 +275,25 @@ class WebFacadeStub implements WebFacade {
275 } 275 }
276 276
277 // Helper methods for ScreenTestImpl 277 // Helper methods for ScreenTestImpl
278 String getResponseText() { return responseText } 278 String getResponseText() {
279 if (responseText != null) {
280 logger.info("getResponseText: returning responseText (length: ${responseText.length()})")
281 return responseText
282 }
283 if (httpServletResponse instanceof MockHttpServletResponse) {
284 // Flush the writer to ensure all content is captured
285 try {
286 httpServletResponse.getWriter().flush()
287 } catch (IOException e) {
288 logger.warn("Error flushing response writer: ${e.message}")
289 }
290 def content = ((MockHttpServletResponse) httpServletResponse).getResponseContent()
291 logger.info("getResponseText: returning content from mock response (length: ${content?.length() ?: 0})")
292 return content
293 }
294 logger.warn("getResponseText: httpServletResponse is not MockHttpServletResponse: ${httpServletResponse?.getClass()?.getName()}")
295 return null
296 }
279 Object getResponseJsonObj() { return responseJsonObj } 297 Object getResponseJsonObj() { return responseJsonObj }
280 298
281 // Mock HTTP classes 299 // Mock HTTP classes
...@@ -564,4 +582,4 @@ class WebFacadeStub implements WebFacade { ...@@ -564,4 +582,4 @@ class WebFacadeStub implements WebFacade {
564 @Override String getResponseCharacterEncoding() { return "UTF-8" } 582 @Override String getResponseCharacterEncoding() { return "UTF-8" }
565 @Override void setResponseCharacterEncoding(String encoding) {} 583 @Override void setResponseCharacterEncoding(String encoding) {}
566 } 584 }
567 }
...\ No newline at end of file ...\ No newline at end of file
585 }
......
...@@ -13,48 +13,53 @@ ...@@ -13,48 +13,53 @@
13 */ 13 */
14 package org.moqui.mcp.test 14 package org.moqui.mcp.test
15 15
16 import org.junit.jupiter.api.AfterAll
17 import org.junit.jupiter.api.BeforeAll
18 import org.junit.jupiter.api.Test
19 import org.junit.jupiter.api.DisplayName
20 import org.junit.jupiter.api.MethodOrderer
21 import org.junit.jupiter.api.Order
22 import org.junit.jupiter.api.TestMethodOrder
23 import org.moqui.Moqui 16 import org.moqui.Moqui
17 import org.moqui.context.ExecutionContext
18 import spock.lang.Shared
19 import spock.lang.Specification
20 import spock.lang.Stepwise
24 21
25 @DisplayName("MCP Test Suite") 22 @Stepwise
26 @TestMethodOrder(MethodOrderer.OrderAnnotation.class) 23 class McpTestSuite extends Specification {
27 class McpTestSuite {
28 24
29 static SimpleMcpClient client 25 @Shared
30 static boolean criticalTestFailed = false 26 ExecutionContext ec
27
28 @Shared
29 SimpleMcpClient client
30
31 @Shared
32 boolean criticalTestFailed = false
31 33
32 @BeforeAll 34 def setupSpec() {
33 static void setupMoqui() {
34 // Initialize Moqui framework for testing 35 // Initialize Moqui framework for testing
35 System.setProperty('moqui.runtime', '../runtime') 36 // Note: moqui.runtime is set by build.gradle
36 System.setProperty('moqui.conf', 'MoquiConf.xml') 37
37 System.setProperty('moqui.init.static', 'true') 38 // Clear moqui.conf to ensure we use the runtime's MoquiDevConf.xml instead of component's minimal conf
39 //System.clearProperty('moqui.conf')
38 40
41 // System.setProperty('moqui.init.static', 'true')
42
43 ec = Moqui.getExecutionContext()
44
39 // Initialize MCP client 45 // Initialize MCP client
40 client = new SimpleMcpClient() 46 client = new SimpleMcpClient()
41 } 47 }
42 48
43 @AfterAll 49 def cleanupSpec() {
44 static void cleanup() {
45 if (client) { 50 if (client) {
46 client.closeSession() 51 client.closeSession()
47 } 52 }
53 if (ec) {
54 ec.destroy()
55 }
48 } 56 }
49 57
50 @Test 58 def "Test Internal Service Direct Call"() {
51 @Order(1)
52 @DisplayName("Test Internal Service Direct Call")
53 void testInternalServiceDirectCall() {
54 println "🔧 Testing Internal Service Direct Call" 59 println "🔧 Testing Internal Service Direct Call"
60 println "📂 Runtime Path: ${System.getProperty('moqui.runtime')}"
61 println "📂 Conf Path: ${System.getProperty('moqui.conf')}"
55 62
56 // Try to get ExecutionContext to verify if we are running in-container
57 def ec = Moqui.getExecutionContext()
58 if (ec == null) { 63 if (ec == null) {
59 println "⚠️ No ExecutionContext available - skipping internal service test (running in external client mode)" 64 println "⚠️ No ExecutionContext available - skipping internal service test (running in external client mode)"
60 return 65 return
...@@ -62,7 +67,17 @@ class McpTestSuite { ...@@ -62,7 +67,17 @@ class McpTestSuite {
62 67
63 println "✅ ExecutionContext available, testing service directly" 68 println "✅ ExecutionContext available, testing service directly"
64 69
70 // Login as mcp-user to ensure we have a valid user context for the screen render
65 try { 71 try {
72 ec.user.internalLoginUser("mcp-user")
73 println "✅ Logged in as mcp-user"
74 } catch (Throwable t) {
75 println "❌ Failed to login as mcp-user: ${t.message}"
76 t.printStackTrace()
77 // Continue to see if service call works (it might fail auth but shouldn't crash)
78 }
79
80 when:
66 // Call the service directly 81 // Call the service directly
67 def result = ec.service.sync().name("McpServices.execute#ScreenAsMcpTool") 82 def result = ec.service.sync().name("McpServices.execute#ScreenAsMcpTool")
68 .parameters([ 83 .parameters([
...@@ -73,13 +88,14 @@ class McpTestSuite { ...@@ -73,13 +88,14 @@ class McpTestSuite {
73 .call() 88 .call()
74 89
75 println "✅ Service returned result: ${result}" 90 println "✅ Service returned result: ${result}"
76 91
92 then:
77 // Verify result structure 93 // Verify result structure
78 assert result != null 94 result != null
79 assert result.result != null 95 result.result != null
80 assert result.result.type == "text" 96 result.result.type == "text"
81 assert result.result.screenPath == "component://moqui-mcp-2/screen/McpTestScreen.xml" 97 result.result.screenPath == "component://moqui-mcp-2/screen/McpTestScreen.xml"
82 assert !result.result.isError 98 !result.result.isError
83 99
84 // Verify content 100 // Verify content
85 def text = result.result.text 101 def text = result.result.text
...@@ -88,67 +104,56 @@ class McpTestSuite { ...@@ -88,67 +104,56 @@ class McpTestSuite {
88 println "🎉 SUCCESS: Found test message in direct render output" 104 println "🎉 SUCCESS: Found test message in direct render output"
89 } else { 105 } else {
90 println "⚠️ Test message not found in output (or output empty)" 106 println "⚠️ Test message not found in output (or output empty)"
91 // Note: We don't fail the critical test on empty output yet as it might be an environment quirk,
92 // but if we wanted to enforce it, we would throw AssertionError here.
93 } 107 }
94 108
95 } catch (Exception e) { 109 cleanup:
96 println "❌ Service call failed: ${e.message}" 110 ec.user.logoutUser()
97 e.printStackTrace()
98 criticalTestFailed = true
99 throw e
100 } catch (AssertionError e) {
101 println "❌ Service assertion failed: ${e.message}"
102 criticalTestFailed = true
103 throw e
104 }
105 } 111 }
106 112
107 @Test 113 def "Test MCP Server Connectivity"() {
108 @Order(2)
109 @DisplayName("Test MCP Server Connectivity")
110 void testMcpServerConnectivity() {
111 if (criticalTestFailed) return 114 if (criticalTestFailed) return
115
112 println "🔌 Testing MCP Server Connectivity" 116 println "🔌 Testing MCP Server Connectivity"
113 117
118 expect:
114 // Test session initialization first 119 // Test session initialization first
115 assert client.initializeSession() : "MCP session should initialize successfully" 120 client.initializeSession()
116 println "✅ Session initialized successfully" 121 println "✅ Session initialized successfully"
117 122
118 // Test server ping 123 // Test server ping
119 assert client.ping() : "MCP server should respond to ping" 124 client.ping()
120 println "✅ Server ping successful" 125 println "✅ Server ping successful"
121 126
122 // Test tool listing 127 // Test tool listing
123 def tools = client.listTools() 128 def tools = client.listTools()
124 assert tools != null : "Tools list should not be null" 129 tools != null
125 assert tools.size() > 0 : "Should have at least one tool available" 130 tools.size() > 0
126 println "✅ Found ${tools.size()} available tools" 131 println "✅ Found ${tools.size()} available tools"
127 } 132 }
128 133
129 @Test 134 def "Test PopCommerce Product Search"() {
130 @Order(3)
131 @DisplayName("Test PopCommerce Product Search")
132 void testPopCommerceProductSearch() {
133 if (criticalTestFailed) return 135 if (criticalTestFailed) return
136
134 println "🛍️ Testing PopCommerce Product Search" 137 println "🛍️ Testing PopCommerce Product Search"
135 138
139 when:
136 // Use SimpleScreens search screen directly (PopCommerce/SimpleScreens reuses this) 140 // Use SimpleScreens search screen directly (PopCommerce/SimpleScreens reuses this)
137 // Pass "Blue" as queryString to find blue products 141 // Pass "Blue" as queryString to find blue products
138 def result = client.callScreen("component://SimpleScreens/screen/SimpleScreens/Catalog/Search.xml", [queryString: "Blue"]) 142 def result = client.callScreen("component://SimpleScreens/screen/SimpleScreens/Catalog/Search.xml", [queryString: "Blue"])
139 143
140 assert result != null : "Screen call result should not be null" 144 then:
141 assert result instanceof Map : "Screen result should be a map" 145 result != null
146 result instanceof Map
142 147
143 // Fail test if screen returns error 148 // Fail test if screen returns error
144 assert !result.containsKey('error') : "Screen call should not return error: ${result.error}" 149 !result.containsKey('error')
145 assert !result.isError : "Screen result should not have isError set to true" 150 !result.isError
146 151
147 println "✅ PopCommerce search screen accessed successfully" 152 println "✅ PopCommerce search screen accessed successfully"
148 153
149 // Check if we got content - fail test if no content 154 // Check if we got content - fail test if no content
150 def content = result.result?.content 155 def content = result.result?.content
151 assert content != null && content instanceof List && content.size() > 0 : "Screen should return content with blue products" 156 content != null && content instanceof List && content.size() > 0
152 println "✅ Screen returned content with ${content.size()} items" 157 println "✅ Screen returned content with ${content.size()} items"
153 158
154 def blueProductsFound = false 159 def blueProductsFound = false
...@@ -194,21 +199,21 @@ class McpTestSuite { ...@@ -194,21 +199,21 @@ class McpTestSuite {
194 } 199 }
195 200
196 // Fail test if no blue products were found 201 // Fail test if no blue products were found
197 assert blueProductsFound : "Should find at least one blue product (Demo with Variants Blue) in search results" 202 blueProductsFound
198 } 203 }
199 204
200 @Test 205 def "Test Customer Lookup"() {
201 @Order(4)
202 @DisplayName("Test Customer Lookup")
203 void testCustomerLookup() {
204 if (criticalTestFailed) return 206 if (criticalTestFailed) return
207
205 println "👤 Testing Customer Lookup" 208 println "👤 Testing Customer Lookup"
206 209
210 when:
207 // Use actual available screen - PartyList from mantle component 211 // Use actual available screen - PartyList from mantle component
208 def result = client.callScreen("component://mantle/screen/party/PartyList.xml", [:]) 212 def result = client.callScreen("component://mantle/screen/party/PartyList.xml", [:])
209 213
210 assert result != null : "Screen call result should not be null" 214 then:
211 assert result instanceof Map : "Screen result should be a map" 215 result != null
216 result instanceof Map
212 217
213 if (result.containsKey('error')) { 218 if (result.containsKey('error')) {
214 println "⚠️ Screen call returned error: ${result.error}" 219 println "⚠️ Screen call returned error: ${result.error}"
...@@ -233,18 +238,18 @@ class McpTestSuite { ...@@ -233,18 +238,18 @@ class McpTestSuite {
233 } 238 }
234 } 239 }
235 240
236 @Test 241 def "Test Complete Order Workflow"() {
237 @Order(5)
238 @DisplayName("Test Complete Order Workflow")
239 void testCompleteOrderWorkflow() {
240 if (criticalTestFailed) return 242 if (criticalTestFailed) return
243
241 println "🛒 Testing Complete Order Workflow" 244 println "🛒 Testing Complete Order Workflow"
242 245
246 when:
243 // Use actual available screen - OrderList from mantle component 247 // Use actual available screen - OrderList from mantle component
244 def result = client.callScreen("component://mantle/screen/order/OrderList.xml", [:]) 248 def result = client.callScreen("component://mantle/screen/order/OrderList.xml", [:])
245 249
246 assert result != null : "Screen call result should not be null" 250 then:
247 assert result instanceof Map : "Screen result should be a map" 251 result != null
252 result instanceof Map
248 253
249 if (result.containsKey('error')) { 254 if (result.containsKey('error')) {
250 println "⚠️ Screen call returned error: ${result.error}" 255 println "⚠️ Screen call returned error: ${result.error}"
...@@ -269,20 +274,20 @@ class McpTestSuite { ...@@ -269,20 +274,20 @@ class McpTestSuite {
269 } 274 }
270 } 275 }
271 276
272 @Test 277 def "Test MCP Screen Infrastructure"() {
273 @Order(6)
274 @DisplayName("Test MCP Screen Infrastructure")
275 void testMcpScreenInfrastructure() {
276 if (criticalTestFailed) return 278 if (criticalTestFailed) return
279
277 println "🖥️ Testing MCP Screen Infrastructure" 280 println "🖥️ Testing MCP Screen Infrastructure"
278 281
282 when:
279 // Test calling the MCP test screen with a custom message 283 // Test calling the MCP test screen with a custom message
280 def result = client.callScreen("component://moqui-mcp-2/screen/McpTestScreen.xml", [ 284 def result = client.callScreen("component://moqui-mcp-2/screen/McpTestScreen.xml", [
281 message: "MCP Test Successful!" 285 message: "MCP Test Successful!"
282 ]) 286 ])
283 287
284 assert result != null : "Screen call result should not be null" 288 then:
285 assert result instanceof Map : "Screen result should be a map" 289 result != null
290 result instanceof Map
286 291
287 if (result.containsKey('error')) { 292 if (result.containsKey('error')) {
288 println "⚠️ Screen call returned error: ${result.error}" 293 println "⚠️ Screen call returned error: ${result.error}"
...@@ -323,5 +328,4 @@ class McpTestSuite { ...@@ -323,5 +328,4 @@ class McpTestSuite {
323 } 328 }
324 } 329 }
325 } 330 }
326
327 } 331 }
......
1 # MCP Test Configuration 1 # MCP Test Configuration
2 # Test user credentials 2 # Test user credentials
3 test.user=john.sales 3 test.user=john.sales
4 test.password=opencode 4 test.password=moqui
5 test.mcp.url=http://localhost:8080/mcp 5 test.mcp.url=http://localhost:8080/mcp
6 6
7 # Test data 7 # Test data
......