How to evaluate SOAP response using Schematron? - soap

I have a SOAP response below
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<SOAP-ENV:Body>
<ns2:getCountryResponse xmlns:ns2="http://spring.io/guides/gs-producing-web-service">
<ns2:country>
<ns2:name>Spain</ns2:name>
<ns2:population>46704314</ns2:population>
<ns2:capital>Madrid</ns2:capital>
<ns2:currency>EUR</ns2:currency>
</ns2:country>
</ns2:getCountryResponse>
</SOAP-ENV:Body>
and a Schematron file which I expect to spit out an error when I run the validation since the Header element does not have an attribute named 'foo'
<sch:schema xmlns:sch="http://purl.oclc.org/dsdl/schematron">
<sch:ns uri="http://www.w3.org/2003/05/soap-envelope" prefix="SOAP-ENV"/>
<sch:pattern id="structure">
<sch:rule context="SOAP-ENV:Header">
<sch:assert test="#foo">The element Header must have an attribute named foo.</sch:assert>
</sch:rule>
</sch:pattern>
But I get no failures. What am I getting wrong? The code that I use for validation is
internal class SchematronValidatorUtil {
companion object {
#JvmStatic
fun isXmlStringValidAgainstSchema(schema: String, xml: String,
charset: Charset = StandardCharsets.UTF_8): SchematronResult {
val schematronResource = SchematronResourcePure.fromString(schema, charset)
val input = StreamSource(ByteArrayInputStream(xml.toByteArray(charset)))
val schematronOutput = schematronResource.applySchematronValidationToSVRL(input)
val failures = mutableListOf<String>()
return schematronOutput?.let { schemaOutput ->
schemaOutput.activePatternAndFiredRuleAndFailedAssert
.map { each -> each as? FailedAssert }
.forEach { each -> each?.let { value ->
failures.add(String.format("%s: %s", value.test, value.text)) } }
val type = if (failures.any())
SchematronResultType.XmlDoesNotMatchSchema else SchematronResultType.XmlMatchesSchema
return SchematronResult(type, failures)
} ?: SchematronResult(SchematronResultType.InvalidSchematronFile, failures)
}
}
}

The SOAP envelope declares the SOAP-ENV namespace prefix as http://schemas.xmlsoap.org/soap/envelope/
The Schematron schema declares the SOAP-ENV namespace prefix as http://www.w3.org/2003/05/soap-envelope
Change the ns declaration in the Schematron schema to match the namespace URI declared in the SOAP envelope, and it should work.
(A useful way to test an issue like this is to temporarily change the rule context to *:Header, which would match the Header element in any namespace. If the schema works with that change, then you've narrowed the problem down to a namespace issue.)

Related

node-soap adding namespace to envelope

I am trying to consume this soap service: http://testws.truckstop.com:8080/v13/Posting/LoadPosting.svc?singleWsdl with node-soap, but the client is mangling the namespaces and I have been unable to find a working solution.
I believe the answer is to either add a namespace to the soap envelope, or overwrite the soap envelope.
Using Soap UI, the request should look like:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:v11="http://webservices.truckstop.com/v11"
xmlns:web="http://schemas.datacontract.org/2004/07/WebServices">
<soapenv:Header/>
<soapenv:Body>
<v11:GetLoads>
<v11:listRequest>
<web:IntegrationId>integrationId</web:IntegrationId>
<web:Password>password</web:Password>
<web:UserName>username</web:UserName>
</v11:listRequest>
</v11:GetLoads>
</soapenv:Body>
</soapenv:Envelope>
However, when I do:
client = soap.createClient(url);
let query = {
listRequest: {
Password: password,
UserName: username,
IntegrationId: integrationId
}
};
let results = client.GetLoads(query);
The client generates this xml:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:tns="http://webservices.truckstop.com/v11"
xmlns:q1="http://schemas.datacontract.org/2004/07/WebServices.Posting"
xmlns:q2="http://schemas.datacontract.org/2004/07/WebServices.Objects"
xmlns:q3="http://schemas.datacontract.org/2004/07/WebServices.Posting"
xmlns:q4="http://schemas.datacontract.org/2004/07/WebServices.Objects"
xmlns:q5="http://schemas.datacontract.org/2004/07/WebServices.Posting"
xmlns:q6="http://schemas.datacontract.org/2004/07/WebServices.Objects"
xmlns:q7="http://schemas.datacontract.org/2004/07/WebServices.Posting"
xmlns:q8="http://schemas.datacontract.org/2004/07/WebServices.Objects"
xmlns:q9="http://schemas.datacontract.org/2004/07/WebServices.Posting"
xmlns:q10="http://schemas.datacontract.org/2004/07/WebServices.Objects"
xmlns:q11="http://schemas.datacontract.org/2004/07/WebServices.Posting"
xmlns:q12="http://schemas.datacontract.org/2004/07/WebServices.Objects">
<soap:Body>
<GetLoads xmlns="http://webservices.truckstop.com/v11">
<listRequest>
<ns1:IntegrationId>integrationId</ns1:IntegrationId>
<ns1:Password>password</ns1:Password>
<ns1:UserName>usernam</ns1:UserName>
</listRequest>
</GetLoads>
</soap:Body>
</soap:Envelope>
This fails because IntegrationId, Password and UserName need http://schemas.datacontract.org/2004/07/WebServices, but the namespace isn't referenced in the envelope.
I've tried updating the client to add the namespace as suggested here:
client.wsdl.definitions.xmlns.ns1 = "http://schemas.datacontract.org/2004/07/WebServices";
client.wsdl.xmlnInEnvelope = client.wsdl._xmlnsMap();
I can see the namespace in client.wsdl.xmlnInEnvelope, but it doesn't seem to change the actual generated xml.
Is there another step required to refresh the client to use the updated envelope?
I also tried overriding the root element as shown here:
var wsdlOptions = {
//namespaceArrayElements: "xmlns:ns1=http://schemas.datacontract.org/2004/07/WebServices"
"overrideRootElement": {
"namespace": "xmlns:tns",
"xmlnsAttributes": [{
"name": "xmlns:tns",
"value": "http://webservices.truckstop.com/v11"
}, {
"name": "xmlns:ns1",
"value": "http://schemas.datacontract.org/2004/07/WebServices"
}]
}
};
this.loadPostClient = soap.createClient(this.tsConfig.loadPostUrl, wsdlOptions);
This changes the root body element:
<soap:Body>
<xmlns:tns:GetLoads
xmlns:tns="http://webservices.truckstop.com/v11"
xmlns:ns1="http://schemas.datacontract.org/2004/07/WebServices">
<listRequest>
<ns1:IntegrationId>integrationId</ns1:IntegrationId>
<ns1:Password>password</ns1:Password>
<ns1:UserName>username</ns1:UserName>
</listRequest>
</xmlns:tns:GetLoads>
</soap:Body>
But the remote server doesn't understand.
Thank you for reading!
This answer was correct all along
It wasn't working for me due to autocomplete and similar fields
client.wsdl.xmlnInEnvelope = client.wsdl._xmlnsMap();
Should have been:
client.wsdl.xmlnsInEnvelope = client.wsdl._xmlnsMap();
I left out an s and was setting xmlnInEnvelope instead of xmlnsInEvelope
It's been a few years, but I ran into a similar need of adding custom attributes to the soap envelope and wanted to give an alternative.
As of this writing, that _xmlnsMap() is a private method on the WSDL class so you can use it at your own risk. I always take private methods as subject to change from the developer without any notice to the library consumers so I wanted to find another way and turns out its possible.
TL;DR - Create your own WSDL class instance and pass it to your own Client class instance.
Use the open_wsdl method to bring in your WSDL
Use the callback to build your own custom attributes in a concatenated string.
Assign the attributes to the public xmlnsInEnvelope property.
return the updated WSDL instance (I used a promise).
const fetchWSDL = new Promise<WSDL>((resolve, reject) => {
// method that returns a WSDL instance from a url/file path
open_wsdl(this.wsdl, (err: any, wsdl?: WSDL) => {
// Build custom attributes
if (wsdl && wsdl.definitions.xmlns) {
const xmlns: { [key: string]: string } = {
[your namespaces]: 'values',
};
// turn your custom attributes map into a single concatenated string
let str = '';
for (const alias in xmlns) {
const ns = xmlns[alias];
str += ' xmlns:' + alias + '="' + ns + '"';
}
// Leverage public attribute on WSDL instance to apply our custom attributes
wsdl.xmlnsInEnvelope = str;
resolve(wsdl);
}
reject(err);
});
});
Use the updated WSDL instance to create your own client.
NOTE: the createClient method is just a convenience wrapper for creating a WSDL instance and returning a new Client instance.
const ModifiedWSDL = await fetchWSDL;
// Create client with our modified WSDL instance
this.client = new Client(ModifiedWSDL)
// adjust your Client instance as needed
A bit more code that the OP, but hopefully more in line with node-soap types and safer to use if you plan to upgrade.

Scala akka-http evaluate headers and continue routing if successful?

I'm new to scala, and I'm trying to figure out how to add to the existing routes we have so that if a certain path is hit, we evaluate the headers by checking for the existence of some values and whether or not they equal some accepted values. If it succeeds, we get some String out of the headers and pass it on, otherwise we should not continue routing and return some failure.
/abc -> don't check headers
/abc/def -> check headers, return
pathPrefix("abc") {
path("def") { // want to ADD something here to check headers and send it into someMethod
get {
complete(HttpEntity(something.someMethod(someValue)))
}
} ~ path("gdi") {
get { ... etc}
}
}
Any ideas or dummy examples would be really helpful. I see some directives here to get stuff from the request, and the header (https://doc.akka.io/docs/akka-http/10.0.11/scala/http/routing-dsl/directives/header-directives/headerValue.html), but I don't understand how to chain directives in this way.
If I'm misunderstanding something, please help clarify! Thanks
Use headerValueByName, which looks for a specific header and rejects the request if that header isn't found:
get {
headerValueByName("MyHeader") { headerVal =>
complete(HttpEntity(something.someMethod(headerVal)))
}
}
To validate the header value if it exists:
get {
headerValueByName("MyHeader") { headerVal =>
if (isValid(headerVal)) // isValid is a custom method that you provide
complete(HttpEntity(something.someMethod(headerVal)))
else
complete((BadRequest, "The MyHeader value is invalid."))
}
}
isValid in the above example could look something like:
def isValid(headerValue: String): Boolean = {
val acceptedValues = Set("burrito", "quesadilla", "taco")
acceptedValues.contains(headerValue.toLowerCase)
}

Custom spray.io directive to validate request header value

I am new to spray and I am trying to write a custom directive. I would like the directive to reject the request if the header value is not valid otherwise leave the request alone.
I've tried to absorb this page:
http://spray.io/documentation/1.1.2/spray-routing/key-concepts/directives/
Specifically, the part about the responder chain. I'm trying to create something at the level of the bar Directive in the illustration. I'm just not getting how to pass the context unchanged to the inner route.
My else block below is not correct but expresses what I am trying to do. I just can't figure out how to implement it.
Any help would be greatly appreciated.
trait ApiKeyDirective {
import spray.routing.directives.HeaderDirectives._
import spray.routing.directives.BasicDirectives._
def validateApiKey(): Directive1 = {
headerValueByName("api-key") {key =>
val valid = key == "123"
if (!valid) reject() else pass
}
}
}
object ApiKeyDirective extends ApiKeyDirective
You can combine
headerValueByName:
def headerValueByName(headerName: String): Directive1[String]
with validate:
def validate(check: ⇒ Boolean, errorMsg: String): Directive0
For example:
def validateApiKey(route: Route) =
headerValueByName("api-key") { key =>
validate(key == "123", "Invalid API key") {
route
}
}
or without validate:
def validateApiKey(route: Route) =
headerValueByName("api-key") { key =>
if (key == "123")
route
else
reject(ValidationRejection("Invalid API key"))
}
Usage:
lazy val route = ...
... ~
pathPrefix("test_directive") {
get {
validateApiKey {
complete("ok")
}
}
} ~
...
Test from cmd/shell:
# curl http://localhost:8080/test_directive
Request is missing required HTTP header 'api-key'
# curl http://localhost:8080/test_directive -H 'api-key: bad'
Invalid API key
# curl http://localhost:8080/test_directive -H 'api-key: 123'
"ok"
I'm just not getting how to pass the context unchanged to the inner
route.
Spray does that for you!
Your code is mostly correct, there are just 2 simple problems to fix!
Firstly, you need to flatMap headerValueByName("api-key") directive.
Secondly, the return type will be Directive0 because the directive won't provide any value.
So final code would look like this:
object ApiKeyDirective {
import spray.routing.Directives._
val validateApiKey: Directive0 =
headerValueByName("api-key").flatMap { key =>
val valid = key == "123"
if (!valid) reject() else pass
}
}
Also, I recommend you to add a custom rejection to reject() block so that API users will be informed when their api key is invalid.

Getting name of previous test step of type Rest Request in SoapUI groovy script

I'm using groovy script to transfer a certain property from the response of a REST request like this:
def setCookie = testRunner.testCase.testSteps["SubmitCompleteDeviceRegistration"].testRequest.response.responseHeaders["Set-Cookie"]
def global = com.eviware.soapui.SoapUI.globalProperties
re = /(SESSION_AUTHENTICATION_TOKEN=[A-Za-z0-9_-]+;)/
matcher = ( setCookie =~ re )
def cookie = matcher[0][0]
global.setPropertyValue("SESSION_AUTHENTICATION_TOKEN","$cookie")
return cookie
Now what I want to do is make the name of the above teststep, "SubmitCompleteDeviceRegistration", variable, so I can use the transfer for various REST-Requests.
The name of this variable TestStep should equal the name of the previous TestStep of the RestRequest type.
How can I go about defining the TestStep that equals this condition?
I'm trying to use something like
def prevGroovyTestStep =
testRunner.testCase.findPreviousStepOfType(testRunner.testCase.getTestStepByName
("SubmitCompleteDeviceRegistration"),RestRequest)
log.info(prevGroovyTestStep.getName())
But I'm not sure how to implement this.
Any help would be really appreciated!
Getting the previous step name
def previousStepName = context.testCase.testStepList[context.currentStepIndex - 1].name
log.info "Previous step name is : ${previousStepName}"
Getting the previous step name if its type is Rest Request
def testStep = context.testCase.testStepList[context.currentStepIndex - 1]
def previousStepName
if (testStep instanceof com.eviware.soapui.impl.wsdl.teststeps.RestTestRequestStep) {
previousStepName = testStep.name
} else {
log.error "Previous step is not of Rest Request Type"
}
if (previousStepName) {
log.info "Previous step name is : ${previousStepName}"
}
If type does not match in the above case, it will log the error message.
UPDATE - updating as per the latest comments from the author of this question. The below one helps all your need and the above may not needed any more.
Add a custom property for the test case, whose name is STEP_NAME and its value is the test step name to which http header needs to be added. As you commented, the last test step name in this case.
Go the request test step where you are getting the Cookie as response header.
Add an assertion of type Script Assertion and have the below code. Note that, you need to modify the test step name to which you want to add the request header Cookie. Using the place holder for now.
/**Below script should be used as script assertion for first test request step
* Assumes below
* a. test response contains http header called 'Set-Cookie'
* b. other request needs to send http header called 'Cookie'
* In case if there is any change in the two header names you may need to
* change its references below
**/
def responseCookieKey = 'Set-Cookie'
def requestCookieKey = 'Cookie'
def setHttpHeaders(String nextStepName, def headers) {
def nextRequest = context.testCase.testSteps[nextStepName].httpRequest
def existingHeaders = nextRequest.requestHeaders
headers.each {
existingHeaders[it.key] = it.value
}
nextRequest.requestHeaders = existingHeaders
}
if (messageExchange.responseHeaders.containsKey(responseCookieKey)) {
log.info "Found Cookie in the response headers"
def cookiez = messageExchange.responseHeaders[responseCookieKey]
assert null != cookiez, "Response does not contain Cookie"
def headers = [(requestCookieKey) : (cookiez)]
setHttpHeaders(context.testCase.getProvertyValue('STEP_NAME'), headers)
} else {
log.error "Not Found Cookie in the response headers"
}

How can I parse out get request parameters in spray-routing?

This is what the section of code looks like
get{
respondWithMediaType(MediaTypes.`application/json`){
entity(as[HttpRequest]){
obj => complete{
println(obj)
"ok"
}
}
}
}~
I can map the request to a spray.http.HttpRequest object and I can extract the uri from this object but I imagine there is an easier way to parse out the parameters in a get request than doing it manually.
For example if my get request is
http://localhost:8080/url?id=23434&age=24
I want to be able to get id and age out of this request
Actually you can do this much much better. In routing there are two directives: parameter and parameters, I guess the difference is clear, you can also use some modifiers: ! and ?. In case of !, it means that this parameter must be provided or the request is going to be rejected and ? returns an option, so you can provide a default parameter in this case. Example:
val route: Route = {
(path("search") & get) {
parameter("q"!) { query =>
....
}
}
}
val route: Route = {
(path("search") & get) {
parameters("q"!, "filter" ? "all") { (query, filter) =>
...
}
}
}