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:

../../_images/import-01_a1.png

Upload the Patient folder to Touchstone:

../../_images/upload-example-01_a1.png

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:

../../_images/example-01-create-operation.png
<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:

../../_images/rule_folder.png

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:

../../_images/new_groovy_class_a1.png

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.

../../_images/relative_path.png

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:

../../_images/upload1.png

Execute the Patient-server-id-json test script on the UI:

../../_images/create_setup1_a1.png

Here’s how the summary and description get displayed on the TestScript Execution screen:

../../_images/summaryAndDescription.png

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:

../../_images/assert_false.png

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:

../../_images/upload2.png

Re-execute the same test execution on Touchstone UI:

../../_images/execute_again1.png

Notice the correlation between the error message in the rule and what’s displayed on the UI:

../../_images/assertion_failed1_a1.png

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:

../../_images/etag_assertion_passes.png

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
e.g. 200. See RFC Status Codes and Touchstone


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

serverCapStmt

destCapStmt,
destConfStmt,
serverConfStmt
The Capability Statement of the server in the message exchange. See

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.