diff --git a/lib/common/tests/rules/pcmk__evaluate_attr_expression_test.c b/lib/common/tests/rules/pcmk__evaluate_attr_expression_test.c
index fda55ee95f..e88774c226 100644
--- a/lib/common/tests/rules/pcmk__evaluate_attr_expression_test.c
+++ b/lib/common/tests/rules/pcmk__evaluate_attr_expression_test.c
@@ -1,827 +1,841 @@
 /*
  * Copyright 2024 the Pacemaker project contributors
  *
  * The version control history for this file may have further details.
  *
  * This source code is licensed under the GNU General Public License version 2
  * or later (GPLv2+) WITHOUT ANY WARRANTY.
  */
 
 #include <crm_internal.h>
 
 #include <stdio.h>
 #include <glib.h>
 
 #include <crm/common/xml.h>
 #include <crm/common/rules_internal.h>
 #include <crm/common/unittest_internal.h>
 #include "crmcommon_private.h"
 
 /*
  * Shared data
  */
 
 #define MATCHED_STRING "server-north"
 
 static const regmatch_t submatches[] = {
     { .rm_so = 0, .rm_eo = 12 }, // %0 = Entire string
     { .rm_so = 7, .rm_eo = 12 }, // %1 = "north"
 };
 
 static pcmk_rule_input_t rule_input = {
     // These are the only members used to evaluate attribute expressions
 
     // Used to replace submatches in attribute name
     .rsc_id = MATCHED_STRING,
     .rsc_id_submatches = submatches,
     .rsc_id_nmatches = 2,
 
     // Used when source is instance attributes
     .rsc_params = NULL,
 
     // Used when source is meta-attributes
     .rsc_meta = NULL,
 
     // Used to get actual value of node attribute
     .node_attrs = NULL,
 };
 
 static int
 setup(void **state)
 {
     rule_input.rsc_params = pcmk__strkey_table(free, free);
     pcmk__insert_dup(rule_input.rsc_params, "foo-param", "bar");
     pcmk__insert_dup(rule_input.rsc_params, "myparam", "different");
 
     rule_input.rsc_meta = pcmk__strkey_table(free, free);
     pcmk__insert_dup(rule_input.rsc_meta, "foo-meta", "bar");
     pcmk__insert_dup(rule_input.rsc_params, "mymeta", "different");
 
     rule_input.node_attrs = pcmk__strkey_table(free, free);
     pcmk__insert_dup(rule_input.node_attrs, "foo", "bar");
     pcmk__insert_dup(rule_input.node_attrs, "num", "10");
     pcmk__insert_dup(rule_input.node_attrs, "ver", "3.5.0");
     pcmk__insert_dup(rule_input.node_attrs, "prefer-north", "100");
 
     return 0;
 }
 
 static int
 teardown(void **state)
 {
     g_hash_table_destroy(rule_input.rsc_params);
     g_hash_table_destroy(rule_input.rsc_meta);
     g_hash_table_destroy(rule_input.node_attrs);
     return 0;
 }
 
 /*!
  * \internal
  * \brief Run one test, comparing return value
  *
  * \param[in] xml_string    Node attribute expression XML as string
  * \param[in] reference_rc  Assert that evaluation result equals this
  */
 static void
 assert_attr_expression(const char *xml_string, int reference_rc)
 {
     xmlNode *xml = pcmk__xml_parse(xml_string);
 
     assert_int_equal(pcmk__evaluate_attr_expression(xml, &rule_input),
                      reference_rc);
     pcmk__xml_free(xml);
 }
 
 
 /*
  * Invalid arguments
  */
 
 #define EXPR_SOURCE_LITERAL_PASSES                      \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='foo' "                     \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_VALUE "='bar' "                         \
         PCMK_XA_VALUE_SOURCE "='" PCMK_VALUE_LITERAL "' />"
 
 static void
 null_invalid(void **state)
 {
     xmlNode *xml = pcmk__xml_parse(EXPR_SOURCE_LITERAL_PASSES);
 
     assert_int_equal(pcmk__evaluate_attr_expression(NULL, NULL), EINVAL);
     assert_int_equal(pcmk__evaluate_attr_expression(xml, NULL), EINVAL);
     assert_int_equal(pcmk__evaluate_attr_expression(NULL, &rule_input), EINVAL);
 
     pcmk__xml_free(xml);
 }
 
 
 /*
  * Test PCMK_XA_ID
  */
 
 #define EXPR_ID_MISSING                                 \
         "<" PCMK_XE_EXPRESSION " "                      \
         PCMK_XA_ATTRIBUTE "='foo' "                     \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_VALUE "='bar' />"
 
 static void
 id_missing(void **state)
 {
     assert_attr_expression(EXPR_ID_MISSING, pcmk_rc_unpack_error);
 }
 
 
 /*
  * Test PCMK_XA_ATTRIBUTE
  */
 
 #define EXPR_ATTR_MISSING                               \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_VALUE "='bar' />"
 
 static void
 attr_missing(void **state)
 {
     assert_attr_expression(EXPR_ATTR_MISSING, pcmk_rc_unpack_error);
 }
 
 #define EXPR_ATTR_SUBMATCH_PASSES                       \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='prefer-%1' "               \
         PCMK_XA_OPERATION "='" PCMK_VALUE_DEFINED "' />"
 
 static void
 attr_with_submatch_passes(void **state)
 {
     assert_attr_expression(EXPR_ATTR_SUBMATCH_PASSES, pcmk_rc_ok);
 }
 
 #define EXPR_ATTR_SUBMATCH_FAILS                        \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='undefined-%1' "            \
         PCMK_XA_OPERATION "='" PCMK_VALUE_DEFINED "' />"
 
 static void
 attr_with_submatch_fails(void **state)
 {
     assert_attr_expression(EXPR_ATTR_SUBMATCH_FAILS, pcmk_rc_op_unsatisfied);
 }
 
 
 /*
  * Test PCMK_XA_VALUE_SOURCE
  */
 
 #define EXPR_SOURCE_MISSING                             \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_ATTRIBUTE "='foo' "                     \
         PCMK_XA_VALUE "='bar' />"
 
 static void
 source_missing(void **state)
 {
     // Defaults to literal
     assert_attr_expression(EXPR_SOURCE_MISSING, pcmk_rc_ok);
 }
 
 #define EXPR_SOURCE_INVALID                             \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='foo' "                     \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_VALUE "='bar' "                         \
         PCMK_XA_VALUE_SOURCE "='not-a-source' />"
 
 static void
 source_invalid(void **state)
 {
     assert_attr_expression(EXPR_SOURCE_INVALID, pcmk_rc_unpack_error);
 }
 
 static void
 source_literal_passes(void **state)
 {
     assert_attr_expression(EXPR_SOURCE_LITERAL_PASSES, pcmk_rc_ok);
 }
 
 #define EXPR_SOURCE_LITERAL_VALUE_FAILS                 \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='foo' "                     \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_VALUE "='wrong-value' "                 \
         PCMK_XA_VALUE_SOURCE "='" PCMK_VALUE_LITERAL "' />"
 
 static void
 source_literal_value_fails(void **state)
 {
     assert_attr_expression(EXPR_SOURCE_LITERAL_VALUE_FAILS,
                            pcmk_rc_op_unsatisfied);
 }
 
 #define EXPR_SOURCE_LITERAL_ATTR_FAILS                  \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='not-an-attribute' "        \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_VALUE "='bar' "                         \
         PCMK_XA_VALUE_SOURCE "='" PCMK_VALUE_LITERAL "' />"
 
 static void
 source_literal_attr_fails(void **state)
 {
     assert_attr_expression(EXPR_SOURCE_LITERAL_ATTR_FAILS,
                            pcmk_rc_op_unsatisfied);
 }
 
 #define EXPR_SOURCE_PARAM_MISSING                       \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='foo' "                     \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_VALUE "='not-a-param' "                 \
         PCMK_XA_VALUE_SOURCE "='" PCMK_VALUE_PARAM "' />"
 
 static void
 source_params_missing(void **state)
 {
     assert_attr_expression(EXPR_SOURCE_PARAM_MISSING, pcmk_rc_op_unsatisfied);
 }
 
 #define EXPR_SOURCE_PARAM_PASSES                        \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='foo' "                     \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_VALUE "='foo-param' "                   \
         PCMK_XA_VALUE_SOURCE "='" PCMK_VALUE_PARAM "' />"
 
 static void
 source_params_passes(void **state)
 {
     assert_attr_expression(EXPR_SOURCE_PARAM_PASSES, pcmk_rc_ok);
 }
 
 #define EXPR_SOURCE_PARAM_FAILS                         \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='foo' "                     \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_VALUE "='myparam' "                     \
         PCMK_XA_VALUE_SOURCE "='" PCMK_VALUE_PARAM "' />"
 
 static void
 source_params_fails(void **state)
 {
     assert_attr_expression(EXPR_SOURCE_PARAM_FAILS, pcmk_rc_op_unsatisfied);
 }
 
 #define EXPR_SOURCE_META_MISSING                        \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='foo' "                     \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_VALUE "='not-a-meta' "                  \
         PCMK_XA_VALUE_SOURCE "='" PCMK_VALUE_META "' />"
 
 static void
 source_meta_missing(void **state)
 {
     assert_attr_expression(EXPR_SOURCE_META_MISSING, pcmk_rc_op_unsatisfied);
 }
 
 #define EXPR_SOURCE_META_PASSES                         \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='foo' "                     \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_VALUE "='foo-meta' "                    \
         PCMK_XA_VALUE_SOURCE "='" PCMK_VALUE_META "' />"
 
 static void
 source_meta_passes(void **state)
 {
     assert_attr_expression(EXPR_SOURCE_META_PASSES, pcmk_rc_ok);
 }
 
 #define EXPR_SOURCE_META_FAILS                        \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='foo' "                     \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_VALUE "='mymeta' "                      \
         PCMK_XA_VALUE_SOURCE "='" PCMK_VALUE_META "' />"
 
 static void
 source_meta_fails(void **state)
 {
     assert_attr_expression(EXPR_SOURCE_META_FAILS, pcmk_rc_op_unsatisfied);
 }
 
 
 /*
  * Test PCMK_XA_TYPE
  */
 
 #define EXPR_TYPE_DEFAULT_NUMBER                        \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_OPERATION "='" PCMK_VALUE_GT "' "       \
         PCMK_XA_ATTRIBUTE "='num' "                     \
         PCMK_XA_VALUE "='2.5' />"
 
 static void
 type_default_number(void **state)
 {
     // Defaults to number for "gt" if either value contains a decimal point
     assert_attr_expression(EXPR_TYPE_DEFAULT_NUMBER, pcmk_rc_ok);
 }
 
 #define EXPR_TYPE_DEFAULT_INT                           \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_OPERATION "='" PCMK_VALUE_GT "' "       \
         PCMK_XA_ATTRIBUTE "='num' "                     \
         PCMK_XA_VALUE "='2' />"
 
 static void
 type_default_int(void **state)
 {
     // Defaults to integer for "gt" if neither value contains a decimal point
     assert_attr_expression(EXPR_TYPE_DEFAULT_INT, pcmk_rc_ok);
 }
 
+#define EXPR_TYPE_INVALID                               \
+        "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
+        PCMK_XA_TYPE "='not-a-value' "                  \
+        PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
+        PCMK_XA_ATTRIBUTE "='foo' "                     \
+        PCMK_XA_VALUE "='bar' />"
+
+static void
+type_invalid(void **state)
+{
+    assert_attr_expression(EXPR_TYPE_INVALID, pcmk_rc_unpack_error);
+}
+
 #define EXPR_TYPE_STRING_PASSES                         \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_STRING "' "        \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_ATTRIBUTE "='foo' "                     \
         PCMK_XA_VALUE "='bar' />"
 
 static void
 type_string_passes(void **state)
 {
     assert_attr_expression(EXPR_TYPE_STRING_PASSES, pcmk_rc_ok);
 }
 
 #define EXPR_TYPE_STRING_FAILS                          \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_STRING "' "        \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_ATTRIBUTE "='foo' "                     \
         PCMK_XA_VALUE "='bat' />"
 
 static void
 type_string_fails(void **state)
 {
     assert_attr_expression(EXPR_TYPE_STRING_FAILS, pcmk_rc_op_unsatisfied);
 }
 
 #define EXPR_TYPE_INTEGER_PASSES                        \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_INTEGER "' "       \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_ATTRIBUTE "='num' "                     \
         PCMK_XA_VALUE "='10' />"
 
 static void
 type_integer_passes(void **state)
 {
     assert_attr_expression(EXPR_TYPE_INTEGER_PASSES, pcmk_rc_ok);
 }
 
 #define EXPR_TYPE_INTEGER_FAILS                         \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_INTEGER "' "       \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_ATTRIBUTE "='num' "                     \
         PCMK_XA_VALUE "='11' />"
 
 static void
 type_integer_fails(void **state)
 {
     assert_attr_expression(EXPR_TYPE_INTEGER_FAILS, pcmk_rc_op_unsatisfied);
 }
 
 #define EXPR_TYPE_INTEGER_TRUNCATION                    \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_INTEGER "' "       \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_ATTRIBUTE "='num' "                     \
         PCMK_XA_VALUE "='10.5' />"
 
 static void
 type_integer_truncation(void **state)
 {
     assert_attr_expression(EXPR_TYPE_INTEGER_TRUNCATION, pcmk_rc_ok);
 }
 
 #define EXPR_TYPE_NUMBER_PASSES                         \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_NUMBER "' "        \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_ATTRIBUTE "='num' "                     \
         PCMK_XA_VALUE "='10.0' />"
 
 static void
 type_number_passes(void **state)
 {
     assert_attr_expression(EXPR_TYPE_NUMBER_PASSES, pcmk_rc_ok);
 }
 
 #define EXPR_TYPE_NUMBER_FAILS                          \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_NUMBER "' "        \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_ATTRIBUTE "='num' "                     \
         PCMK_XA_VALUE "='10.1' />"
 
 static void
 type_number_fails(void **state)
 {
     assert_attr_expression(EXPR_TYPE_NUMBER_FAILS, pcmk_rc_op_unsatisfied);
 }
 
 #define EXPR_TYPE_VERSION_PASSES                        \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_VERSION "' "       \
         PCMK_XA_OPERATION "='" PCMK_VALUE_GT "' "       \
         PCMK_XA_ATTRIBUTE "='ver' "                     \
         PCMK_XA_VALUE "='3.4.9' />"
 
 static void
 type_version_passes(void **state)
 {
     assert_attr_expression(EXPR_TYPE_VERSION_PASSES, pcmk_rc_ok);
 }
 
 #define EXPR_TYPE_VERSION_EQUALITY                      \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_VERSION "' "       \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_ATTRIBUTE "='ver' "                     \
         PCMK_XA_VALUE "='3.5' />"
 
 static void
 type_version_equality(void **state)
 {
     assert_attr_expression(EXPR_TYPE_VERSION_EQUALITY, pcmk_rc_ok);
 }
 
 #define EXPR_TYPE_VERSION_FAILS                         \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_VERSION "' "       \
         PCMK_XA_OPERATION "='" PCMK_VALUE_GTE "' "      \
         PCMK_XA_ATTRIBUTE "='ver' "                     \
         PCMK_XA_VALUE "='4.0' />"
 
 static void
 type_version_fails(void **state)
 {
     assert_attr_expression(EXPR_TYPE_VERSION_FAILS, pcmk_rc_before_range);
 }
 
 /*
  * Test PCMK_XA_OPERATION
  */
 
 #define EXPR_OP_MISSING                                 \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='foo' "                     \
         PCMK_XA_VALUE "='bar' />"
 
 static void
 op_missing(void **state)
 {
     assert_attr_expression(EXPR_OP_MISSING, pcmk_rc_unpack_error);
 }
 
 #define EXPR_OP_INVALID                                 \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='foo' "                     \
         PCMK_XA_OPERATION "='not-an-operation' "        \
         PCMK_XA_VALUE "='bar' />"
 
 static void
 op_invalid(void **state)
 {
     assert_attr_expression(EXPR_OP_INVALID, pcmk_rc_unpack_error);
 }
 
 #define EXPR_OP_LT_PASSES                               \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_INTEGER "' "       \
         PCMK_XA_OPERATION "='" PCMK_VALUE_LT "' "       \
         PCMK_XA_ATTRIBUTE "='num' "                     \
         PCMK_XA_VALUE "='20' />"
 
 static void
 op_lt_passes(void **state)
 {
     assert_attr_expression(EXPR_OP_LT_PASSES, pcmk_rc_ok);
 }
 
 #define EXPR_OP_LT_FAILS                                \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_INTEGER "' "       \
         PCMK_XA_OPERATION "='" PCMK_VALUE_LT "' "       \
         PCMK_XA_ATTRIBUTE "='num' "                     \
         PCMK_XA_VALUE "='2' />"
 
 static void
 op_lt_fails(void **state)
 {
     assert_attr_expression(EXPR_OP_LT_FAILS, pcmk_rc_after_range);
 }
 
 #define EXPR_OP_GT_PASSES                               \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_INTEGER "' "       \
         PCMK_XA_OPERATION "='" PCMK_VALUE_GT "' "       \
         PCMK_XA_ATTRIBUTE "='num' "                     \
         PCMK_XA_VALUE "='2' />"
 
 static void
 op_gt_passes(void **state)
 {
     assert_attr_expression(EXPR_OP_GT_PASSES, pcmk_rc_ok);
 }
 
 #define EXPR_OP_GT_FAILS                                \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_INTEGER "' "       \
         PCMK_XA_OPERATION "='" PCMK_VALUE_GT "' "       \
         PCMK_XA_ATTRIBUTE "='num' "                     \
         PCMK_XA_VALUE "='20' />"
 
 static void
 op_gt_fails(void **state)
 {
     assert_attr_expression(EXPR_OP_GT_FAILS, pcmk_rc_before_range);
 }
 
 #define EXPR_OP_LTE_LT_PASSES                           \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_INTEGER "' "       \
         PCMK_XA_OPERATION "='" PCMK_VALUE_LTE "' "      \
         PCMK_XA_ATTRIBUTE "='num' "                     \
         PCMK_XA_VALUE "='20' />"
 
 static void
 op_lte_lt_passes(void **state)
 {
     assert_attr_expression(EXPR_OP_LTE_LT_PASSES, pcmk_rc_ok);
 }
 
 #define EXPR_OP_LTE_EQ_PASSES                           \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_INTEGER "' "       \
         PCMK_XA_OPERATION "='" PCMK_VALUE_LTE "' "      \
         PCMK_XA_ATTRIBUTE "='num' "                     \
         PCMK_XA_VALUE "='10' />"
 
 static void
 op_lte_eq_passes(void **state)
 {
     assert_attr_expression(EXPR_OP_LTE_EQ_PASSES, pcmk_rc_ok);
 }
 
 #define EXPR_OP_LTE_FAILS                               \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_INTEGER "' "       \
         PCMK_XA_OPERATION "='" PCMK_VALUE_LTE "' "      \
         PCMK_XA_ATTRIBUTE "='num' "                     \
         PCMK_XA_VALUE "='9' />"
 
 static void
 op_lte_fails(void **state)
 {
     assert_attr_expression(EXPR_OP_LTE_FAILS, pcmk_rc_after_range);
 }
 
 #define EXPR_OP_GTE_GT_PASSES                           \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_INTEGER "' "       \
         PCMK_XA_OPERATION "='" PCMK_VALUE_GTE "' "      \
         PCMK_XA_ATTRIBUTE "='num' "                     \
         PCMK_XA_VALUE "='1' />"
 
 static void
 op_gte_gt_passes(void **state)
 {
     assert_attr_expression(EXPR_OP_GTE_GT_PASSES, pcmk_rc_ok);
 }
 
 #define EXPR_OP_GTE_EQ_PASSES                           \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_INTEGER "' "       \
         PCMK_XA_OPERATION "='" PCMK_VALUE_GTE "' "      \
         PCMK_XA_ATTRIBUTE "='num' "                     \
         PCMK_XA_VALUE "='10' />"
 
 static void
 op_gte_eq_passes(void **state)
 {
     assert_attr_expression(EXPR_OP_GTE_EQ_PASSES, pcmk_rc_ok);
 }
 
 #define EXPR_OP_GTE_FAILS                               \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_INTEGER "' "       \
         PCMK_XA_OPERATION "='" PCMK_VALUE_GTE "' "      \
         PCMK_XA_ATTRIBUTE "='num' "                     \
         PCMK_XA_VALUE "='11' />"
 
 static void
 op_gte_fails(void **state)
 {
     assert_attr_expression(EXPR_OP_GTE_FAILS, pcmk_rc_before_range);
 }
 
 // This also tests that string is used if values aren't parseable as numbers
 #define EXPR_OP_EQ_PASSES                               \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_NUMBER "' "        \
         PCMK_XA_ATTRIBUTE "='foo' "                     \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_VALUE "='bar' "                         \
         PCMK_XA_VALUE_SOURCE "='" PCMK_VALUE_LITERAL "' />"
 
 static void
 op_eq_passes(void **state)
 {
     assert_attr_expression(EXPR_OP_EQ_PASSES, pcmk_rc_ok);
 }
 
 #define EXPR_OP_EQ_FAILS                                \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_INTEGER "' "       \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' "       \
         PCMK_XA_ATTRIBUTE "='num' "                     \
         PCMK_XA_VALUE "='bar' />"
 
 static void
 op_eq_fails(void **state)
 {
     assert_attr_expression(EXPR_OP_EQ_FAILS, pcmk_rc_op_unsatisfied);
 }
 
 #define EXPR_OP_NE_PASSES                               \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_STRING "' "        \
         PCMK_XA_ATTRIBUTE "='foo' "                     \
         PCMK_XA_OPERATION "='" PCMK_VALUE_NE "' "       \
         PCMK_XA_VALUE "='bat' "                         \
         PCMK_XA_VALUE_SOURCE "='" PCMK_VALUE_LITERAL "' />"
 
 static void
 op_ne_passes(void **state)
 {
     assert_attr_expression(EXPR_OP_NE_PASSES, pcmk_rc_ok);
 }
 
 #define EXPR_OP_NE_FAILS                                \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_TYPE "='" PCMK_VALUE_INTEGER "' "       \
         PCMK_XA_OPERATION "='" PCMK_VALUE_NE "' "       \
         PCMK_XA_ATTRIBUTE "='num' "                     \
         PCMK_XA_VALUE "='10' />"
 
 static void
 op_ne_fails(void **state)
 {
     assert_attr_expression(EXPR_OP_NE_FAILS, pcmk_rc_op_unsatisfied);
 }
 
 #define EXPR_OP_DEFINED_PASSES                          \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='foo' "                     \
         PCMK_XA_OPERATION "='" PCMK_VALUE_DEFINED "' />"
 
 static void
 op_defined_passes(void **state)
 {
     assert_attr_expression(EXPR_OP_DEFINED_PASSES, pcmk_rc_ok);
 }
 
 #define EXPR_OP_DEFINED_FAILS                           \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='boo' "                     \
         PCMK_XA_OPERATION "='" PCMK_VALUE_DEFINED "' />"
 
 static void
 op_defined_fails(void **state)
 {
     assert_attr_expression(EXPR_OP_DEFINED_FAILS, pcmk_rc_op_unsatisfied);
 }
 
 #define EXPR_OP_DEFINED_WITH_VALUE                      \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='foo' "                     \
         PCMK_XA_VALUE "='bar' "                         \
         PCMK_XA_OPERATION "='" PCMK_VALUE_DEFINED "' />"
 
 static void
 op_defined_with_value(void **state)
 {
     assert_attr_expression(EXPR_OP_DEFINED_WITH_VALUE, pcmk_rc_unpack_error);
 }
 
 #define EXPR_OP_UNDEFINED_PASSES                        \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='boo' "                     \
         PCMK_XA_OPERATION "='" PCMK_VALUE_NOT_DEFINED "' />"
 
 static void
 op_undefined_passes(void **state)
 {
     assert_attr_expression(EXPR_OP_UNDEFINED_PASSES, pcmk_rc_ok);
 }
 
 #define EXPR_OP_UNDEFINED_FAILS                         \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='foo' "                     \
         PCMK_XA_OPERATION "='" PCMK_VALUE_NOT_DEFINED "' />"
 
 static void
 op_undefined_fails(void **state)
 {
     assert_attr_expression(EXPR_OP_DEFINED_FAILS, pcmk_rc_op_unsatisfied);
 }
 
 
 /*
  * Test PCMK_XA_VALUE
  */
 
 #define EXPR_VALUE_MISSING_DEFINED_OK                   \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='num' "                     \
         PCMK_XA_OPERATION "='" PCMK_VALUE_DEFINED "' />"
 
 static void
 value_missing_defined_ok(void **state)
 {
     assert_attr_expression(EXPR_VALUE_MISSING_DEFINED_OK, pcmk_rc_ok);
 }
 
 #define EXPR_VALUE_MISSING_EQ_FAILS                     \
         "<" PCMK_XE_EXPRESSION " " PCMK_XA_ID "='e' "   \
         PCMK_XA_ATTRIBUTE "='not-an-attr' "             \
         PCMK_XA_OPERATION "='" PCMK_VALUE_EQ "' />"
 
 static void
 value_missing_eq_fails(void **state)
 {
     assert_attr_expression(EXPR_VALUE_MISSING_EQ_FAILS, pcmk_rc_unpack_error);
 }
 
 
 #define expr_test(f) cmocka_unit_test_setup_teardown(f, setup, teardown)
 
 PCMK__UNIT_TEST(pcmk__xml_test_setup_group, pcmk__xml_test_teardown_group,
                 cmocka_unit_test(null_invalid),
                 expr_test(id_missing),
                 expr_test(attr_missing),
                 expr_test(attr_with_submatch_passes),
                 expr_test(attr_with_submatch_fails),
                 expr_test(source_missing),
                 expr_test(source_invalid),
                 expr_test(source_literal_passes),
                 expr_test(source_literal_value_fails),
                 expr_test(source_literal_attr_fails),
                 expr_test(source_params_missing),
                 expr_test(source_params_passes),
                 expr_test(source_params_fails),
                 expr_test(source_meta_missing),
                 expr_test(source_meta_passes),
                 expr_test(source_meta_fails),
                 expr_test(type_default_number),
                 expr_test(type_default_int),
+                expr_test(type_invalid),
                 expr_test(type_string_passes),
                 expr_test(type_string_fails),
                 expr_test(type_integer_passes),
                 expr_test(type_integer_fails),
                 expr_test(type_integer_truncation),
                 expr_test(type_number_passes),
                 expr_test(type_number_fails),
                 expr_test(type_version_passes),
                 expr_test(type_version_equality),
                 expr_test(type_version_fails),
                 expr_test(op_missing),
                 expr_test(op_invalid),
                 expr_test(op_lt_passes),
                 expr_test(op_lt_fails),
                 expr_test(op_gt_passes),
                 expr_test(op_gt_fails),
                 expr_test(op_lte_lt_passes),
                 expr_test(op_lte_eq_passes),
                 expr_test(op_lte_fails),
                 expr_test(op_gte_gt_passes),
                 expr_test(op_gte_eq_passes),
                 expr_test(op_gte_fails),
                 expr_test(op_eq_passes),
                 expr_test(op_eq_fails),
                 expr_test(op_ne_passes),
                 expr_test(op_ne_fails),
                 expr_test(op_defined_passes),
                 expr_test(op_defined_fails),
                 expr_test(op_defined_with_value),
                 expr_test(op_undefined_passes),
                 expr_test(op_undefined_fails),
                 expr_test(value_missing_defined_ok),
                 expr_test(value_missing_eq_fails))