Fix compilation error by creating MCP-specific screen test infrastructure
- Created McpScreenTest and McpScreenTestRender interfaces - Rewrote CustomScreenTestImpl to implement MCP-specific interfaces instead of extending framework's buggy ScreenTestImpl - Added all required interface methods for complete functionality - Resolves @Override annotation error on non-existent makeWebFacade method - Maintains MCP functionality while providing independent screen test capabilities
Showing
3 changed files
with
396 additions
and
7 deletions
| ... | @@ -13,19 +13,330 @@ | ... | @@ -13,19 +13,330 @@ |
| 13 | */ | 13 | */ |
| 14 | package org.moqui.mcp | 14 | package org.moqui.mcp |
| 15 | 15 | ||
| 16 | import org.moqui.impl.screen.ScreenTestImpl | 16 | import groovy.transform.CompileStatic |
| 17 | import org.moqui.impl.context.ExecutionContextFactoryImpl | 17 | import org.moqui.impl.context.ExecutionContextFactoryImpl |
| 18 | import org.moqui.impl.context.ExecutionContextImpl | ||
| 19 | import org.moqui.context.WebFacade | ||
| 20 | import org.moqui.util.ContextStack | ||
| 21 | import org.moqui.impl.screen.ScreenUrlInfo | ||
| 22 | import org.moqui.impl.screen.ScreenFacadeImpl | ||
| 23 | import org.moqui.impl.screen.ScreenDefinition | ||
| 24 | import org.moqui.screen.ScreenRender | ||
| 25 | import org.moqui.BaseArtifactException | ||
| 26 | import org.moqui.util.MNode | ||
| 27 | import org.slf4j.Logger | ||
| 28 | import org.slf4j.LoggerFactory | ||
| 18 | 29 | ||
| 19 | /** | 30 | /** |
| 20 | * Custom ScreenTest implementation for MCP access | 31 | * MCP-specific ScreenTest implementation for simulating screen web requests |
| 21 | * This provides the necessary web context for screen rendering in MCP environment | 32 | * This provides a proper web context for screen rendering in MCP environment |
| 33 | * using the MCP component's WebFacadeStub instead of the framework's buggy one | ||
| 22 | */ | 34 | */ |
| 23 | class CustomScreenTestImpl extends ScreenTestImpl { | 35 | @CompileStatic |
| 36 | class CustomScreenTestImpl implements McpScreenTest { | ||
| 24 | 37 | ||
| 38 | protected final static Logger logger = LoggerFactory.getLogger(CustomScreenTestImpl.class) | ||
| 39 | |||
| 40 | protected final ExecutionContextFactoryImpl ecfi | ||
| 41 | protected final ScreenFacadeImpl sfi | ||
| 42 | // see FtlTemplateRenderer.MoquiTemplateExceptionHandler, others | ||
| 43 | final List<String> errorStrings = ["[Template Error", "FTL stack trace", "Could not find subscreen or transition"] | ||
| 44 | |||
| 45 | protected String rootScreenLocation = null | ||
| 46 | protected ScreenDefinition rootScreenDef = null | ||
| 47 | protected String baseScreenPath = null | ||
| 48 | protected List<String> baseScreenPathList = null | ||
| 49 | protected ScreenDefinition baseScreenDef = null | ||
| 50 | |||
| 51 | protected String outputType = null | ||
| 52 | protected String characterEncoding = null | ||
| 53 | protected String macroTemplateLocation = null | ||
| 54 | protected String baseLinkUrl = null | ||
| 55 | protected String servletContextPath = null | ||
| 56 | protected String webappName = null | ||
| 57 | protected boolean skipJsonSerialize = false | ||
| 58 | protected static final String hostname = "localhost" | ||
| 59 | |||
| 60 | long renderCount = 0, errorCount = 0, totalChars = 0, startTime = System.currentTimeMillis() | ||
| 61 | |||
| 62 | final Map<String, Object> sessionAttributes = [:] | ||
| 63 | |||
| 25 | CustomScreenTestImpl(ExecutionContextFactoryImpl ecfi) { | 64 | CustomScreenTestImpl(ExecutionContextFactoryImpl ecfi) { |
| 26 | super(ecfi) | 65 | this.ecfi = ecfi |
| 66 | sfi = ecfi.screenFacade | ||
| 67 | |||
| 68 | // init default webapp, root screen | ||
| 69 | webappName('webroot') | ||
| 70 | } | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Create WebFacade using our properly implemented WebFacadeStub | ||
| 74 | * instead of the framework's version that has null contextPath issues | ||
| 75 | */ | ||
| 76 | protected WebFacade createWebFacade(ExecutionContextFactoryImpl ecfi, Map<String, Object> parameters, | ||
| 77 | Map<String, Object> sessionAttributes, String requestMethod) { | ||
| 78 | if (logger.isDebugEnabled()) { | ||
| 79 | logger.debug("CustomScreenTestImpl.createWebFacade() called with parameters: ${parameters?.keySet()}, sessionAttributes: ${sessionAttributes?.keySet()}, requestMethod: ${requestMethod}") | ||
| 80 | } | ||
| 81 | |||
| 82 | // Use our MCP component's WebFacadeStub which properly handles null contextPath | ||
| 83 | return new org.moqui.mcp.WebFacadeStub(ecfi, parameters, sessionAttributes, requestMethod) | ||
| 84 | } | ||
| 85 | |||
| 86 | @Override | ||
| 87 | McpScreenTest rootScreen(String screenLocation) { | ||
| 88 | rootScreenLocation = screenLocation | ||
| 89 | rootScreenDef = sfi.getScreenDefinition(rootScreenLocation) | ||
| 90 | if (rootScreenDef == null) throw new IllegalArgumentException("Root screen not found: ${rootScreenLocation}") | ||
| 91 | baseScreenDef = rootScreenDef | ||
| 92 | return this | ||
| 27 | } | 93 | } |
| 28 | 94 | ||
| 29 | // Use the default makeWebFacade from ScreenTestImpl | 95 | @Override |
| 30 | // It should work for basic screen rendering in MCP context | 96 | McpScreenTest baseScreenPath(String screenPath) { |
| 97 | if (!rootScreenLocation) throw new BaseArtifactException("No rootScreen specified") | ||
| 98 | baseScreenPath = screenPath | ||
| 99 | if (baseScreenPath.endsWith("/")) baseScreenPath = baseScreenPath.substring(0, baseScreenPath.length() - 1) | ||
| 100 | if (baseScreenPath) { | ||
| 101 | baseScreenPathList = ScreenUrlInfo.parseSubScreenPath(rootScreenDef, rootScreenDef, [], baseScreenPath, null, sfi) | ||
| 102 | if (baseScreenPathList == null) throw new BaseArtifactException("Error in baseScreenPath, could find not base screen path ${baseScreenPath} under ${rootScreenDef.location}") | ||
| 103 | for (String screenName in baseScreenPathList) { | ||
| 104 | ScreenDefinition.SubscreensItem ssi = baseScreenDef.getSubscreensItem(screenName) | ||
| 105 | if (ssi == null) throw new BaseArtifactException("Error in baseScreenPath, could not find ${screenName} under ${baseScreenDef.location}") | ||
| 106 | baseScreenDef = sfi.getScreenDefinition(ssi.location) | ||
| 107 | if (baseScreenDef == null) throw new BaseArtifactException("Error in baseScreenPath, could not find screen ${screenName} at ${ssi.location}") | ||
| 108 | } | ||
| 109 | } | ||
| 110 | return this | ||
| 111 | } | ||
| 112 | |||
| 113 | @Override McpScreenTest renderMode(String outputType) { this.outputType = outputType; return this } | ||
| 114 | @Override McpScreenTest encoding(String characterEncoding) { this.characterEncoding = characterEncoding; return this } | ||
| 115 | @Override McpScreenTest macroTemplate(String macroTemplateLocation) { this.macroTemplateLocation = macroTemplateLocation; return this } | ||
| 116 | @Override McpScreenTest baseLinkUrl(String baseLinkUrl) { this.baseLinkUrl = baseLinkUrl; return this } | ||
| 117 | @Override McpScreenTest servletContextPath(String scp) { this.servletContextPath = scp; return this } | ||
| 118 | @Override McpScreenTest skipJsonSerialize(boolean skip) { this.skipJsonSerialize = skip; return this } | ||
| 119 | |||
| 120 | @Override | ||
| 121 | McpScreenTest webappName(String wan) { | ||
| 122 | webappName = wan | ||
| 123 | |||
| 124 | // set a default root screen based on config for "localhost" | ||
| 125 | MNode webappNode = ecfi.getWebappNode(webappName) | ||
| 126 | for (MNode rootScreenNode in webappNode.children("root-screen")) { | ||
| 127 | if (hostname.matches(rootScreenNode.attribute('host'))) { | ||
| 128 | String rsLoc = rootScreenNode.attribute('location') | ||
| 129 | rootScreen(rsLoc) | ||
| 130 | break | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | return this | ||
| 135 | } | ||
| 136 | |||
| 137 | @Override | ||
| 138 | List<String> getNoRequiredParameterPaths(Set<String> screensToSkip) { | ||
| 139 | if (!rootScreenLocation) throw new IllegalStateException("No rootScreen specified") | ||
| 140 | |||
| 141 | List<String> noReqParmLocations = baseScreenDef.nestedNoReqParmLocations("", screensToSkip) | ||
| 142 | // logger.info("======= rootScreenLocation=${rootScreenLocation}\nbaseScreenPath=${baseScreenPath}\nbaseScreenDef: ${baseScreenDef.location}\nnoReqParmLocations: ${noReqParmLocations}") | ||
| 143 | return noReqParmLocations | ||
| 144 | } | ||
| 145 | |||
| 146 | @Override | ||
| 147 | void renderAll(List<String> screenPathList, Map<String, Object> parameters, String requestMethod) { | ||
| 148 | // NOTE: using single thread for now, doesn't actually make a lot of difference in overall test run time | ||
| 149 | int threads = 1 | ||
| 150 | if (threads == 1) { | ||
| 151 | for (String screenPath in screenPathList) { | ||
| 152 | McpScreenTestRender str = render(screenPath, parameters, requestMethod) | ||
| 153 | logger.info("Rendered ${screenPath} in ${str.getRenderTime()}ms, ${str.getOutput()?.length()} characters") | ||
| 154 | } | ||
| 155 | } else { | ||
| 156 | ExecutionContextImpl eci = ecfi.getEci() | ||
| 157 | ArrayList<java.util.concurrent.Future> threadList = new ArrayList<java.util.concurrent.Future>(threads) | ||
| 158 | int screenPathListSize = screenPathList.size() | ||
| 159 | for (int si = 0; si < screenPathListSize; si++) { | ||
| 160 | String screenPath = (String) screenPathList.get(si) | ||
| 161 | threadList.add(eci.runAsync({ | ||
| 162 | McpScreenTestRender str = render(screenPath, parameters, requestMethod) | ||
| 163 | logger.info("Rendered ${screenPath} in ${str.getRenderTime()}ms, ${str.getOutput()?.length()} characters") | ||
| 164 | })) | ||
| 165 | if (threadList.size() == threads || (si + 1) == screenPathList.size()) { | ||
| 166 | for (int i = 0; i < threadList.size(); i++) { ((java.util.concurrent.Future) threadList.get(i)).get() } | ||
| 167 | threadList.clear() | ||
| 168 | } | ||
| 169 | } | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | long getRenderCount() { return renderCount } | ||
| 174 | long getErrorCount() { return errorCount } | ||
| 175 | long getRenderTotalChars() { return totalChars } | ||
| 176 | long getStartTime() { return startTime } | ||
| 177 | |||
| 178 | /** | ||
| 179 | * Override render method to use our custom ScreenTestRenderImpl | ||
| 180 | */ | ||
| 181 | @Override | ||
| 182 | McpScreenTestRender render(String screenPath, Map<String, Object> parameters, String requestMethod) { | ||
| 183 | if (!rootScreenLocation) throw new IllegalArgumentException("No rootScreenLocation specified") | ||
| 184 | return new CustomScreenTestRenderImpl(this, screenPath, parameters, requestMethod).render() | ||
| 185 | } | ||
| 186 | |||
| 187 | /** | ||
| 188 | * Custom ScreenTestRenderImpl that uses our WebFacadeStub | ||
| 189 | */ | ||
| 190 | @CompileStatic | ||
| 191 | static class CustomScreenTestRenderImpl implements McpScreenTestRender { | ||
| 192 | protected final CustomScreenTestImpl sti | ||
| 193 | String screenPath = (String) null | ||
| 194 | Map<String, Object> parameters = [:] | ||
| 195 | String requestMethod = (String) null | ||
| 196 | |||
| 197 | ScreenRender screenRender = (ScreenRender) null | ||
| 198 | String outputString = (String) null | ||
| 199 | Object jsonObj = null | ||
| 200 | long renderTime = 0 | ||
| 201 | Map postRenderContext = (Map) null | ||
| 202 | protected List<String> errorMessages = [] | ||
| 203 | |||
| 204 | CustomScreenTestRenderImpl(CustomScreenTestImpl sti, String screenPath, Map<String, Object> parameters, String requestMethod) { | ||
| 205 | this.sti = sti | ||
| 206 | this.screenPath = screenPath | ||
| 207 | if (parameters != null) this.parameters.putAll(parameters) | ||
| 208 | this.requestMethod = requestMethod | ||
| 209 | } | ||
| 210 | |||
| 211 | McpScreenTestRender render() { | ||
| 212 | // render in separate thread with an independent ExecutionContext so it doesn't muck up the current one | ||
| 213 | ExecutionContextFactoryImpl ecfi = sti.ecfi | ||
| 214 | ExecutionContextImpl localEci = ecfi.getEci() | ||
| 215 | String username = localEci.userFacade.getUsername() | ||
| 216 | org.apache.shiro.subject.Subject loginSubject = localEci.userFacade.getCurrentSubject() | ||
| 217 | boolean authzDisabled = localEci.artifactExecutionFacade.getAuthzDisabled() | ||
| 218 | CustomScreenTestRenderImpl stri = this | ||
| 219 | Throwable threadThrown = null | ||
| 220 | |||
| 221 | Thread newThread = new Thread("CustomScreenTestRender") { | ||
| 222 | @Override void run() { | ||
| 223 | try { | ||
| 224 | ExecutionContextImpl threadEci = ecfi.getEci() | ||
| 225 | if (loginSubject != null) threadEci.userFacade.internalLoginSubject(loginSubject) | ||
| 226 | else if (username != null && !username.isEmpty()) threadEci.userFacade.internalLoginUser(username) | ||
| 227 | if (authzDisabled) threadEci.artifactExecutionFacade.disableAuthz() | ||
| 228 | // as this is used for server-side transition calls don't do tarpit checks | ||
| 229 | threadEci.artifactExecutionFacade.disableTarpit() | ||
| 230 | renderInternalCustom(threadEci, stri) | ||
| 231 | threadEci.destroy() | ||
| 232 | } catch (Throwable t) { | ||
| 233 | threadThrown = t | ||
| 234 | } | ||
| 235 | } | ||
| 236 | } | ||
| 237 | newThread.start() | ||
| 238 | newThread.join() | ||
| 239 | if (threadThrown != null) throw threadThrown | ||
| 240 | return this | ||
| 241 | } | ||
| 242 | |||
| 243 | private static void renderInternalCustom(ExecutionContextImpl eci, CustomScreenTestRenderImpl stri) { | ||
| 244 | CustomScreenTestImpl csti = stri.sti | ||
| 245 | long startTime = System.currentTimeMillis() | ||
| 246 | |||
| 247 | // parse the screenPath | ||
| 248 | ArrayList<String> screenPathList = ScreenUrlInfo.parseSubScreenPath(csti.rootScreenDef, csti.baseScreenDef, | ||
| 249 | csti.baseScreenPathList, stri.screenPath, stri.parameters, csti.sfi) | ||
| 250 | if (screenPathList == null) throw new BaseArtifactException("Could not find screen path ${stri.screenPath} under base screen ${csti.baseScreenDef.location}") | ||
| 251 | |||
| 252 | // push the context | ||
| 253 | ContextStack cs = eci.getContext() | ||
| 254 | cs.push() | ||
| 255 | // 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) | ||
| 257 | // set stub on eci, will also put parameters in the context | ||
| 258 | eci.setWebFacade(wfs) | ||
| 259 | // make the ScreenRender | ||
| 260 | ScreenRender screenRender = csti.sfi.makeRender() | ||
| 261 | stri.screenRender = screenRender | ||
| 262 | // pass through various settings | ||
| 263 | if (csti.rootScreenLocation != null && csti.rootScreenLocation.length() > 0) screenRender.rootScreen(csti.rootScreenLocation) | ||
| 264 | if (csti.outputType != null && csti.outputType.length() > 0) screenRender.renderMode(csti.outputType) | ||
| 265 | if (csti.characterEncoding != null && csti.characterEncoding.length() > 0) screenRender.encoding(csti.characterEncoding) | ||
| 266 | if (csti.macroTemplateLocation != null && csti.macroTemplateLocation.length() > 0) screenRender.macroTemplate(csti.macroTemplateLocation) | ||
| 267 | if (csti.baseLinkUrl != null && csti.baseLinkUrl.length() > 0) screenRender.baseLinkUrl(csti.baseLinkUrl) | ||
| 268 | if (csti.servletContextPath != null && csti.servletContextPath.length() > 0) screenRender.servletContextPath(csti.servletContextPath) | ||
| 269 | screenRender.webappName(csti.webappName) | ||
| 270 | if (csti.skipJsonSerialize) { | ||
| 271 | // Set skipJsonSerialize on our WebFacadeStub | ||
| 272 | wfs.skipJsonSerialize = true | ||
| 273 | } | ||
| 274 | |||
| 275 | // set the screenPath | ||
| 276 | screenRender.screenPath(screenPathList) | ||
| 277 | |||
| 278 | // do the render | ||
| 279 | try { | ||
| 280 | screenRender.render(wfs.getRequest(), wfs.getResponse()) | ||
| 281 | // get the response text from the WebFacadeStub | ||
| 282 | stri.outputString = wfs.getResponseText() | ||
| 283 | stri.jsonObj = wfs.getResponseJsonObj() | ||
| 284 | } catch (Throwable t) { | ||
| 285 | String errMsg = "Exception in render of ${stri.screenPath}: ${t.toString()}" | ||
| 286 | logger.warn(errMsg, t) | ||
| 287 | stri.errorMessages.add(errMsg) | ||
| 288 | csti.errorCount++ | ||
| 289 | } | ||
| 290 | // calc renderTime | ||
| 291 | stri.renderTime = System.currentTimeMillis() - startTime | ||
| 292 | |||
| 293 | // pop the context stack, get rid of var space | ||
| 294 | stri.postRenderContext = cs.pop() | ||
| 295 | |||
| 296 | // check, pass through, error messages | ||
| 297 | if (eci.message.hasError()) { | ||
| 298 | stri.errorMessages.addAll(eci.message.getErrors()) | ||
| 299 | eci.message.clearErrors() | ||
| 300 | StringBuilder sb = new StringBuilder("Error messages from ${stri.screenPath}: ") | ||
| 301 | for (String errorMessage in stri.errorMessages) sb.append("\n").append(errorMessage) | ||
| 302 | logger.warn(sb.toString()) | ||
| 303 | csti.errorCount += stri.errorMessages.size() | ||
| 304 | } | ||
| 305 | |||
| 306 | // check for error strings in output | ||
| 307 | if (stri.outputString != null) for (String errorStr in csti.errorStrings) if (stri.outputString.contains(errorStr)) { | ||
| 308 | String errMsg = "Found error [${errorStr}] in output from ${stri.screenPath}" | ||
| 309 | stri.errorMessages.add(errMsg) | ||
| 310 | csti.errorCount++ | ||
| 311 | logger.warn(errMsg) | ||
| 312 | } | ||
| 313 | |||
| 314 | // update stats | ||
| 315 | csti.renderCount++ | ||
| 316 | if (stri.outputString != null) csti.totalChars += stri.outputString.length() | ||
| 317 | } | ||
| 318 | |||
| 319 | @Override ScreenRender getScreenRender() { return screenRender } | ||
| 320 | @Override String getOutput() { return outputString } | ||
| 321 | @Override Object getJsonObject() { return jsonObj } | ||
| 322 | @Override long getRenderTime() { return renderTime } | ||
| 323 | @Override Map getPostRenderContext() { return postRenderContext } | ||
| 324 | @Override List<String> getErrorMessages() { return errorMessages } | ||
| 325 | |||
| 326 | @Override | ||
| 327 | boolean assertContains(String text) { | ||
| 328 | if (!outputString) return false | ||
| 329 | return outputString.contains(text) | ||
| 330 | } | ||
| 331 | @Override | ||
| 332 | boolean assertNotContains(String text) { | ||
| 333 | if (!outputString) return true | ||
| 334 | return !outputString.contains(text) | ||
| 335 | } | ||
| 336 | @Override | ||
| 337 | boolean assertRegex(String regex) { | ||
| 338 | if (!outputString) return false | ||
| 339 | return outputString.matches(regex) | ||
| 340 | } | ||
| 341 | } | ||
| 31 | } | 342 | } |
| ... | \ No newline at end of file | ... | \ No newline at end of file | ... | ... |
| 1 | /* | ||
| 2 | * This software is in the public domain under CC0 1.0 Universal plus a | ||
| 3 | * Grant of Patent License. | ||
| 4 | * | ||
| 5 | * To the extent possible under law, author(s) have dedicated all | ||
| 6 | * copyright and related and neighboring rights to this software to the | ||
| 7 | * public domain worldwide. This software is distributed without any | ||
| 8 | * warranty. | ||
| 9 | * | ||
| 10 | * You should have received a copy of the CC0 Public Domain Dedication | ||
| 11 | * along with this software (see the LICENSE.md file). If not, see | ||
| 12 | * <http://creativecommons.org/publicdomain/zero/1.0/>. | ||
| 13 | */ | ||
| 14 | package org.moqui.mcp | ||
| 15 | |||
| 16 | import groovy.transform.CompileStatic | ||
| 17 | |||
| 18 | /** | ||
| 19 | * MCP-specific ScreenTest interface for simulating screen web requests | ||
| 20 | * This is separate from the core Moqui ScreenTest system and tailored for MCP needs | ||
| 21 | */ | ||
| 22 | @CompileStatic | ||
| 23 | interface McpScreenTest { | ||
| 24 | McpScreenTest rootScreen(String screenLocation) | ||
| 25 | McpScreenTest baseScreenPath(String screenPath) | ||
| 26 | McpScreenTest renderMode(String outputType) | ||
| 27 | McpScreenTest encoding(String characterEncoding) | ||
| 28 | McpScreenTest macroTemplate(String macroTemplateLocation) | ||
| 29 | McpScreenTest baseLinkUrl(String baseLinkUrl) | ||
| 30 | McpScreenTest servletContextPath(String scp) | ||
| 31 | McpScreenTest skipJsonSerialize(boolean skip) | ||
| 32 | McpScreenTest webappName(String wan) | ||
| 33 | |||
| 34 | McpScreenTestRender render(String screenPath, Map<String, Object> parameters, String requestMethod) | ||
| 35 | void renderAll(List<String> screenPathList, Map<String, Object> parameters, String requestMethod) | ||
| 36 | |||
| 37 | List<String> getNoRequiredParameterPaths(Set<String> screensToSkip) | ||
| 38 | |||
| 39 | long getRenderCount() | ||
| 40 | long getErrorCount() | ||
| 41 | long getRenderTotalChars() | ||
| 42 | long getStartTime() | ||
| 43 | } | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
| 1 | /* | ||
| 2 | * This software is in the public domain under CC0 1.0 Universal plus a | ||
| 3 | * Grant of Patent License. | ||
| 4 | * | ||
| 5 | * To the extent possible under law, author(s) have dedicated all | ||
| 6 | * copyright and related and neighboring rights to this software to the | ||
| 7 | * public domain worldwide. This software is distributed without any | ||
| 8 | * warranty. | ||
| 9 | * | ||
| 10 | * You should have received a copy of the CC0 Public Domain Dedication | ||
| 11 | * along with this software (see the LICENSE.md file). If not, see | ||
| 12 | * <http://creativecommons.org/publicdomain/zero/1.0/>. | ||
| 13 | */ | ||
| 14 | package org.moqui.mcp | ||
| 15 | |||
| 16 | import groovy.transform.CompileStatic | ||
| 17 | import org.moqui.screen.ScreenRender | ||
| 18 | |||
| 19 | /** | ||
| 20 | * MCP-specific ScreenTestRender interface for screen rendering results | ||
| 21 | * This is separate from the core Moqui ScreenTest system and tailored for MCP needs | ||
| 22 | */ | ||
| 23 | @CompileStatic | ||
| 24 | interface McpScreenTestRender { | ||
| 25 | ScreenRender getScreenRender() | ||
| 26 | String getOutput() | ||
| 27 | Object getJsonObject() | ||
| 28 | long getRenderTime() | ||
| 29 | Map getPostRenderContext() | ||
| 30 | List<String> getErrorMessages() | ||
| 31 | |||
| 32 | boolean assertContains(String text) | ||
| 33 | boolean assertNotContains(String text) | ||
| 34 | boolean assertRegex(String regex) | ||
| 35 | } | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
-
Please register or sign in to post a comment