Convert McpTestSuite to Spock with @Shared ExecutionContext and update test infrastructure
Showing
7 changed files
with
136 additions
and
120 deletions
| ... | @@ -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 | ... | ... |
| ... | @@ -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 | ... | ... |
-
Please register or sign in to post a comment