Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question - SOAP - Error on "No operations found matching body root" #538

Closed
CedricMtta opened this issue Mar 21, 2024 · 5 comments
Closed
Assignees
Labels

Comments

@CedricMtta
Copy link

Hello,

Thanks for this great project.

I successfully used Imposter with Quarkus CXF guide and a "Calculator" SOAP service, cf: https://docs.quarkiverse.io/quarkus-cxf/dev/user-guide/first-soap-client.html

Now, I'm trying to apply it on a real use case for our company.

I cannot share the whole WSDL unfortunately, but the most relevant part is the service and binding:

<wsdl:binding name="SomeBusinessNameEnvelopeInterfaceSoapBinding"
              type="tns:SomeBusinessNameEnvelopeInterface">
    <soap:binding style="document"
                  transport="http://schemas.xmlsoap.org/soap/http" />
    <wsdl:operation name="submit">
        <soap:operation soapAction="" />
        <wsdl:input>
            <soap:header message="tns:SomeBusinessNameEnvelope" part="SomeBusinessNameHeader"
                         use="literal" />
            <soap:body parts="SomeBusinessNameBody" use="literal" />
        </wsdl:input>
        <wsdl:output>
            <soap:body use="literal" />
        </wsdl:output>
        <wsdl:fault name="FaultResponse">
            <soap:fault name="FaultResponse" use="literal" />
        </wsdl:fault>
    </wsdl:operation>
</wsdl:binding>

<wsdl:service name="SomeBusinessNameEnvelopeService">
<wsdl:port name="SomeBusinessNameEnvelopeBinding" binding="tns:SomeBusinessNameEnvelopeInterfaceSoapBinding">
    <soap:address location="/services/someBusinessName" />
</wsdl:port>
</wsdl:service>
...

My imposter config is the following:

# somebusinessname-config.yaml
---
plugin: soap
wsdlFile: SomeBusinessName.wsdl

I run it through docker.

I generate a Java client using Quarkus CXF, and when I target the imposter docker container I run into the following error message:

08:53:10 DEBUG i.g.i.p.s.SoapResourceMatcher - Matched 0 operations in binding SomeBusinessNameEnvelopeInterfaceSoapBinding based on body root element: ns2:SomeBusinessNameBody
08:53:10 WARN  i.g.i.p.s.SoapResourceMatcher - No operations found matching body root element: ns2:SomeBusinessNameBody
08:53:10 WARN  i.g.i.p.s.SoapPluginImpl - Unable to find a matching binding operation using SOAPAction or SOAP request body

I logged the generated SOAP Request from CXF for the working "calculator" CXF SOAP guide, and the one for my "SomeBusinessName" SOAP service and I don't see a relevant difference.

Apologize for not being able to provide complete WSDL and request output.
Do you see something wrong with what I did so far ? Or might it be the WSDL is too complex for Imposter to map it without some help ?

Thanks in advance for your time, I wish you a good day.

@outofcoffee
Copy link
Owner

outofcoffee commented Apr 20, 2024

Hi @CedricMtta, thank you for raising this.

Are you able to see the HTTP headers for your request? Ideally, either the SOAPAction header or the Content-Type header (like this application/soap+xml;charset=UTF-8;action="example") should specify a value matching your binding.

In the WSDL you shared, this is blank:

<soap:operation soapAction="" />

If this is intentional, then Imposter will attempt to fall back to matching the root element in the SOAP request body against the 'input' messages in the WSDL.

It's possible that the /wsdl:binding/wsdl:operation/wsdl:input/soap:body[@parts] syntax isn't being parsed so the wrong part of the message for this operation is being compared, so this fallback isn't working.

@outofcoffee
Copy link
Owner

outofcoffee commented Apr 21, 2024

Hi @CedricMtta, support for the part filter in the binding operation, as used by the WSDL you shared, has been added in v3.38.1. This means the request should match based on the body root element without you having to set a SOAPAction.

Please let us know if this resolves the issue you were seeing.

@CedricMtta
Copy link
Author

Hi @outofcoffee ,

Thanks a lot for your time ! That sounds great.

I gave it a shot with version v3.38.1 and I cannot start Imposter: I face a NullPointerException.
I tried again with version 3.35.0, and Imposter starts well.

I set the log level to TRACE, and here is the result:

08:06:49 TRACE i.g.i.c.ImposterLauncher - Searching metadata for plugins
08:06:49 TRACE i.g.i.u.ClassLoaderUtil - No plugin files found in /opt/imposter/plugins
08:06:49 TRACE i.g.i.u.ClassLoaderUtil - Plugins will use default classloader
08:06:49 TRACE i.g.i.c.u.MetaUtil - Read 2 config resolvers from metadata: [class io.gatehill.imposter.config.resolver.S3ConfigResolver, class io.gatehill.imposter.config.resolver.LocalFileConfigResolver]
08:06:49 TRACE i.g.i.c.u.ConfigUtil - Configuration resolvers: [class io.gatehill.imposter.config.resolver.S3ConfigResolver, class io.gatehill.imposter.config.resolver.LocalFileConfigResolver]
08:06:49 TRACE i.g.i.u.FeatureUtil - Features: {metrics=true, stores=true}
08:06:50 TRACE i.g.i.EngineBuilder - Initialising mock engine
08:06:50 INFO  i.g.i.Imposter - Starting mock engine 3.38.1
08:06:50 TRACE i.g.i.c.u.ConfigUtil - Excluded from config file search: [.git, .idea, .svn, node_modules]
08:06:50 TRACE i.g.i.c.u.ConfigUtil - Configuration files discovered in /opt/imposter/config: [ConfigReference(file=/opt/imposter/config/my-imposter-config.yaml, configRoot=/opt/imposter/config)]
08:06:50 TRACE i.g.i.Imposter - Engine config: ImposterConfig(host=0.0.0.0, listenPort=8080, configDirs=[/opt/imposter/config], serverUrl=http://localhost:8080, isTlsEnabled=false, keystorePath=classpath:/keystore/ssl.jks, keystorePassword=password, plugins=[io.gatehill.imposter.plugin.internal.MetaInfPluginDetectorImpl], pluginArgs={}, serverFactory=io.gatehill.imposter.server.vertxweb.VertxWebServerFactoryImpl, pluginDiscoveryStrategy=null, pluginDiscoveryStrategyClass=io.gatehill.imposter.plugin.DynamicPluginDiscoveryStrategyImpl, requestHandlingMode=ASYNC, useEmbeddedScriptEngine=false)
08:06:50 DEBUG i.g.i.c.u.ConfigUtil - Loading configuration file: /opt/imposter/config/my-imposter-config.yaml
08:06:50 TRACE i.g.i.p.DynamicPluginDiscoveryStrategyImpl - Discovered [364 ms] annotated plugins on classpath: {config-detector=io.gatehill.imposter.plugin.detector.ConfigPluginDetectorImpl, fake-data=io.gatehill.imposter.plugin.fakedata.FakeDataPlugin, meta-detector=io.gatehill.imposter.plugin.internal.MetaInfPluginDetectorImpl, openapi=io.gatehill.imposter.plugin.openapi.OpenApiPluginImpl, rest=io.gatehill.imposter.plugin.rest.RestPluginImpl, sfdc=io.gatehill.imposter.plugin.sfdc.SfdcPluginImpl, soap=io.gatehill.imposter.plugin.soap.SoapPluginImpl, wiremock=io.gatehill.imposter.plugin.wiremock.WiremockPluginImpl, js-detector=io.gatehill.imposter.scripting.common.JsPluginDetectorImpl, js-graal=io.gatehill.imposter.scripting.graalvm.service.GraalvmScriptServiceImpl, js-nashorn=io.gatehill.imposter.scripting.nashorn.service.NashornScriptServiceImpl, store-detector=io.gatehill.imposter.store.StorePluginDetectorImpl, store-dynamodb=io.gatehill.imposter.store.dynamodb.DynamoDBStoreFactoryImpl, store-graphql=io.gatehill.imposter.store.graphql.GraphQLQueryPlugin, store-inmem=io.gatehill.imposter.store.inmem.InMemoryStoreFactoryImpl, store-redis=io.gatehill.imposter.store.redis.RedisStoreFactoryImpl}
08:06:50 TRACE i.g.i.c.u.ConfigUtil - Loaded 1 plugin configuration file(s) with 0 error(s): [ConfigReference(file=/opt/imposter/config/my-imposter-config.yaml, configRoot=/opt/imposter/config)]
08:06:50 TRACE i.g.i.p.DynamicPluginDiscoveryStrategyImpl - Registered plugin: js-detector with class: io.gatehill.imposter.scripting.common.JsPluginDetectorImpl
08:06:50 TRACE i.g.i.p.DynamicPluginDiscoveryStrategyImpl - 1 plugin(s) provided by: js-detector
08:06:50 TRACE i.g.i.p.DynamicPluginDiscoveryStrategyImpl - Registered plugin: store-detector with class: io.gatehill.imposter.store.StorePluginDetectorImpl
08:06:50 TRACE i.g.i.p.DynamicPluginDiscoveryStrategyImpl - 1 plugin(s) provided by: store-detector
08:06:50 TRACE i.g.i.p.DynamicPluginDiscoveryStrategyImpl - Registered plugin: io.gatehill.imposter.plugin.internal.MetaInfPluginDetectorImpl with class: io.gatehill.imposter.plugin.internal.MetaInfPluginDetectorImpl
08:06:50 TRACE i.g.i.c.u.MetaUtil - Read 7 plugins from metadata: {openapi=PluginMetadata(name=openapi, class=io.gatehill.imposter.plugin.openapi.OpenApiPluginImpl, load=LAZY), rest=PluginMetadata(name=rest, class=io.gatehill.imposter.plugin.rest.RestPluginImpl, load=LAZY), sfdc=PluginMetadata(name=sfdc, class=io.gatehill.imposter.plugin.sfdc.SfdcPluginImpl, load=LAZY), fake-data=PluginMetadata(name=fake-data, class=io.gatehill.imposter.plugin.fakedata.FakeDataPlugin, load=EAGER), wiremock=PluginMetadata(name=wiremock, class=io.gatehill.imposter.plugin.wiremock.WiremockPluginImpl, load=LAZY), store-graphql=PluginMetadata(name=store-graphql, class=io.gatehill.imposter.store.graphql.GraphQLQueryPlugin, load=EAGER), soap=PluginMetadata(name=soap, class=io.gatehill.imposter.plugin.soap.SoapPluginImpl, load=LAZY)}
08:06:50 TRACE i.g.i.p.DynamicPluginDiscoveryStrategyImpl - 3 plugin(s) provided by: meta-detector
08:06:50 TRACE i.g.i.p.DynamicPluginDiscoveryStrategyImpl - Registered plugin: js-nashorn with class: io.gatehill.imposter.scripting.nashorn.service.NashornScriptServiceImpl
08:06:50 TRACE i.g.i.p.DynamicPluginDiscoveryStrategyImpl - Registered plugin: store-inmem with class: io.gatehill.imposter.store.inmem.InMemoryStoreFactoryImpl
08:06:50 TRACE i.g.i.p.DynamicPluginDiscoveryStrategyImpl - Registered plugin: config-detector with class: io.gatehill.imposter.plugin.detector.ConfigPluginDetectorImpl
08:06:50 TRACE i.g.i.p.DynamicPluginDiscoveryStrategyImpl - 1 plugin(s) provided by: config-detector
08:06:50 TRACE i.g.i.p.DynamicPluginDiscoveryStrategyImpl - Registered plugin: fake-data with class: io.gatehill.imposter.plugin.fakedata.FakeDataPlugin
08:06:50 TRACE i.g.i.p.DynamicPluginDiscoveryStrategyImpl - Registered plugin: store-graphql with class: io.gatehill.imposter.store.graphql.GraphQLQueryPlugin
08:06:50 TRACE i.g.i.p.DynamicPluginDiscoveryStrategyImpl - Registered plugin: io.gatehill.imposter.plugin.soap.SoapPluginImpl with class: io.gatehill.imposter.plugin.soap.SoapPluginImpl
08:06:51 TRACE i.g.i.l.LifecycleHooks - Registered listener: io.gatehill.imposter.store.service.CaptureServiceImpl
08:06:51 TRACE i.g.i.l.LifecycleHooks - Registered listener: io.gatehill.imposter.service.script.ScriptedResponseServiceImpl
08:06:51 TRACE i.g.i.l.LifecycleHooks - Registered listener: io.gatehill.imposter.service.security.SecurityLifecycleListenerImpl
08:06:51 TRACE i.g.i.s.s.StoreServiceImpl - Stores enabled
08:06:51 TRACE i.g.i.l.LifecycleHooks - Registered listener: io.gatehill.imposter.store.service.StoreServiceImpl
08:06:51 TRACE i.g.i.l.LifecycleHooks - Registered listener: io.gatehill.imposter.store.service.StoreServiceImpl
08:06:51 TRACE i.g.i.l.LifecycleHooks - Registered listener: io.gatehill.imposter.store.graphql.GraphQLQueryService
08:06:51 TRACE i.g.i.l.LifecycleHooks - Registered listener: io.gatehill.imposter.store.service.StoreRestApiServiceImpl
08:06:51 TRACE i.g.i.p.PluginManager - Starting plugins with 1 configs
08:06:51 DEBUG i.g.i.p.PluginManager - Loaded 9 plugin(s): [js-detector, store-detector, meta-detector, js-nashorn, store-inmem, config-detector, fake-data, store-graphql, soap]
08:06:51 TRACE i.g.i.l.LifecycleHooks - Registered listener: io.gatehill.imposter.plugin.fakedata.FakeDataPlugin
08:06:51 TRACE i.g.i.Imposter - Metrics enabled
08:06:51 DEBUG i.g.i.p.s.p.VersionAwareWsdlParser - Using WSDL parser: V1 for: /opt/imposter/config/redacted-somefile.wsdl
08:06:51 TRACE i.g.i.p.s.p.Wsdl1Parser - Embedded types schema: <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="redacted-somenamespace" xmlns:dbsc="redacted-somenamespace" elementFormDefault="qualified">

                        <xsd:import namespace="redacted-somenamespace" schemaLocation="redacted-somefile.xsd" />
                </xsd:schema>
08:06:51 DEBUG i.g.i.p.s.p.Wsdl1Parser - Discovered 4 schema(s) for WSDL: /opt/imposter/config/redacted-somefile.wsdl
08:06:51 TRACE i.g.i.p.s.p.WsdlRelativeXsdEntityResolver - Resolve redacted-somefile.xsd relative to path /opt/imposter/config
08:06:51 DEBUG i.g.i.p.s.p.WsdlRelativeXsdEntityResolver - Resolved XSD /opt/imposter/config/redacted-somefile.xsd relative to WSDL path
08:06:52 ERROR i.v.c.i.l.c.VertxIsolatedDeployer - Failed in deploying verticle
java.lang.NullPointerException: null
        at io.gatehill.imposter.plugin.soap.parser.AbstractWsdlParser.resolveElementTypeFromXsd(AbstractWsdlParser.kt:151) ~[imposter-mock-soap-3.38.1.jar:?]
        at io.gatehill.imposter.plugin.soap.parser.Wsdl1Parser.getMessage(Wsdl1Parser.kt:271) ~[imposter-mock-soap-3.38.1.jar:?]
        at io.gatehill.imposter.plugin.soap.parser.Wsdl1Parser.getInputOrOutput(Wsdl1Parser.kt:211) ~[imposter-mock-soap-3.38.1.jar:?]
        at io.gatehill.imposter.plugin.soap.parser.Wsdl1Parser.getOperation(Wsdl1Parser.kt:189) ~[imposter-mock-soap-3.38.1.jar:?]
        at io.gatehill.imposter.plugin.soap.parser.Wsdl1Parser.getBinding(Wsdl1Parser.kt:100) ~[imposter-mock-soap-3.38.1.jar:?]
        at io.gatehill.imposter.plugin.soap.parser.VersionAwareWsdlParser.getBinding(VersionAwareWsdlParser.kt:94) ~[imposter-mock-soap-3.38.1.jar:?]
        at io.gatehill.imposter.plugin.soap.SoapPluginImpl.parseWsdls(SoapPluginImpl.kt:129) ~[imposter-mock-soap-3.38.1.jar:?]
        at io.gatehill.imposter.plugin.soap.SoapPluginImpl.configureRoutes(SoapPluginImpl.kt:113) ~[imposter-mock-soap-3.38.1.jar:?]
        at io.gatehill.imposter.Imposter.configureRoutes(Imposter.kt:221) ~[imposter-engine-3.38.1.jar:?]
        at io.gatehill.imposter.Imposter.access$configureRoutes(Imposter.kt:88) ~[imposter-engine-3.38.1.jar:?]
        at io.gatehill.imposter.Imposter$start$1.invokeSuspend(Imposter.kt:135) ~[imposter-engine-3.38.1.jar:?]
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) ~[kotlin-stdlib-1.9.10.jar:1.9.10-release-459]
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:108) ~[kotlinx-coroutines-core-jvm-1.7.3.jar:?]
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584) ~[kotlinx-coroutines-core-jvm-1.7.3.jar:?]
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793) ~[kotlinx-coroutines-core-jvm-1.7.3.jar:?]
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697) ~[kotlinx-coroutines-core-jvm-1.7.3.jar:?]
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684) ~[kotlinx-coroutines-core-jvm-1.7.3.jar:?]

I also gave a shot with version 3.37 and Imposter is well able to start with this version.

If I can help you by providing more information, don't hesitate to ask.

Thanks again for your time and help.

Have a nice day,

@outofcoffee
Copy link
Owner

Hi @CedricMtta, thank you for the update and for checking back against older versions.

I'm guessing that the WSDL has elements defined with anonymous inner types; resolution of these was too sensitive causing the NPE you reported.

The behaviour is more tolerant as of v3.38.2 - I hope this helps!

@CedricMtta
Copy link
Author

Hi @outofcoffee ,

With pleasure.

I confirm it is working like a charm with v3.38.2 :)

Thanks a lot for your reactivity !

The issue can be closed. I wish you a nice day.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants