Rule Basics¶
The TestScript’s rule element can be used to reference complex validation logic that goes beyond what the basic TestScript assert element supports. As such, rules are recommended only when TestScript assert cannot be used in its basic form.
Touchstone Rules-Engine supports rules written in the following languages:
Groovy
XSLT
Schematron
Support for additional languages may be added in the future. Unless you plan on executing test scripts against a test system that only supports XML, it is highly recommended to write rules in Groovy as XSLT and Schematron rules can only be evaluated against requests and responses whose content is in XML while Groovy supports JSON as well.
The examples in this guide will be in Groovy. For more information on how to write rules in XSLT and Schematron, please refer to XSLT and Schematron.
Definition¶
We will start with a simple test script that has no rules and gradually add rules. You can download all of the examples used in this section from here.
Open an instance of TestScript Editor and import the project in the example “no-rules” directory within the zip file:
Upload the Patient folder to Touchstone:
Execute the newly uploaded script successfully in Touchstone. You can use WildFHIR 3.3.0 server as the target server.
The first operation in RegisterNewPatient
test of Patient-server-id-json.xml test script is a create operation followed by a basic assertion:
<action>
<operation>
<type>
<system value="http://hl7.org/fhir/testscript-operation-codes"/>
<code value="create"/>
</type>
<resource value="Patient"/>
<description value="Create patient with server assigned resource id."/>
<accept value="json"/>
<contentType value="json"/>
<encodeRequestUrl value="true"/>
<sourceId value="patient-create"/>
</operation>
</action>
<action>
<assert>
<description value="Confirm that the returned HTTP status is 200(OK) or 201(Created)."/>
<direction value="response"/>
<operator value="in"/>
<responseCode value="200,201"/>
<warningOnly value="false"/>
</assert>
</action>
According to the FHIR specification, if a server supports versioning then it should return an ETag header with the versionId in the create operation response. Although the basic TestScript assertion supports verification of arbitrary response headers, we will create a rule to perform this verification for demonstration purposes.
Create a folder called ‘rule’ under the _reference folder:
This folder will host all the rule definitions that are specific to the Patient test group.
Create a new file called ‘AssertHeader.groovy’ under this folder:
Keep the rule contents empty for now.
Declaration¶
To use a rule in a test script assert, you must first declare the Touchstone IG in the test script’s metadata:
<meta>
<profile value="http://touchstone.aegis.net/touchstone/fhir/testing/StructureDefinition/testscript"/>
</meta>
.. warning:: If this metadata is not in your testscript Touchstone cannot evaluate any rule/ruleset asserts.
Then you can declare the assertETag
rule before the url definition in Patient-server-id-json.xml
test script as follows:
<extension url="http://touchstone.aegis.net/touchstone/fhir/testing/StructureDefinition/testscript-rule">
<extension url="ruleId">
<valueId value="assertETag"/>
</extension>
<extension url="path">
<valueString value="../_reference/rule/AssertHeader.groovy"/>
</extension>
</extension>
Assertion¶
We can now use the rule in rule assertions. Add the following rule assertion to the RegisterNewPatient
test in Patient-server-id-json.xml
test script:
<assert>
<extension url="http://touchstone.aegis.net/touchstone/fhir/testing/StructureDefinition/testscript-assert-rule">
<extension url="ruleId">
<valueId value="assertETag"/>
</extension>
</extension>
<warningOnly value="false" />
</assert>
Notice we’re giving a relative path to the actual rule definition. The rule id assertETag
has to match the one used in the rule assertion. Defining the rule at the top of the test script allows us to reuse this rule in many tests within this test script.
Summary and Description¶
Let’s now add some content to the AssertHeader.groovy rule.
The summary and description lines communicate the intent of the rule assertion to the end-user. They are declared in Groovy comments at the top of the rule definition as follows:
/*
rule.summary=Response ETag header cannot be empty
rule.description=Validates the 'ETag' header in the response
*/
Go ahead and upload the Patient folder to Touchstone:
Execute the Patient-server-id-json test script on the UI:
Here’s how the summary and description get displayed on the TestScript Execution screen:
The rule did not have any logic. So the rule assertion passed.
Add the following assert to the rule content to make it fail unconditionally:
An error is raised with the message specified after the colon (i.e. “Could not find ‘ETag’ header in response”) if the assertion fails. The “assert false” call is an unconditional failure; so we must get an error with this message.
We only changed the groovy rule. Upload the groovy rule to Touchstone:
Re-execute the same test execution on Touchstone UI:
Notice the correlation between the error message in the rule and what’s displayed on the UI:
Change the assert in the groovy rule to evaluate the ETag header in the response correctly:
assert response.header('ETag').isNotEmpty(): "Could not find 'ETag' header in response"
We are using the response
binding that represents the HTTP response from the target server to grab the actual ETag header received from the server. Bindings are covered in the next section.
Upload the groovy rule to Touchstone and re-execute the test execution. This time, the assertion passes because the server does indeed return an ETag in the response header:
While it’s perfectly valid to use the Groovy assert
and construct the error messages yourself (e.g. “Could not find ‘ETag’ header in response”), the Touchstone Rules API provides helper methods that relieves rule authors from constructing these error messages and thereby keeps them consistent across many rule definitions. These helper methods are offered on many of the binding variables and come in the form of “assertXXXX” e.g. assertHeaderEquals
, assertStatusCodeEquals
, assertContentTypeEquals
, etc. For full listing, please refer to Rules API Reference.
Let’s change the assertion in AssertHeader.groovy to the following:
response.assertHeaderNotEmpty('ETag')
Upload the rule and re-execute. The results should be same as before.
Bindings¶
We used the response
binding to make a header assertion in the previous section. There are many other bindings that the Rules-Engine provides access to within a Groovy rule template. They are touched upon below and are covered in more detail in Rules API Reference.
Binding |
Alternative bindings |
Description |
---|---|---|
exchange |
The exchange taking place between a client (Touchstone or Test system) and
a server Test System in Touchstone
|
|
request |
exchange.request |
The request message sent by a client (either Touchstone or another
Test System in Touchstone).
|
requests |
Map of Testscript.operation.requestId to request.
If operation.requestId were specified as ‘create-request’, then the request could
be fetched within the rule using requests.get(‘create-request’). See Multiple Assertions.
|
|
response |
exchange.response |
The response received from the server Test System.
|
responses |
Map of Testscript.operation.responseId to request.
If operation.responseId were specified as ‘create-response’, then the response could
be fetched within the rule using responses.get(‘create-response’). See Multiple Assertions.
|
|
requestHeaders |
request.headers
|
The headers of the request.
|
responseHeaders |
response.headers
|
The headers of the response.
|
requestBody |
request.body
|
The payload of the request.
|
responseBody |
response.body
|
The payload of the response.
|
responseCode |
statusCode,
response.responseCode
response.statusCode
|
|
requestResource |
request.resource |
e.g. ‘Patient’ if the request message payload is present and
the resource within it is a Patient resource. See FHIR Resource List.
|
responseResource |
response.resource |
e.g. ‘Bundle’ if the response message payload is present and the
FHIR resource within it is a Bundle resource.
See Touchstone Resource rule assertions.
|
clientCapStmt |
originCapStmt,
originConfStmt,
clientConfStmt
|
The Capability Statement of the client in the message exchange. See
Touchstone Capability rule assertions.
|
serverCapStmt |
destCapStmt,
destConfStmt,
serverConfStmt
|
The Capability Statement of the server in the message exchange. See
Touchstone Capability rule assertions.
|
direction |
‘REQUEST’ if the rule assertion is being performed on the request
message. ‘RESPONSE’ if the rule assertion is being performed on
the response message. This is useful if the same rule template is being
used for both request and response and you need to execute slightly
different logic for the two.
|
|
targetMessage |
This will be ‘request’ (binding variable above) if assert direction is ‘request’
and it will be ‘response’ (binding variable above) if assert direction is ‘response’.
This is useful if the same rule template is being used for both request
and response and you need to execute the same logic against whatever
the message is. See Operator parameter for an example.
|
|
param |
Parameter(s) passed from the TestScript to the rule template.
|
|
ruleOutputs |
Map of Rule Output Id to Rule Output (document or string). See Rule Outputs.
|