Ruleset

Sometimes, you may want to apply a bunch of rules as a group to a request or response message.

For example, the ETag and Last-Modified rules developed in the previous section would make sense to be applied as a group to a response message after it has been determined that the response status was “201 (Created)”. Rather than putting all the logic in one rule, you can split each rule into a separate rule template and then group the rule templates in a RuleSet. The advantage of doing so is that you get separate assertions in the TestScript Execution results. That’s easier to understand for user analyzing test results and is more maintainable for the rule author.

Definition

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 “rules” directory within the zip file:

../../_images/import-02_a1.png

Create a new directory called ruleset and a new file called “RuleSet-Versioning.xml” in it:

../../_images/ruleset_folder.png

../../_images/ruleset_folder2_a1.png

../../_images/ruleset_file.png

../../_images/ruleset_file2_a1.png

Copy the following contents into the newly created RuleSet-VersioningHeaders.xml:

<RuleSet>
   <description value="Contains common rules for validating versioning-related headers." />
   <rule id="assertResponseCode">
      <required  value="false" />
      <reference value="../rule/AssertResponseCode.groovy"/>
   </rule>
   <rule id="assertETag">
      <required  value="true" />
      <reference value="../rule/AssertHeader.groovy"/>
   </rule>
   <rule id="assertLastModified">
      <required  value="false" />
      <reference value="../rule/AssertHeader.groovy"/>
   </rule>
</RuleSet>

The RuleSet contains three different rules. Two of them are marked required and one is marked optional. The optional rules in the RuleSet can be opted out in test scripts. If the TestScript.ruleset and the assert.ruleset elements in the test script do not specify the optional rule, then the rule is not evaluated against the message. On the other hand, if neither the TestScript.ruleset nor the TestScript.test.action.assert.ruleset specifies a required rule (by the id used in the RuleSet definition), then the system will raise an error and refuse to process the rule evaluation.

The rules within a RuleSet definition can be referenced using relative paths (as is the case above) or via absolute paths. Using relative paths makes it easier for you to move test groups (and their contained resources) around and as such is the recommended practice if the rules and ruleset are applicable to the containing test group only. If they can be used across test groups, then it’s recommended to use absolute paths.

Each rule within a RuleSet will be evaluated as a separate TestScript assertion during test execution as required by the FHIR spec.

The description element in a RuleSet is mandatory.

Declaration

To use a RuleSet in a TestScript assert, it must first be declared as a TestScript.ruleset.

At the top of the Patient-server-id-json.xml test script, let’s replace the previous rule declarations with a ruleset declaration to use RuleSet-VersioningHeaders.xml that was defined earlier:

<extension url="http://touchstone.aegis.net/touchstone/fhir/testing/StructureDefinition/testscript-ruleset">
    <extension url="rulesetId">
        <valueId value="ruleset-versioning-headers"/>
    </extension>
    <extension url="path">
        <valueString value="../_reference/ruleset/RuleSet-VersioningHeaders.xml"/>
    </extension>
    <extension url="rule">
        <extension url="ruleId">
            <valueId value="assertResponseCode"/>
        </extension>
        <extension url="param">
            <extension url="name">
                <valueString value="responseCode"/>
            </extension>
            <extension url="value">
                <valueString value="200,201"/>
            </extension>
        </extension>
        <extension url="param">
            <extension url="name">
                <valueString value="responseCodeOperator"/>
            </extension>
            <extension url="value">
                <valueString value="in"/>
            </extension>
        </extension>
    </extension>
    <extension url="rule">
        <extension url="ruleId">
            <valueId value="assertETag"/>
        </extension>
        <extension url="param">
            <extension url="name">
                <valueString value="header"/>
            </extension>
            <extension url="value">
                <valueString value="ETag"/>
            </extension>
        </extension>
        <extension url="param">
            <extension url="name">
                <valueString value="headerOperator"/>
            </extension>
            <extension url="value">
                <valueString value="notEmpty"/>
            </extension>
        </extension>
    </extension>
    <extension url="rule">
        <extension url="ruleId">
            <valueId value="assertLastModified"/>
        </extension>
        <extension url="param">
            <extension url="name">
                <valueString value="header"/>
            </extension>
            <extension url="value">
                <valueString value="Last-Modified"/>
            </extension>
        </extension>
        <extension url="param">
            <extension url="name">
                <valueString value="headerOperator"/>
            </extension>
            <extension url="value">
                <valueString value="notEmpty"/>
            </extension>
        </extension>
    </extension>
</extension>

Notice that rule declarations have been replaced with the ruleset declaration in the test script:

../../_images/ruleset-declaration-1b.png

Assertion

Now that we have declared the ruleset, we can use it in assertions. Replace the rule-assertions with the following ruleset-assertion in Patient-server-id-json.xml test script:

<assert>
  <extension url="http://touchstone.aegis.net/touchstone/fhir/testing/StructureDefinition/testscript-assert-ruleset">
    <extension url="rulesetId">
      <valueId value="ruleset-versioning-headers"/>
    </extension>
  </extension>
  <description value="Complex ruleset assertion to validate expected versioning HTTP Headers."/>
  <direction value="response"/>
  <warningOnly value="false"/>
</assert>

Re-upload and re-execute. You should see the following results:

../../_images/ruleset-passes.png

The assertResponseCode, assertETag, and assertLastModified rules were marked as required rules by the RuleSet-VersioningHeaders.xml definition (as covered in a previous section earlier). If these rules were not specified at the TestScript.ruleset, then they would have been expected to be specified in the assert.ruleset. Otherwise, the system would raise an error.

We have supplied parameters for all the rules within the RuleSet declaration at the top of the test script. These can be overwritten in individual TestScript assertions.

Overriding rules

Replace the ruleset-assertion with the following in Patient-server-id-json.xml test script:

<assert>
  <extension url="http://touchstone.aegis.net/touchstone/fhir/testing/StructureDefinition/testscript-assert-ruleset">
    <extension url="rulesetId">
      <valueId value="ruleset-versioning-headers"/>
    </extension>
    <extension url="rule">
      <extension url="ruleId">
          <valueId value="assertResponseCode"/>
      </extension>
      <extension url="param">
          <extension url="name">
              <valueString value="responseCode"/>
          </extension>
          <extension url="value">
              <valueString value="201"/>
          </extension>
      </extension>
      <extension url="param">
          <extension url="name">
              <valueString value="responseCodeOperator"/>
          </extension>
          <extension url="value">
              <valueString value="equals"/>
          </extension>
      </extension>
    </extension>
  </extension>
  <description value="Complex ruleset assertion to validate expected versioning HTTP Headers."/>
  <direction value="response"/>
  <warningOnly value="false"/>
</assert>

Notice that we’re overriding the responseCode and responseCodeOperator parameters in the ruleset-declaration at the top of the test script with different values in this ruleset-assertion.

Re-upload and re-execute. Notice the change in the expectations and the summary/description:

../../_images/ruleset-passes2.png