ソースを参照

Introduce exist assertion (#98721)

It is common to check is a field exists in the response json (regardless the
value) in yaml tests. Today this is done using `is_true` assertion if the value
is not "0" otherwise assertion is failing and need to be replaced with either
`gte` or `is_false`. This change introduces the `exist` assertion that allows to
 verify the field exists regardless its value.
Ievgen Degtiarenko 2 年 前
コミット
47a590831a

+ 8 - 0
rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/README.asciidoc

@@ -605,3 +605,11 @@ This depends on the data type of the value being examined, eg:
     - length: { _tokens: 3 }   # the `_tokens` array has 3 elements
     - length: { _source: 5 }   # the `_source` hash has 5 keys
 ....
+
+=== `exists`
+
+Checks if specified path exists with any value (empty string/list/object is permitted).
+
+....
+    - exists:  fields._source  # checks if the fields._source exist
+....

+ 33 - 33
rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/cluster.desired_balance/10_basic.yml

@@ -73,34 +73,34 @@ setup:
   - do:
       _internal.get_desired_balance: { }
 
-  - is_true: 'cluster_balance_stats'
-  - is_true: 'cluster_balance_stats.tiers'
-  - is_true: 'cluster_balance_stats.tiers.data_content.shard_count'
-  - is_true: 'cluster_balance_stats.tiers.data_content.shard_count.total'
-  - is_true: 'cluster_balance_stats.tiers.data_content.shard_count.min'
-  - is_true: 'cluster_balance_stats.tiers.data_content.shard_count.max'
-  - is_true: 'cluster_balance_stats.tiers.data_content.shard_count.average'
-  - is_true: 'cluster_balance_stats.tiers.data_content.shard_count.std_dev'
-  - is_true: 'cluster_balance_stats.tiers.data_content.forecast_write_load'
-  - is_true: 'cluster_balance_stats.tiers.data_content.forecast_write_load.total'
-  - is_true: 'cluster_balance_stats.tiers.data_content.forecast_write_load.min'
-  - is_true: 'cluster_balance_stats.tiers.data_content.forecast_write_load.max'
-  - is_true: 'cluster_balance_stats.tiers.data_content.forecast_write_load.average'
-  - is_true: 'cluster_balance_stats.tiers.data_content.forecast_write_load.std_dev'
-  - is_true: 'cluster_balance_stats.tiers.data_content.forecast_disk_usage'
-  - is_true: 'cluster_balance_stats.tiers.data_content.forecast_disk_usage.total'
-  - is_true: 'cluster_balance_stats.tiers.data_content.forecast_disk_usage.min'
-  - is_true: 'cluster_balance_stats.tiers.data_content.forecast_disk_usage.max'
-  - is_true: 'cluster_balance_stats.tiers.data_content.forecast_disk_usage.average'
-  - is_true: 'cluster_balance_stats.tiers.data_content.forecast_disk_usage.std_dev'
-  - is_true: 'cluster_balance_stats.tiers.data_content.actual_disk_usage'
-  - is_true: 'cluster_balance_stats.tiers.data_content.actual_disk_usage.total'
-  - is_true: 'cluster_balance_stats.tiers.data_content.actual_disk_usage.min'
-  - is_true: 'cluster_balance_stats.tiers.data_content.actual_disk_usage.max'
-  - is_true: 'cluster_balance_stats.tiers.data_content.actual_disk_usage.average'
-  - is_true: 'cluster_balance_stats.tiers.data_content.actual_disk_usage.std_dev'
-  - is_true: 'cluster_balance_stats.nodes'
-  - is_true: 'cluster_balance_stats.nodes.$node_name'
+  - exists: 'cluster_balance_stats'
+  - exists: 'cluster_balance_stats.tiers'
+  - exists: 'cluster_balance_stats.tiers.data_content.shard_count'
+  - exists: 'cluster_balance_stats.tiers.data_content.shard_count.total'
+  - exists: 'cluster_balance_stats.tiers.data_content.shard_count.min'
+  - exists: 'cluster_balance_stats.tiers.data_content.shard_count.max'
+  - exists: 'cluster_balance_stats.tiers.data_content.shard_count.average'
+  - exists: 'cluster_balance_stats.tiers.data_content.shard_count.std_dev'
+  - exists: 'cluster_balance_stats.tiers.data_content.forecast_write_load'
+  - exists: 'cluster_balance_stats.tiers.data_content.forecast_write_load.total'
+  - exists: 'cluster_balance_stats.tiers.data_content.forecast_write_load.min'
+  - exists: 'cluster_balance_stats.tiers.data_content.forecast_write_load.max'
+  - exists: 'cluster_balance_stats.tiers.data_content.forecast_write_load.average'
+  - exists: 'cluster_balance_stats.tiers.data_content.forecast_write_load.std_dev'
+  - exists: 'cluster_balance_stats.tiers.data_content.forecast_disk_usage'
+  - exists: 'cluster_balance_stats.tiers.data_content.forecast_disk_usage.total'
+  - exists: 'cluster_balance_stats.tiers.data_content.forecast_disk_usage.min'
+  - exists: 'cluster_balance_stats.tiers.data_content.forecast_disk_usage.max'
+  - exists: 'cluster_balance_stats.tiers.data_content.forecast_disk_usage.average'
+  - exists: 'cluster_balance_stats.tiers.data_content.forecast_disk_usage.std_dev'
+  - exists: 'cluster_balance_stats.tiers.data_content.actual_disk_usage'
+  - exists: 'cluster_balance_stats.tiers.data_content.actual_disk_usage.total'
+  - exists: 'cluster_balance_stats.tiers.data_content.actual_disk_usage.min'
+  - exists: 'cluster_balance_stats.tiers.data_content.actual_disk_usage.max'
+  - exists: 'cluster_balance_stats.tiers.data_content.actual_disk_usage.average'
+  - exists: 'cluster_balance_stats.tiers.data_content.actual_disk_usage.std_dev'
+  - exists: 'cluster_balance_stats.nodes'
+  - exists: 'cluster_balance_stats.nodes.$node_name'
   - gte: { 'cluster_balance_stats.nodes.$node_name.shard_count' : 0 }
   - gte: { 'cluster_balance_stats.nodes.$node_name.forecast_write_load': 0.0 }
   - gte: { 'cluster_balance_stats.nodes.$node_name.forecast_disk_usage_bytes' : 0 }
@@ -116,7 +116,7 @@ setup:
   - do:
       _internal.get_desired_balance: { }
 
-  - is_true: 'cluster_info'
+  - exists: 'cluster_info'
 
 ---
 "Test cluster_balance_stats contains node ID and roles":
@@ -133,9 +133,9 @@ setup:
   - do:
       _internal.get_desired_balance: { }
 
-  - is_true: 'cluster_balance_stats.nodes.$node_name'
-  - is_true: 'cluster_balance_stats.nodes.$node_name.node_id'
-  - is_true: 'cluster_balance_stats.nodes.$node_name.roles'
+  - exists: 'cluster_balance_stats.nodes.$node_name'
+  - exists: 'cluster_balance_stats.nodes.$node_name.node_id'
+  - exists: 'cluster_balance_stats.nodes.$node_name.roles'
 
 ---
 "Test tier_preference":
@@ -160,7 +160,7 @@ setup:
   - do:
       _internal.get_desired_balance: { }
 
-  - is_true: 'routing_table.test.0.current.0.tier_preference'
+  - exists: 'routing_table.test.0.current.0.tier_preference'
 
 ---
 "Test computed_shard_movements":

+ 2 - 1
test/yaml-rest-runner/src/main/java/org/elasticsearch/test/rest/yaml/section/ExecutableSection.java

@@ -36,7 +36,8 @@ public interface ExecutableSection {
         new NamedXContentRegistry.Entry(ExecutableSection.class, new ParseField("lte"), LessThanOrEqualToAssertion::parse),
         new NamedXContentRegistry.Entry(ExecutableSection.class, new ParseField("contains"), ContainsAssertion::parse),
         new NamedXContentRegistry.Entry(ExecutableSection.class, new ParseField("length"), LengthAssertion::parse),
-        new NamedXContentRegistry.Entry(ExecutableSection.class, new ParseField("close_to"), CloseToAssertion::parse)
+        new NamedXContentRegistry.Entry(ExecutableSection.class, new ParseField("close_to"), CloseToAssertion::parse),
+        new NamedXContentRegistry.Entry(ExecutableSection.class, new ParseField("exists"), ExistsAssertion::parse)
     );
 
     /**

+ 49 - 0
test/yaml-rest-runner/src/main/java/org/elasticsearch/test/rest/yaml/section/ExistsAssertion.java

@@ -0,0 +1,49 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.test.rest.yaml.section;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.elasticsearch.xcontent.XContentLocation;
+import org.elasticsearch.xcontent.XContentParser;
+
+import java.io.IOException;
+
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Represents an exists assert section:
+ *
+ *   - exists:  get.fields.bar
+ *
+ */
+public class ExistsAssertion extends Assertion {
+
+    private static final Logger logger = LogManager.getLogger(ExistsAssertion.class);
+
+    public static ExistsAssertion parse(XContentParser parser) throws IOException {
+        return new ExistsAssertion(parser.getTokenLocation(), ParserUtils.parseField(parser));
+    }
+
+    public ExistsAssertion(XContentLocation location, String field) {
+        super(location, field, true);
+    }
+
+    @Override
+    protected void doAssert(Object actualValue, Object expectedValue) {
+        logger.trace("assert that field [{}] exists with any value", getField());
+        String errorMessage = errorMessage();
+        assertThat(errorMessage, actualValue, notNullValue());
+    }
+
+    private String errorMessage() {
+        return "field [" + getField() + "] does not exist";
+    }
+}

+ 15 - 0
test/yaml-rest-runner/src/test/java/org/elasticsearch/test/rest/yaml/section/AssertionTests.java

@@ -9,6 +9,7 @@ package org.elasticsearch.test.rest.yaml.section;
 
 import org.elasticsearch.xcontent.yaml.YamlXContent;
 
+import java.io.IOException;
 import java.util.List;
 import java.util.Map;
 
@@ -170,4 +171,18 @@ public class AssertionTests extends AbstractClientYamlTestFragmentParserTestCase
         exception = expectThrows(IllegalArgumentException.class, () -> CloseToAssertion.parse(parser));
         assertThat(exception.getMessage(), equalTo("value is missing or not a number"));
     }
+
+    public void testExists() throws IOException {
+        parser = createParser(YamlXContent.yamlXContent, "get.fields._timestamp");
+
+        ExistsAssertion existsAssertion = ExistsAssertion.parse(parser);
+
+        assertThat(existsAssertion, notNullValue());
+        assertThat(existsAssertion.getField(), equalTo("get.fields._timestamp"));
+
+        existsAssertion.doAssert(randomFrom(1, "", "non-empty", List.of(), Map.of()), existsAssertion.getExpectedValue());
+
+        AssertionError e = expectThrows(AssertionError.class, () -> existsAssertion.doAssert(null, existsAssertion.getExpectedValue()));
+        assertThat(e.getMessage(), containsString("field [get.fields._timestamp] does not exist"));
+    }
 }