Skip to content

Commit

Permalink
Add new IsAfterAssertion for yaml rest tests (#103122)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
timgrein authored Dec 8, 2023
1 parent 39d9ce8 commit c6f7e2d
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public final class Features {
"arbitrary_key",
"allowed_warnings",
"allowed_warnings_regex",
"close_to"
"close_to",
"is_after"
);

private Features() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,17 @@ private static Stream<String> validateExecutableSections(
""", 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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ public void testParseLength() throws Exception {
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 }");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

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;
Expand Down Expand Up @@ -715,6 +716,17 @@ public void testAddingCloseToWithSkip() {
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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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));
}
}

0 comments on commit c6f7e2d

Please sign in to comment.