Browse Source

Add new IsAfterAssertion for yaml rest tests (#103122)

Adds a new assertion "is_after", which can be used in the yaml based rest tests to check, whether one instant comes after the other.
Tim Grein 1 year ago
parent
commit
c6f7e2d868

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

@@ -526,6 +526,15 @@ For instance, when testing you may want to base64 encode username and password f
 
 Stashed values can be used as described in the `set` section
 
+=== `is_after`
+
+Used to compare two variables (both need to be of type String, which can be parsed to an Instant) and check, whether
+the first one is after the other one.
+
+....
+    - is_after: { result.some_field: 2023-05-25T12:30:00.000Z }
+....
+
 === `is_true`
 
 The specified key exists and has a true value (ie not `0`, `false`, `undefined`, `null`

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

@@ -39,7 +39,8 @@ public final class Features {
         "arbitrary_key",
         "allowed_warnings",
         "allowed_warnings_regex",
-        "close_to"
+        "close_to",
+        "is_after"
     );
 
     private Features() {

+ 11 - 0
test/yaml-rest-runner/src/main/java/org/elasticsearch/test/rest/yaml/section/ClientYamlTestSuite.java

@@ -272,6 +272,17 @@ public class ClientYamlTestSuite {
                     """, section.getLocation().lineNumber()))
         );
 
+        errors = Stream.concat(
+            errors,
+            sections.stream()
+                .filter(section -> section instanceof IsAfterAssertion)
+                .filter(section -> false == hasSkipFeature("is_after", testSection, setupSection, teardownSection))
+                .map(section -> String.format(Locale.ROOT, """
+                    attempted to add an [is_after] assertion without a corresponding ["skip": "features": "is_after"] \
+                    so runners that do not support the [is_after] assertion can skip the test at line [%d]\
+                    """, section.getLocation().lineNumber()))
+        );
+
         return errors;
     }
 

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

@@ -28,6 +28,7 @@ public interface ExecutableSection {
         new NamedXContentRegistry.Entry(ExecutableSection.class, new ParseField("set"), SetSection::parse),
         new NamedXContentRegistry.Entry(ExecutableSection.class, new ParseField("transform_and_set"), TransformAndSetSection::parse),
         new NamedXContentRegistry.Entry(ExecutableSection.class, new ParseField("match"), MatchAssertion::parse),
+        new NamedXContentRegistry.Entry(ExecutableSection.class, new ParseField("is_after"), IsAfterAssertion::parse),
         new NamedXContentRegistry.Entry(ExecutableSection.class, new ParseField("is_true"), IsTrueAssertion::parse),
         new NamedXContentRegistry.Entry(ExecutableSection.class, new ParseField("is_false"), IsFalseAssertion::parse),
         new NamedXContentRegistry.Entry(ExecutableSection.class, new ParseField("gt"), GreaterThanAssertion::parse),

+ 69 - 0
test/yaml-rest-runner/src/main/java/org/elasticsearch/test/rest/yaml/section/IsAfterAssertion.java

@@ -0,0 +1,69 @@
+/*
+ * 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.elasticsearch.core.Tuple;
+import org.elasticsearch.logging.LogManager;
+import org.elasticsearch.logging.Logger;
+import org.elasticsearch.xcontent.XContentLocation;
+import org.elasticsearch.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.time.format.DateTimeParseException;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Represents an is after assert section:
+ *
+ *    - is_after: { result.some_instant: "2023-05-25T12:30:00.000Z" }
+ *
+ */
+public class IsAfterAssertion extends Assertion {
+
+    public static IsAfterAssertion parse(XContentParser parser) throws IOException {
+        XContentLocation location = parser.getTokenLocation();
+        Tuple<String, Object> stringObjectTuple = ParserUtils.parseTuple(parser);
+        return new IsAfterAssertion(location, stringObjectTuple.v1(), stringObjectTuple.v2());
+    }
+
+    private static final Logger logger = LogManager.getLogger(IsAfterAssertion.class);
+
+    public IsAfterAssertion(XContentLocation location, String field, Object expectedValue) {
+        super(location, field, expectedValue);
+    }
+
+    @Override
+    protected void doAssert(Object actualValue, Object expectedValue) {
+        assertNotNull("field [" + getField() + "] is null", actualValue);
+        assertNotNull("value to test against cannot be null", expectedValue);
+
+        Instant fieldInstant = parseToInstant(
+            actualValue.toString(),
+            "field [" + getField() + "] cannot be parsed to " + Instant.class.getSimpleName() + ", got [" + actualValue + "]"
+        );
+        Instant valueInstant = parseToInstant(
+            expectedValue.toString(),
+            "value to test against [" + expectedValue + "] cannot be parsed to " + Instant.class.getSimpleName()
+        );
+
+        logger.trace("assert that [{}] is after [{}] (field [{}])", fieldInstant, valueInstant);
+        assertTrue("field [" + getField() + "] should be after [" + actualValue + "], but was not", fieldInstant.isAfter(valueInstant));
+    }
+
+    private Instant parseToInstant(String string, String onErrorMessage) {
+        try {
+            return Instant.parse(string);
+        } catch (DateTimeParseException e) {
+            throw new AssertionError(onErrorMessage, e);
+        }
+    }
+}

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

@@ -69,6 +69,17 @@ public class AssertionTests extends AbstractClientYamlTestFragmentParserTestCase
         assertThat((Integer) lengthAssertion.getExpectedValue(), equalTo(22));
     }
 
+    public void testParseIsAfter() throws Exception {
+        parser = createParser(YamlXContent.yamlXContent, "{ field: 2021-05-25T12:30:00.000Z}");
+
+        IsAfterAssertion isAfterAssertion = IsAfterAssertion.parse(parser);
+
+        assertThat(isAfterAssertion, notNullValue());
+        assertThat(isAfterAssertion.getField(), equalTo("field"));
+        assertThat(isAfterAssertion.getExpectedValue(), instanceOf(String.class));
+        assertThat(isAfterAssertion.getExpectedValue(), equalTo("2021-05-25T12:30:00.000Z"));
+    }
+
     public void testParseMatchSimpleIntegerValue() throws Exception {
         parser = createParser(YamlXContent.yamlXContent, "{ field: 10 }");
 

+ 12 - 0
test/yaml-rest-runner/src/test/java/org/elasticsearch/test/rest/yaml/section/ClientYamlTestSuiteTests.java

@@ -17,6 +17,7 @@ import org.elasticsearch.xcontent.yaml.YamlXContent;
 
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -715,6 +716,17 @@ public class ClientYamlTestSuiteTests extends AbstractClientYamlTestFragmentPars
         createTestSuite(skipSection, closeToAssertion).validate();
     }
 
+    public void testAddingIsAfterWithSkip() {
+        int lineNumber = between(1, 10000);
+        SkipSection skipSection = new SkipSection(null, singletonList("is_after"), emptyList(), null);
+        IsAfterAssertion isAfterAssertion = new IsAfterAssertion(
+            new XContentLocation(lineNumber, 0),
+            randomAlphaOfLength(randomIntBetween(3, 30)),
+            randomInstantBetween(Instant.ofEpochSecond(0L), Instant.ofEpochSecond(3000000000L))
+        );
+        createTestSuite(skipSection, isAfterAssertion).validate();
+    }
+
     private static ClientYamlTestSuite createTestSuite(SkipSection skipSection, ExecutableSection executableSection) {
         final SetupSection setupSection;
         final TeardownSection teardownSection;

+ 63 - 0
test/yaml-rest-runner/src/test/java/org/elasticsearch/test/rest/yaml/section/IsAfterAssertionTests.java

@@ -0,0 +1,63 @@
+/*
+ * 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.elasticsearch.xcontent.XContentLocation;
+
+import java.io.IOException;
+
+public class IsAfterAssertionTests extends AbstractClientYamlTestFragmentParserTestCase {
+
+    public void testParseIsAfterAssertionWithNonInstantValue() {
+        XContentLocation xContentLocation = new XContentLocation(0, 0);
+        IsAfterAssertion isAfterAssertion = new IsAfterAssertion(xContentLocation, "some_field", "non instant value");
+
+        expectThrows(AssertionError.class, () -> isAfterAssertion.doAssert("2022-05-25T12:30:00.000Z", "non instant value"));
+    }
+
+    public void testIsAfter() {
+        String field = "some_field";
+
+        // actual value one year after value to test against
+        String actualValue = "2022-05-25T12:30:00.000Z";
+        String expectedValue = "2021-05-25T12:30:00.000Z";
+
+        XContentLocation xContentLocation = new XContentLocation(0, 0);
+        IsAfterAssertion isAfterAssertion = new IsAfterAssertion(xContentLocation, field, expectedValue);
+
+        isAfterAssertion.doAssert(actualValue, expectedValue);
+    }
+
+    public void testIsNotAfter() {
+        String field = "some_field";
+
+        // actual value one year before value to test against
+        String actualValue = "2020-05-25T12:30:00.000Z";
+        String expectedValue = "2021-05-25T12:30:00.000Z";
+
+        XContentLocation xContentLocation = new XContentLocation(0, 0);
+        IsAfterAssertion isAfterAssertion = new IsAfterAssertion(xContentLocation, field, expectedValue);
+
+        expectThrows(AssertionError.class, () -> isAfterAssertion.doAssert(actualValue, expectedValue));
+    }
+
+    public void testActualValueIsNull() {
+        XContentLocation xContentLocation = new XContentLocation(0, 0);
+        IsAfterAssertion isAfterAssertion = new IsAfterAssertion(xContentLocation, "field", "2021-05-25T12:30:00.000Z");
+
+        expectThrows(AssertionError.class, () -> isAfterAssertion.doAssert(null, "2021-05-25T12:30:00.000Z"));
+    }
+
+    public void testExpectedValueIsNull() throws IOException {
+        XContentLocation xContentLocation = new XContentLocation(0, 0);
+        IsAfterAssertion isAfterAssertion = new IsAfterAssertion(xContentLocation, "field", "2021-05-25T12:30:00.000Z");
+
+        expectThrows(AssertionError.class, () -> isAfterAssertion.doAssert("2021-05-25T12:30:00.000Z", null));
+    }
+}