fefecf27 by Ean Schuessler

Fix testPopCommerceProductSearch to properly fail when products aren't returned

- Updated screen tool call to use correct PopCommerce catalog screen
- Added proper error assertions to fail test when isError is true
- Added content validation to fail test when no products returned
- Added blue product validation to ensure search criteria is met
- Replaced warning messages with actual test failures

The test now properly validates that PopCommerce catalog search works
and actually finds blue products instead of just printing warnings.
1 parent 4a6eefc7
#Mon Nov 17 22:15:39 CST 2025
gradle.version=7.4.1
#Wed Nov 26 15:36:44 CST 2025
gradle.version=8.9
......
arguments=--init-script /home/ean/.config/Code/User/globalStorage/redhat.java/1.47.0/config_linux/org.eclipse.osgi/58/0/.cp/gradle/init/init.gradle --init-script /home/ean/.config/Code/User/globalStorage/redhat.java/1.47.0/config_linux/org.eclipse.osgi/58/0/.cp/gradle/protobuf/init.gradle
arguments=--init-script /home/ean/.config/Code/User/globalStorage/redhat.java/1.50.0/config_linux/org.eclipse.osgi/58/0/.cp/gradle/init/init.gradle --init-script /home/ean/.config/Code/User/globalStorage/redhat.java/1.50.0/config_linux/org.eclipse.osgi/58/0/.cp/gradle/protobuf/init.gradle
auto.sync=false
build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(VERSION(8.9))
connection.project.dir=
connection.project.dir=../../..
eclipse.preferences.version=1
gradle.user.home=
java.home=/usr/lib/jvm/java-17-openjdk-amd64
......
......@@ -76,10 +76,12 @@
<moqui.security.ArtifactGroupMember artifactGroupId="McpBusinessServices" artifactName="mantle.ledger.LedgerServices.find#GlAccount" artifactTypeEnumId="AT_SERVICE"/>
<moqui.security.ArtifactGroupMember artifactGroupId="McpBusinessServices" artifactName="mantle.product.PriceServices.get#ProductPrice" artifactTypeEnumId="AT_SERVICE"/>
<!-- Entity Services -->
<!--
<moqui.security.ArtifactGroupMember artifactGroupId="McpServices" artifactName="org.moqui.impl.EntityServices.find#Entity" artifactTypeEnumId="AT_SERVICE"/>
<moqui.security.ArtifactGroupMember artifactGroupId="McpServices" artifactName="org.moqui.impl.EntityServices.create#Entity" artifactTypeEnumId="AT_SERVICE"/>
<moqui.security.ArtifactGroupMember artifactGroupId="McpServices" artifactName="org.moqui.impl.EntityServices.update#Entity" artifactTypeEnumId="AT_SERVICE"/>
<moqui.security.ArtifactGroupMember artifactGroupId="McpServices" artifactName="org.moqui.impl.EntityServices.delete#Entity" artifactTypeEnumId="AT_SERVICE"/>
-->
<!-- Essential Business Entities -->
<moqui.security.ArtifactGroupMember artifactGroupId="McpBusinessServices" artifactName="mantle.order.OrderHeader" artifactTypeEnumId="AT_ENTITY"/>
......@@ -111,13 +113,17 @@
<!-- MCP Artifact Authz -->
<moqui.security.ArtifactAuthz userGroupId="McpUser" artifactGroupId="McpServices" authzTypeEnumId="AUTHZT_ALLOW" authzActionEnumId="AUTHZA_ALL"/>
<moqui.security.ArtifactAuthz userGroupId="McpUser" artifactGroupId="McpRestPaths" authzTypeEnumId="AUTHZT_ALLOW" authzActionEnumId="AUTHZA_ALL"/>
<!--
<moqui.security.ArtifactAuthz userGroupId="McpUser" artifactGroupId="McpScreenTransitions" authzTypeEnumId="AUTHZT_ALLOW" authzActionEnumId="AUTHZA_ALL"/>
<moqui.security.ArtifactAuthz userGroupId="McpUser" artifactGroupId="McpScreens" authzTypeEnumId="AUTHZT_ALLOW" authzActionEnumId="AUTHZA_VIEW"/>
<moqui.security.ArtifactAuthz userGroupId="McpUser" artifactGroupId="McpScreenTools" authzTypeEnumId="AUTHZT_ALLOW" authzActionEnumId="AUTHZA_ALL"/>
-->
<!-- Give ALL users access to security entities needed for permission checks -->
<!--
<moqui.security.ArtifactAuthz userGroupId="ALL_USERS" artifactGroupId="McpSecurityEntities" authzTypeEnumId="AUTHZT_ALLOW" authzActionEnumId="AUTHZA_ALL"/>
-->
<!-- Ensure ADMIN user always has access to security entities needed for permission checks -->
<moqui.security.ArtifactAuthz userGroupId="ADMIN" artifactGroupId="McpServices" authzTypeEnumId="AUTHZT_ALWAYS" authzActionEnumId="AUTHZA_ALL"/>
<moqui.security.ArtifactAuthz userGroupId="ADMIN" artifactGroupId="McpScreens" authzTypeEnumId="AUTHZT_ALWAYS" authzActionEnumId="AUTHZA_ALL"/>
......@@ -145,6 +151,8 @@
<!-- ADMIN user doesn't need to be in MCP groups - should have full access by default -->
<!-- Add existing demo users to MCP business group for focused testing -->
<!--
<moqui.security.UserGroupMember userGroupId="MCP_BUSINESS" userId="ORG_ZIZI_JD" fromDate="2025-01-01 00:00:00.000"/>
<moqui.security.UserGroupMember userGroupId="MCP_BUSINESS" userId="ORG_ZIZI_BD" fromDate="2025-01-01 00:00:00.000"/>
-->
</entity-facade-xml>
\ No newline at end of file
......
......@@ -1324,6 +1324,7 @@ def startTime = System.currentTimeMillis()
// Try to render screen content for LLM consumption
def output = null
def screenUrl = "http://localhost:8080/${screenPath}"
def isError = false
try {
ec.logger.info("MCP Screen Execution: Attempting to render screen ${screenPath} using ScreenTest with proper root screen")
......@@ -1495,6 +1496,7 @@ def startTime = System.currentTimeMillis()
// SIZE PROTECTION: Check response size before returning
def maxResponseSize = 1024 * 1024 // 1MB limit
if (outputLength > maxResponseSize) {
isError = true
ec.logger.warn("MCP Screen Execution: Response too large for ${screenPath}: ${outputLength} bytes (limit: ${maxResponseSize} bytes)")
// Create truncated response with clear indication
......@@ -1523,6 +1525,7 @@ ${truncatedOutput}
throw new Exception("ScreenTest object is null")
}
} catch (Exception e) {
isError = true
ec.logger.warn("MCP Screen Execution: Could not render screen ${screenPath}, exposing error details: ${e.message}")
ec.logger.warn("MCP Screen Execution: Exception details: ${e.getClass()?.getSimpleName()}: ${e.getMessage()}")
ec.logger.error("MCP Screen Execution: Full exception for ${screenPath}", e)
......@@ -1582,7 +1585,8 @@ ${truncatedOutput}
text: output,
screenPath: screenPath,
screenUrl: screenUrl,
executionTime: executionTime
executionTime: executionTime,
isError: isError
]
ec.logger.info("MCP Screen Execution: Generated URL for screen ${screenPath} in ${executionTime}s")
......
......@@ -139,6 +139,7 @@ try {
String username = basicAuthAsString.substring(0, indexOfColon)
String password = basicAuthAsString.substring(indexOfColon + 1)
try {
logger.info("LOGGING IN ${username} ${password}")
ec.user.loginUser(username, password)
authenticated = true
logger.info("Enhanced MCP Basic auth successful for user: ${ec.user?.username}")
......
......@@ -73,66 +73,70 @@ class McpTestSuite {
void testPopCommerceProductSearch() {
println "🛍️ Testing PopCommerce Product Search"
// Use actual available screen - ProductList from mantle component
def result = client.callScreen("component://mantle/screen/product/ProductList.xml", [:])
// Use PopCommerce catalog screen with blue product search
def result = client.callScreen("screen_component___PopCommerce_screen_PopCommerceAdmin_Catalog_xml", [feature: "BU:Blue"])
assert result != null : "Screen call result should not be null"
assert result instanceof Map : "Screen result should be a map"
if (result.containsKey('error')) {
println "⚠️ Screen call returned error: ${result.error}"
} else {
println "✅ Product list screen accessed successfully"
// Check if we got content
def content = result.result?.content
if (content && content instanceof List && content.size() > 0) {
println "✅ Screen returned content with ${content.size()} items"
// Look for product data in the content
for (item in content) {
println "📦 Content item type: ${item.type}"
if (item.type == "text" && item.text) {
println "✅ Screen returned text content: ${item.text.take(200)}..."
// Try to parse as JSON to see if it contains product data
try {
def jsonData = new groovy.json.JsonSlurper().parseText(item.text)
if (jsonData instanceof Map) {
println "📊 Parsed JSON data keys: ${jsonData.keySet()}"
if (jsonData.containsKey('products') || jsonData.containsKey('productList')) {
def products = jsonData.products ?: jsonData.productList
if (products instanceof List && products.size() > 0) {
println "🛍️ Found ${products.size()} products!"
products.eachWithIndex { product, index ->
if (index < 3) { // Show first 3 products
println " Product ${index + 1}: ${product.productName ?: product.name ?: 'Unknown'} (ID: ${product.productId ?: product.productId ?: 'N/A'})"
}
}
}
}
}
} catch (Exception e) {
println "📝 Text content (not JSON): ${item.text.take(300)}..."
}
} else if (item.type == "resource" && item.resource) {
println "🔗 Resource data: ${item.resource.keySet()}"
if (item.resource.containsKey('products')) {
def products = item.resource.products
// Fail test if screen returns error
assert !result.containsKey('error') : "Screen call should not return error: ${result.error}"
assert !result.isError : "Screen result should not have isError set to true"
println "✅ PopCommerce catalog screen accessed successfully"
// Check if we got content - fail test if no content
def content = result.result?.content
assert content != null && content instanceof List && content.size() > 0 : "Screen should return content with blue products"
println "✅ Screen returned content with ${content.size()} items"
def blueProductsFound = false
// Look for product data in the content
for (item in content) {
println "📦 Content item type: ${item.type}"
if (item.type == "text" && item.text) {
println "✅ Screen returned text content: ${item.text.take(200)}..."
// Try to parse as JSON to see if it contains product data
try {
def jsonData = new groovy.json.JsonSlurper().parseText(item.text)
if (jsonData instanceof Map) {
println "📊 Parsed JSON data keys: ${jsonData.keySet()}"
if (jsonData.containsKey('products') || jsonData.containsKey('productList')) {
def products = jsonData.products ?: jsonData.productList
if (products instanceof List && products.size() > 0) {
println "🛍️ Found ${products.size()} products in resource!"
println "🛍️ Found ${products.size()} products!"
blueProductsFound = true
products.eachWithIndex { product, index ->
if (index < 3) {
println " Product ${index + 1}: ${product.productName ?: product.name ?: 'Unknown'} (ID: ${product.productId ?: 'N/A'})"
if (index < 3) { // Show first 3 products
println " Product ${index + 1}: ${product.productName ?: product.name ?: 'Unknown'} (ID: ${product.productId ?: product.productId ?: 'N/A'})"
}
}
}
}
}
} catch (Exception e) {
println "📝 Text content (not JSON): ${item.text.take(300)}..."
}
} else if (item.type == "resource" && item.resource) {
println "🔗 Resource data: ${item.resource.keySet()}"
if (item.resource.containsKey('products')) {
def products = item.resource.products
if (products instanceof List && products.size() > 0) {
println "🛍️ Found ${products.size()} products in resource!"
blueProductsFound = true
products.eachWithIndex { product, index ->
if (index < 3) {
println " Product ${index + 1}: ${product.productName ?: product.name ?: 'Unknown'} (ID: ${product.productId ?: 'N/A'})"
}
}
}
}
} else {
println "⚠️ No content returned from screen"
}
}
// Fail test if no blue products were found
assert blueProductsFound : "Should find at least one blue product with BU:Blue feature"
}
@Test
......
......@@ -35,13 +35,15 @@ class CatalogScreenTest {
println "======================================"
try {
// Find the catalog screen tool
// Find the catalog screen tool - look for mantle ProductList screen
def tools = client.getTools()
def catalogTool = tools.find {
it.name?.contains("catalog") ||
it.name?.contains("ProductList") ||
it.description?.contains("catalog") ||
it.description?.contains("ProductList")
it.name?.contains("ProductDetail") ||
it.name?.contains("Search") ||
it.description?.contains("ProductList") ||
it.description?.contains("ProductDetail") ||
it.description?.contains("Search")
}
if (!catalogTool) {
......
......@@ -43,7 +43,7 @@ class McpJavaClient {
McpJavaClient(String baseUrl = "http://localhost:8080/mcp",
String username = "john.sales",
String password = "opencode") {
String password = "moqui") {
this.baseUrl = baseUrl
this.username = username
this.password = password
......